1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.usb; 18 19 import android.annotation.NonNull; 20 import android.app.PendingIntent; 21 import android.content.ActivityNotFoundException; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.pm.ApplicationInfo; 25 import android.content.pm.PackageManager; 26 import android.content.pm.PackageManager.NameNotFoundException; 27 import android.hardware.usb.UsbAccessory; 28 import android.hardware.usb.UsbConstants; 29 import android.hardware.usb.UsbDevice; 30 import android.hardware.usb.UsbInterface; 31 import android.hardware.usb.UsbManager; 32 import android.os.Binder; 33 import android.os.Process; 34 import android.os.UserHandle; 35 import android.service.usb.UsbSettingsAccessoryPermissionProto; 36 import android.service.usb.UsbSettingsDevicePermissionProto; 37 import android.service.usb.UsbUserSettingsManagerProto; 38 import android.util.Slog; 39 import android.util.SparseBooleanArray; 40 41 import com.android.internal.util.dump.DualDumpOutputStream; 42 43 import java.util.HashMap; 44 45 class UsbUserSettingsManager { 46 private static final String TAG = "UsbUserSettingsManager"; 47 private static final boolean DEBUG = false; 48 49 private final UserHandle mUser; 50 private final boolean mDisablePermissionDialogs; 51 52 private final Context mUserContext; 53 private final PackageManager mPackageManager; 54 55 // Temporary mapping USB device name to list of UIDs with permissions for the device 56 private final HashMap<String, SparseBooleanArray> mDevicePermissionMap = 57 new HashMap<>(); 58 // Temporary mapping UsbAccessory to list of UIDs with permissions for the accessory 59 private final HashMap<UsbAccessory, SparseBooleanArray> mAccessoryPermissionMap = 60 new HashMap<>(); 61 62 private final Object mLock = new Object(); 63 UsbUserSettingsManager(Context context, UserHandle user)64 public UsbUserSettingsManager(Context context, UserHandle user) { 65 if (DEBUG) Slog.v(TAG, "Creating settings for " + user); 66 67 try { 68 mUserContext = context.createPackageContextAsUser("android", 0, user); 69 } catch (NameNotFoundException e) { 70 throw new RuntimeException("Missing android package"); 71 } 72 73 mPackageManager = mUserContext.getPackageManager(); 74 75 mUser = user; 76 77 mDisablePermissionDialogs = context.getResources().getBoolean( 78 com.android.internal.R.bool.config_disableUsbPermissionDialogs); 79 } 80 81 /** 82 * Remove all access permission for a device. 83 * 84 * @param device The device the permissions are for 85 */ removeDevicePermissions(@onNull UsbDevice device)86 void removeDevicePermissions(@NonNull UsbDevice device) { 87 synchronized (mLock) { 88 mDevicePermissionMap.remove(device.getDeviceName()); 89 } 90 } 91 92 /** 93 * Remove all access permission for a accessory. 94 * 95 * @param accessory The accessory the permissions are for 96 */ removeAccessoryPermissions(@onNull UsbAccessory accessory)97 void removeAccessoryPermissions(@NonNull UsbAccessory accessory) { 98 synchronized (mLock) { 99 mAccessoryPermissionMap.remove(accessory); 100 } 101 } 102 103 /** 104 * Check whether a particular device or any of its interfaces 105 * is of class VIDEO. 106 * 107 * @param device The device that needs to get scanned 108 * @return True in case a VIDEO device or interface is present, 109 * False otherwise. 110 */ isCameraDevicePresent(UsbDevice device)111 private boolean isCameraDevicePresent(UsbDevice device) { 112 if (device.getDeviceClass() == UsbConstants.USB_CLASS_VIDEO) { 113 return true; 114 } 115 116 for (int i = 0; i < device.getInterfaceCount(); i++) { 117 UsbInterface iface = device.getInterface(i); 118 if (iface.getInterfaceClass() == UsbConstants.USB_CLASS_VIDEO) { 119 return true; 120 } 121 } 122 123 return false; 124 } 125 126 /** 127 * Check for camera permission of the calling process. 128 * 129 * @param packageName Package name of the caller. 130 * @param uid Linux uid of the calling process. 131 * 132 * @return True in case camera permission is available, False otherwise. 133 */ isCameraPermissionGranted(String packageName, int uid)134 private boolean isCameraPermissionGranted(String packageName, int uid) { 135 int targetSdkVersion = android.os.Build.VERSION_CODES.P; 136 try { 137 ApplicationInfo aInfo = mPackageManager.getApplicationInfo(packageName, 0); 138 // compare uid with packageName to foil apps pretending to be someone else 139 if (aInfo.uid != uid) { 140 Slog.i(TAG, "Package " + packageName + " does not match caller's uid " + uid); 141 return false; 142 } 143 targetSdkVersion = aInfo.targetSdkVersion; 144 } catch (PackageManager.NameNotFoundException e) { 145 Slog.i(TAG, "Package not found, likely due to invalid package name!"); 146 return false; 147 } 148 149 if (targetSdkVersion >= android.os.Build.VERSION_CODES.P) { 150 int allowed = mUserContext.checkCallingPermission(android.Manifest.permission.CAMERA); 151 if (android.content.pm.PackageManager.PERMISSION_DENIED == allowed) { 152 Slog.i(TAG, "Camera permission required for USB video class devices"); 153 return false; 154 } 155 } 156 157 return true; 158 } 159 hasPermission(UsbDevice device, String packageName, int uid)160 public boolean hasPermission(UsbDevice device, String packageName, int uid) { 161 synchronized (mLock) { 162 if (isCameraDevicePresent(device)) { 163 if (!isCameraPermissionGranted(packageName, uid)) { 164 return false; 165 } 166 } 167 if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) { 168 return true; 169 } 170 SparseBooleanArray uidList = mDevicePermissionMap.get(device.getDeviceName()); 171 if (uidList == null) { 172 return false; 173 } 174 return uidList.get(uid); 175 } 176 } 177 hasPermission(UsbAccessory accessory)178 public boolean hasPermission(UsbAccessory accessory) { 179 synchronized (mLock) { 180 int uid = Binder.getCallingUid(); 181 if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) { 182 return true; 183 } 184 SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory); 185 if (uidList == null) { 186 return false; 187 } 188 return uidList.get(uid); 189 } 190 } 191 checkPermission(UsbDevice device, String packageName, int uid)192 public void checkPermission(UsbDevice device, String packageName, int uid) { 193 if (!hasPermission(device, packageName, uid)) { 194 throw new SecurityException("User has not given permission to device " + device); 195 } 196 } 197 checkPermission(UsbAccessory accessory)198 public void checkPermission(UsbAccessory accessory) { 199 if (!hasPermission(accessory)) { 200 throw new SecurityException("User has not given permission to accessory " + accessory); 201 } 202 } 203 requestPermissionDialog(Intent intent, String packageName, PendingIntent pi)204 private void requestPermissionDialog(Intent intent, String packageName, PendingIntent pi) { 205 final int uid = Binder.getCallingUid(); 206 207 // compare uid with packageName to foil apps pretending to be someone else 208 try { 209 ApplicationInfo aInfo = mPackageManager.getApplicationInfo(packageName, 0); 210 if (aInfo.uid != uid) { 211 throw new IllegalArgumentException("package " + packageName + 212 " does not match caller's uid " + uid); 213 } 214 } catch (PackageManager.NameNotFoundException e) { 215 throw new IllegalArgumentException("package " + packageName + " not found"); 216 } 217 218 long identity = Binder.clearCallingIdentity(); 219 intent.setClassName("com.android.systemui", 220 "com.android.systemui.usb.UsbPermissionActivity"); 221 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 222 intent.putExtra(Intent.EXTRA_INTENT, pi); 223 intent.putExtra("package", packageName); 224 intent.putExtra(Intent.EXTRA_UID, uid); 225 try { 226 mUserContext.startActivityAsUser(intent, mUser); 227 } catch (ActivityNotFoundException e) { 228 Slog.e(TAG, "unable to start UsbPermissionActivity"); 229 } finally { 230 Binder.restoreCallingIdentity(identity); 231 } 232 } 233 requestPermission(UsbDevice device, String packageName, PendingIntent pi, int uid)234 public void requestPermission(UsbDevice device, String packageName, PendingIntent pi, int uid) { 235 Intent intent = new Intent(); 236 237 // respond immediately if permission has already been granted 238 if (hasPermission(device, packageName, uid)) { 239 intent.putExtra(UsbManager.EXTRA_DEVICE, device); 240 intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true); 241 try { 242 pi.send(mUserContext, 0, intent); 243 } catch (PendingIntent.CanceledException e) { 244 if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled"); 245 } 246 return; 247 } 248 if (isCameraDevicePresent(device)) { 249 if (!isCameraPermissionGranted(packageName, uid)) { 250 intent.putExtra(UsbManager.EXTRA_DEVICE, device); 251 intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false); 252 try { 253 pi.send(mUserContext, 0, intent); 254 } catch (PendingIntent.CanceledException e) { 255 if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled"); 256 } 257 return; 258 } 259 } 260 261 // start UsbPermissionActivity so user can choose an activity 262 intent.putExtra(UsbManager.EXTRA_DEVICE, device); 263 requestPermissionDialog(intent, packageName, pi); 264 } 265 requestPermission(UsbAccessory accessory, String packageName, PendingIntent pi)266 public void requestPermission(UsbAccessory accessory, String packageName, PendingIntent pi) { 267 Intent intent = new Intent(); 268 269 // respond immediately if permission has already been granted 270 if (hasPermission(accessory)) { 271 intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); 272 intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true); 273 try { 274 pi.send(mUserContext, 0, intent); 275 } catch (PendingIntent.CanceledException e) { 276 if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled"); 277 } 278 return; 279 } 280 281 intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); 282 requestPermissionDialog(intent, packageName, pi); 283 } 284 grantDevicePermission(UsbDevice device, int uid)285 public void grantDevicePermission(UsbDevice device, int uid) { 286 synchronized (mLock) { 287 String deviceName = device.getDeviceName(); 288 SparseBooleanArray uidList = mDevicePermissionMap.get(deviceName); 289 if (uidList == null) { 290 uidList = new SparseBooleanArray(1); 291 mDevicePermissionMap.put(deviceName, uidList); 292 } 293 uidList.put(uid, true); 294 } 295 } 296 grantAccessoryPermission(UsbAccessory accessory, int uid)297 public void grantAccessoryPermission(UsbAccessory accessory, int uid) { 298 synchronized (mLock) { 299 SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory); 300 if (uidList == null) { 301 uidList = new SparseBooleanArray(1); 302 mAccessoryPermissionMap.put(accessory, uidList); 303 } 304 uidList.put(uid, true); 305 } 306 } 307 dump(@onNull DualDumpOutputStream dump, @NonNull String idName, long id)308 public void dump(@NonNull DualDumpOutputStream dump, @NonNull String idName, long id) { 309 long token = dump.start(idName, id); 310 311 synchronized (mLock) { 312 dump.write("user_id", UsbUserSettingsManagerProto.USER_ID, mUser.getIdentifier()); 313 314 for (String deviceName : mDevicePermissionMap.keySet()) { 315 long devicePermissionToken = dump.start("device_permissions", 316 UsbUserSettingsManagerProto.DEVICE_PERMISSIONS); 317 318 dump.write("device_name", UsbSettingsDevicePermissionProto.DEVICE_NAME, deviceName); 319 320 SparseBooleanArray uidList = mDevicePermissionMap.get(deviceName); 321 int count = uidList.size(); 322 for (int i = 0; i < count; i++) { 323 dump.write("uids", UsbSettingsDevicePermissionProto.UIDS, uidList.keyAt(i)); 324 } 325 326 dump.end(devicePermissionToken); 327 } 328 for (UsbAccessory accessory : mAccessoryPermissionMap.keySet()) { 329 long accessoryPermissionToken = dump.start("accessory_permissions", 330 UsbUserSettingsManagerProto.ACCESSORY_PERMISSIONS); 331 332 dump.write("accessory_description", 333 UsbSettingsAccessoryPermissionProto.ACCESSORY_DESCRIPTION, 334 accessory.getDescription()); 335 336 SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory); 337 int count = uidList.size(); 338 for (int i = 0; i < count; i++) { 339 dump.write("uids", UsbSettingsAccessoryPermissionProto.UIDS, uidList.keyAt(i)); 340 } 341 342 dump.end(accessoryPermissionToken); 343 } 344 } 345 346 dump.end(token); 347 } 348 } 349