从 app 申请 usbdevice 权限说起,通用 app 申请 usbdevice 权限代码如下
一般通过监听 ACTION_USB_DEVICE_ATTACHED usb 设备插入广播获取 UsbDevice 设备,或者通过 UsbManager 枚举出我们感兴趣的 UsbDevice 设备
然后调用 requestPermission(UsbDevice, PendingIntent) 申请权限,这时候系统一般会弹对话框询问是否允许访问usb设备,用户点确定或取消都将
收到自定义的回调广播 ACTION_USB_PERMISSION
private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION";
public void getPermission() {
HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();
Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
PendingIntent mPermissionIntent = PendingIntent.getBroadcast(AdbActivity.this, 0, new Intent(ACTION_USB_PERMISSION), 0);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(mUsbReceiver, filter);
while(deviceIterator.hasNext()){
UsbDevice device = deviceIterator.next();
Log.d("AdbActivity", "getPermission: "+device.getDeviceName());
if (device.getDeviceName().contains("/dev/bus/usb/001")) {
if (usbManager.hasPermission(device)){
Toast.makeText(AdbActivity.this,"已授权", Toast.LENGTH_SHORT).show();
usbManager.openDevice(device);
}else {
usbManager.requestPermission(device, mPermissionIntent);
}
}
}
}
private BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ACTION_USB_PERMISSION.equals(action)){
synchronized (this) {
UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
if(device != null){
usbManager.openDevice(device);
hintText.setText("openDevice");
}
}else {
Toast.makeText(context,"permission denied for device ", Toast.LENGTH_SHORT).show();
Log.d("AdbActivity", "permission denied for device " + device);
}
}
}
}
};
接下来分析下系统弹权限框已经授权流程
UsbManager 其实调用 UsbService 中 requestDevicePermission()
UsbService 调用 UsbUserSettingsManager 中 requestPermission()
frameworks\base\services\usb\java\com\android\server\usb\UsbService.java
@Override
public void requestDevicePermission(UsbDevice device, String packageName, PendingIntent pi) {
final int uid = Binder.getCallingUid();
final int userId = UserHandle.getUserId(uid);
final long token = Binder.clearCallingIdentity();
try {
getSettingsForUser(userId).requestPermission(device, packageName, pi, uid);
} finally {
Binder.restoreCallingIdentity(token);
}
}
private UsbUserSettingsManager getSettingsForUser(@UserIdInt int userIdInt) {
return mSettingsManager.getSettingsForUser(userIdInt);
}
frameworks\base\services\usb\java\com\android\server\usb\UsbSettingsManager.java
@NonNull UsbUserSettingsManager getSettingsForUser(@UserIdInt int userId) {
synchronized (mSettingsByUser) {
UsbUserSettingsManager settings = mSettingsByUser.get(userId);
if (settings == null) {
settings = new UsbUserSettingsManager(mContext, UserHandle.of(userId),
new UsbPermissionManager(mContext, UserHandle.of(userId)));
mSettingsByUser.put(userId, settings);
}
return settings;
}
}
先判断 UsbDevice 是否已经授权,若已授权,直接将 PendingIntent 扔回去
若无授权,则准备走弹出申请权限框 requestPermissionDialog()
其实最终调用 UsbPermissionManager 中 requestPermissionDialog()
frameworks\base\services\usb\java\com\android\server\usb\UsbUserSettingsManager.java
public void requestPermission(UsbDevice device, String packageName, PendingIntent pi, int uid) {
Intent intent = new Intent();
// respond immediately if permission has already been granted
if (hasPermission(device, packageName, uid)) {
intent.putExtra(UsbManager.EXTRA_DEVICE, device);
intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true);
try {
pi.send(mUserContext, 0, intent);
} catch (PendingIntent.CanceledException e) {
if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled");
}
return;
}
if (isCameraDevicePresent(device)) {
if (!isCameraPermissionGranted(packageName, uid)) {
intent.putExtra(UsbManager.EXTRA_DEVICE, device);
intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);
try {
pi.send(mUserContext, 0, intent);
} catch (PendingIntent.CanceledException e) {
if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled");
}
return;
}
}
requestPermissionDialog(device, null, canBeDefault(device, packageName), packageName, pi,
uid);
}
private void requestPermissionDialog(@Nullable UsbDevice device,
@Nullable UsbAccessory accessory,
boolean canBeDefault,
String packageName,
PendingIntent pi,
int uid) {
// compare uid with packageName to foil apps pretending to be someone else
try {
ApplicationInfo aInfo = mPackageManager.getApplicationInfo(packageName, 0);
if (aInfo.uid != uid) {
throw new IllegalArgumentException("package " + packageName +
" does not match caller's uid " + uid);
}
} catch (PackageManager.NameNotFoundException e) {
throw new IllegalArgumentException("package " + packageName + " not found");
}
mUsbPermissionManager.requestPermissionDialog(device,
accessory, canBeDefault, packageName, uid, mUserContext, pi);
}
可以看到直接指定了包名和类名,最终是拉起 SystemUI 中 UsbPermissionActivity
frameworks\base\services\usb\java\com\android\server\usb\UsbPermissionManager.java
/**
* Creates UI dialog to request permission for the given package to access the device
* or accessory.
*
* @param device The USB device attached
* @param accessory The USB accessory attached
* @param canBeDefault Whether the calling pacakge can set as default handler
* of the USB device or accessory
* @param packageName The package name of the calling package
* @param uid The uid of the calling package
* @param userContext The context to start the UI dialog
* @param pi PendingIntent for returning result
*/
void requestPermissionDialog(@Nullable UsbDevice device,
@Nullable UsbAccessory accessory,
boolean canBeDefault,
@NonNull String packageName,
int uid,
@NonNull Context userContext,
@NonNull PendingIntent pi) {
long identity = Binder.clearCallingIdentity();
Intent intent = new Intent();
if (device != null) {
intent.putExtra(UsbManager.EXTRA_DEVICE, device);
} else {
intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
}
intent.putExtra(Intent.EXTRA_INTENT, pi);
intent.putExtra(Intent.EXTRA_UID, uid);
intent.putExtra(UsbManager.EXTRA_CAN_BE_DEFAULT, canBeDefault);
intent.putExtra(UsbManager.EXTRA_PACKAGE, packageName);
intent.setClassName("com.android.systemui",
"com.android.systemui.usb.UsbPermissionActivity");
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
userContext.startActivityAsUser(intent, mUser);
} catch (ActivityNotFoundException e) {
Slog.e(LOG_TAG, "unable to start UsbPermissionActivity");
} finally {
Binder.restoreCallingIdentity(identity);
}
}
UsbPermissionActivity 就一个普通 AlertActivity,通常很多系统开发人员做默认授权时都会来改这里,
将 mPermissionGranted=true,直接 finish() 这样就能达到默认授权且不弹框的目的,因为最终授权是在ondestory()中
核心方法就下面两个,一个是仅授权一次,另一个是总是授权(前提是apk没被卸载,apk卸载后授权也会情况)
service.grantDevicePermission(mDevice, mUid);
service.setDevicePackage(mDevice, mPackageName, userId);
饶了半天又得回到 UsbService 中了
frameworks\base\packages\SystemUI\src\com\android\systemui\usb\UsbPermissionActivity.java
<activity android:name=".usb.UsbPermissionActivity"
android:exported="true"
android:permission="android.permission.MANAGE_USB"
android:theme="@style/Theme.SystemUI.Dialog.Alert"
android:finishOnCloseSystemDialogs="true"
android:excludeFromRecents="true">
</activity>
@Override
public void onDestroy() {
IBinder b = ServiceManager.getService(USB_SERVICE);
IUsbManager service = IUsbManager.Stub.asInterface(b);
// send response via pending intent
Intent intent = new Intent();
try {
if (mDevice != null) {
intent.putExtra(UsbManager.EXTRA_DEVICE, mDevice);
if (mPermissionGranted) {
service.grantDevicePermission(mDevice, mUid);
if (mAlwaysUse != null && mAlwaysUse.isChecked()) {
final int userId = UserHandle.getUserId(mUid);
service.setDevicePackage(mDevice, mPackageName, userId);
}
}
}
if (mAccessory != null) {
intent.putExtra(UsbManager.EXTRA_ACCESSORY, mAccessory);
if (mPermissionGranted) {
service.grantAccessoryPermission(mAccessory, mUid);
if (mAlwaysUse != null && mAlwaysUse.isChecked()) {
final int userId = UserHandle.getUserId(mUid);
service.setAccessoryPackage(mAccessory, mPackageName, userId);
}
}
}
intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, mPermissionGranted);
mPendingIntent.send(this, 0, intent);
} catch (PendingIntent.CanceledException e) {
Log.w(TAG, "PendingIntent was cancelled");
} catch (RemoteException e) {
Log.e(TAG, "IUsbService connection failed", e);
}
if (mDisconnectedReceiver != null) {
unregisterReceiver(mDisconnectedReceiver);
}
super.onDestroy();
}
frameworks\base\services\usb\java\com\android\server\usb\UsbService.java
frameworks\base\services\usb\java\com\android\server\usb\UsbUserSettingsManager.java
frameworks\base\services\usb\java\com\android\server\usb\UsbPermissionManager.java
可以看到仅授权一次其实最终就是将 device 和 uid 保存到 mDevicePermissionMap 中
看注释可知这只是一个临时的权限机制
/** Temporary mapping USB device name to list of UIDs with permissions for the device*/
private final HashMap<String, SparseBooleanArray> mDevicePermissionMap = new HashMap<>();
@Override
public void grantDevicePermission(UsbDevice device, int uid) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
final int userId = UserHandle.getUserId(uid);
final long token = Binder.clearCallingIdentity();
try {
getSettingsForUser(userId).grantDevicePermission(device, uid);
} finally {
Binder.restoreCallingIdentity(token);
}
}
public void grantDevicePermission(UsbDevice device, int uid) {
mUsbPermissionManager.grantDevicePermission(device, uid);
}
void grantDevicePermission(@NonNull UsbDevice device, int uid) {
synchronized (mLock) {
String deviceName = device.getDeviceName();
SparseBooleanArray uidList = mDevicePermissionMap.get(deviceName);
if (uidList == null) {
uidList = new SparseBooleanArray(1);
mDevicePermissionMap.put(deviceName, uidList);
}
uidList.put(uid, true);
}
}
boolean hasPermission(@NonNull UsbDevice device, int uid) {
synchronized (mLock) {
if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) {
return true;
}
SparseBooleanArray uidList = mDevicePermissionMap.get(device.getDeviceName());
if (uidList == null) {
return false;
}
return uidList.get(uid);
}
}
再看看下持久保存授权是怎么做到的,调用 UsbProfileGroupSettingsManager 中 setDevicePackage()
packageName 为空,将 device 信息从 mDevicePreferenceMap 中移除,changed 改变重新修改 mSettingsFile 内容
mSettingsFile 实际路径为 data/system/users/0/usb_device_manager.xml
因为安卓支持多用户登录,默认用户id为0,这就是参数中为什么有 userId 原因,用于区分不同用户的操作数据
packageName 不为空,将 device 信息添加到 mDevicePreferenceMap 中,changed 改变写入数据到 mSettingsFile
frameworks\base\services\usb\java\com\android\server\usb\UsbService.java
frameworks\base\services\usb\java\com\android\server\usb\UsbProfileGroupSettingsManager.java
@Override
public void setDevicePackage(UsbDevice device, String packageName, int userId) {
device = Preconditions.checkNotNull(device);
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
UserHandle user = UserHandle.of(userId);
final long token = Binder.clearCallingIdentity();
try {
mSettingsManager.getSettingsForProfileGroup(user).setDevicePackage(device, packageName,
user);
} finally {
Binder.restoreCallingIdentity(token);
}
}
void setDevicePackage(@NonNull UsbDevice device, @Nullable String packageName,
@NonNull UserHandle user) {
DeviceFilter filter = new DeviceFilter(device);
boolean changed;
synchronized (mLock) {
if (packageName == null) {
changed = (mDevicePreferenceMap.remove(filter) != null);
} else {
UserPackage userPackage = new UserPackage(packageName, user);
changed = !userPackage.equals(mDevicePreferenceMap.get(filter));
if (changed) {
mDevicePreferenceMap.put(filter, userPackage);
}
}
if (changed) {
scheduleWriteSettingsLocked();
}
}
}
mSettingsFile = new AtomicFile(new File(
Environment.getUserSystemDirectory(user.getIdentifier()),
"usb_device_manager.xml"), "usb-state");
private void scheduleWriteSettingsLocked() {
if (mIsWriteSettingsScheduled) {
return;
} else {
mIsWriteSettingsScheduled = true;
}
AsyncTask.execute(() -> {
synchronized (mLock) {
FileOutputStream fos = null;
try {
fos = mSettingsFile.startWrite();
FastXmlSerializer serializer = new FastXmlSerializer();
serializer.setOutput(fos, StandardCharsets.UTF_8.name());
serializer.startDocument(null, true);
serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output",
true);
serializer.startTag(null, "settings");
for (DeviceFilter filter : mDevicePreferenceMap.keySet()) {
serializer.startTag(null, "preference");
serializer.attribute(null, "package",
mDevicePreferenceMap.get(filter).packageName);
serializer.attribute(null, "user",
String.valueOf(getSerial(mDevicePreferenceMap.get(filter).user)));
filter.write(serializer);
serializer.endTag(null, "preference");
}
for (AccessoryFilter filter : mAccessoryPreferenceMap.keySet()) {
serializer.startTag(null, "preference");
serializer.attribute(null, "package",
mAccessoryPreferenceMap.get(filter).packageName);
serializer.attribute(null, "user", String.valueOf(
getSerial(mAccessoryPreferenceMap.get(filter).user)));
filter.write(serializer);
serializer.endTag(null, "preference");
}
serializer.endTag(null, "settings");
serializer.endDocument();
mSettingsFile.finishWrite(fos);
} catch (IOException e) {
Slog.e(TAG, "Failed to write settings", e);
if (fos != null) {
mSettingsFile.failWrite(fos);
}
}
mIsWriteSettingsScheduled = false;
}
});
}
最终 DeviceFilter write() 将 usbdevice 关键信息写入 xml 中
frameworks/base/core/java/android/hardware/usb/DeviceFilter.java
public void write(XmlSerializer serializer) throws IOException {
serializer.startTag(null, "usb-device");
if (mVendorId != -1) {
serializer.attribute(null, "vendor-id", Integer.toString(mVendorId));
}
if (mProductId != -1) {
serializer.attribute(null, "product-id", Integer.toString(mProductId));
}
if (mClass != -1) {
serializer.attribute(null, "class", Integer.toString(mClass));
}
if (mSubclass != -1) {
serializer.attribute(null, "subclass", Integer.toString(mSubclass));
}
if (mProtocol != -1) {
serializer.attribute(null, "protocol", Integer.toString(mProtocol));
}
if (mManufacturerName != null) {
serializer.attribute(null, "manufacturer-name", mManufacturerName);
}
if (mProductName != null) {
serializer.attribute(null, "product-name", mProductName);
}
if (mSerialNumber != null) {
serializer.attribute(null, "serial-number", mSerialNumber);
}
serializer.endTag(null, "usb-device");
}
一个完整的 usb_device_manager.xml 信息内容如下
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<settings>
<preference package="com.difengze.carlink" user="0">
<usb-device vendor-id="6353" product-id="15616" class="0" subclass="0" protocol="0" manufacturer-name="Allwinner Technology Inc." product-name="Tina Accessory" serial-number="20080411" />
</preference>
</settings>
流程搞清楚了,针对一些定制太离谱的系统我们可以直接构造出 usb_device_manager.xml 数据,推送到 data/system/users/0/ 路径下,重启设备一次也能达到破解权限目的