You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android 11应用USB重连时自动授予权限方案咨询

问题

我有一款搭载在自有设备上的Android 11应用,该设备不会安装其他应用,且因硬件老旧不会接收系统更新。应用需通过UART通信实现功能,目前每次连接USB线都会弹出权限申请弹窗,授权后可正常工作,但USB断开重连时会重复弹窗。我已实现弹窗抑制并在本地存储权限记录,但仍无法实现重连时自动授权,直接打开串口会抛出SecurityException。


Manifest代码

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
<uses-feature android:name="android.hardware.usb.host" />
 <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.***themename***"
        android:directBootAware="true"
        tools:targetApi="31">
<activity
            android:name=".MainActivity"
            android:screenOrientation="landscape"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
<receiver
            android:name=".USBReceiver.UsbDeviceReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.example.packagename.USB_PERMISSION" />
                <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
                <action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />
            </intent-filter>
            <meta-data
                android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
                android:resource="@xml/device_filter" />
        </receiver>
</application>
</manifest>

UsbDeviceReceiver代码

public class UsbDeviceReceiver extends BroadcastReceiver {

    private static final String ACTION_USB_PERMISSION = "com.example.packagename.USB_PERMISSION";
    private static final String ACTION_USB_ATTACHED = "android.hardware.usb.action.USB_DEVICE_ATTACHED";
    private static final String ACTION_USB_DETACHED = "android.hardware.usb.action.USB_DEVICE_DETACHED";

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
        UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
        if (ACTION_USB_ATTACHED.equals(action)) {
            // Handle USB device attachment
            showToast(context, "USB Device Attached");


                Log.d("USB PERMISSION","has permission:- "+usbManager.hasPermission(device));
                // Check if permission is already granted for this device
                synchronized (this) {
                    if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                        if (device != null) {
                            Log.d("USB PERMISSION", "permission exists");
                            // Permission granted, open the device
                            SerialPortManagerSingleton.getInstance(context, null).openSerialPort((UsbManager) context.getSystemService(Context.USB_SERVICE));
                        }
                    } else {
                        Log.d("USB PERMISSION", "permission does not exist");
                        // Permission denied
                        PendingIntent permissionIntent = PendingIntent.getBroadcast(
                                context,
                                0,
                                new Intent(ACTION_USB_PERMISSION),
                                PendingIntent.FLAG_UPDATE_CURRENT | (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? PendingIntent.FLAG_IMMUTABLE : 0)
                        );
                        usbManager.requestPermission(device, permissionIntent);
                    }
                }

        } else if (ACTION_USB_DETACHED.equals(action)) {
            // Handle USB device detachment
            showToast(context, "USB Device Detached");
            SerialPortManagerSingleton.releaseInstance();
        } else if (ACTION_USB_PERMISSION.equals(action)) {
            synchronized (this) {
                if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                    if (device != null) {
                        Log.d("USB PERMISSION", "granted permission");
                        Log.d("USB PERMISSION","has permission:- "+usbManager.hasPermission(device));
                        // Permission granted, open the device
                        SerialPortManagerSingleton.getInstance(context, null).openSerialPort((UsbManager) context.getSystemService(Context.USB_SERVICE));
                    }
                } else {
                    Log.d("USB PERMISSION", "Permission denied for device " + device);
                    // Handle permission denial
                }
            }
        }
    }
    private void showToast(Context context, String message) {
        Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
    }
}

SerialPortManagerSingleton代码

public class SerialPortManagerSingleton {
    private static SerialPortManager instance;
    private static SerialPortManager.CombinedSerialListener savedListener;

    public static synchronized SerialPortManager getInstance(Context appContext, SerialPortManager.CombinedSerialListener listener) {
        if (instance == null) {
//            savedListener = listener;
            Log.d("saved","listener "+savedListener);
            instance = new SerialPortManager(savedListener);
            instance.openSerialPort((UsbManager) appContext.getSystemService(Context.USB_SERVICE));
        } else {
            if (listener != null) {
                savedListener = listener;
                Log.d("saved","listener set"+savedListener);
                instance.setListener(savedListener);
            } else if (savedListener != null) {
                instance.setListener(savedListener);
            }
        }
        return instance;
    }

    public static synchronized void releaseInstance() {
        if (instance != null) {
            instance.closeSerialPort();
            instance = null;
//            savedListener = null;
        }
    }
}

解决方案

1. 修正当前代码的权限检查逻辑

你当前在ACTION_USB_ATTACHED分支里错误地通过intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)判断权限——这个Extra仅在ACTION_USB_PERMISSION的Intent中存在,ATTACHED的Intent里并没有该值,导致每次都会触发权限申请。

修改ACTION_USB_ATTACHED的处理逻辑,直接调用usbManager.hasPermission(device)判断权限:

if (ACTION_USB_ATTACHED.equals(action)) {
    showToast(context, "USB Device Attached");
    if (device == null) return;
    
    Log.d("USB PERMISSION","has permission:- "+usbManager.hasPermission(device));
    if (usbManager.hasPermission(device)) {
        // 已有权限,直接打开串口
        SerialPortManagerSingleton.getInstance(context, null).openSerialPort(usbManager);
    } else {
        // 无权限,申请权限
        PendingIntent permissionIntent = PendingIntent.getBroadcast(
                context,
                0,
                new Intent(ACTION_USB_PERMISSION),
                PendingIntent.FLAG_UPDATE_CURRENT | (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? PendingIntent.FLAG_IMMUTABLE : 0)
        );
        usbManager.requestPermission(device, permissionIntent);
    }
}

注意:该方法仅能解决同一应用生命周期内重连的权限问题,重启设备或应用后权限会失效,仍需重新授权。

2. 将应用设为系统特权应用(彻底解决弹窗)

因为设备是自有设备,最彻底的方案是将应用签名为系统应用,直接调用UsbManager.grantPermission()自动授权,无需用户交互:

  • 用设备厂商/自定义ROM的系统签名密钥给应用签名,安装到/system/app或/system/priv-app目录,并在Manifest中添加android:sharedUserId="android.uid.system"。
  • 修改USB连接逻辑:
if (ACTION_USB_ATTACHED.equals(action)) {
    showToast(context, "USB Device Attached");
    if (device == null) return;
    
    // 直接授予权限
    usbManager.grantPermission(device);
    // 打开串口
    SerialPortManagerSingleton.getInstance(context, null).openSerialPort(usbManager);
}

此方案可解决所有场景下的权限弹窗问题,包括设备重启后的重连。

3. 利用设备所有者模式(企业设备方案)

如果设备支持设备所有者模式,可通过DevicePolicyManager设置USB权限白名单:

  • 通过adb命令将应用设为设备所有者:dpm set-device-owner com.example.packagename/.YourDeviceAdminReceiver
  • 在代码中添加USB权限规则:
DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
ComponentName adminComponent = new ComponentName(context, YourDeviceAdminReceiver.class);

// 创建USB设备规则
UsbDevicePolicy usbPolicy = new UsbDevicePolicy.Builder()
        .setDeviceId(device.getDeviceId())
        .setVendorId(device.getVendorId())
        .setProductId(device.getProductId())
        .build();

// 添加规则,允许应用访问该USB设备
dpm.addUsbDevicePermissionRule(adminComponent, usbPolicy, true);

该方案无需系统签名,适合企业批量管理的设备。

内容的提问来源于stack exchange,提问作者Kumar Rakesh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 10:35:08