blob: ec7567c40e79f4b16b7f3bacc0f25e5df54fa771 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.usb;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.os.Binder;
import android.os.Process;
import android.os.UserHandle;
import android.service.usb.UsbSettingsAccessoryPermissionProto;
import android.service.usb.UsbSettingsDevicePermissionProto;
import android.service.usb.UsbUserSettingsManagerProto;
import android.util.Slog;
import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.dump.DualDumpOutputStream;
import java.util.HashMap;
/**
* UsbUserPermissionManager manages usb device or accessory access permissions.
*
* @hide
*/
class UsbUserPermissionManager {
private static final String LOG_TAG = UsbUserPermissionManager.class.getSimpleName();
private static final boolean DEBUG = false;
@GuardedBy("mLock")
/** Temporary mapping USB device name to list of UIDs with permissions for the device*/
private final HashMap<String, SparseBooleanArray> mDevicePermissionMap =
new HashMap<>();
@GuardedBy("mLock")
/** Temporary mapping UsbAccessory to list of UIDs with permissions for the accessory*/
private final HashMap<UsbAccessory, SparseBooleanArray> mAccessoryPermissionMap =
new HashMap<>();
private final Context mContext;
private final UserHandle mUser;
private final UsbUserSettingsManager mUsbUserSettingsManager;
private final boolean mDisablePermissionDialogs;
private final Object mLock = new Object();
UsbUserPermissionManager(@NonNull Context context, @NonNull UserHandle user,
@NonNull UsbUserSettingsManager usbUserSettingsManager) {
mContext = context;
mUser = user;
mUsbUserSettingsManager = usbUserSettingsManager;
mDisablePermissionDialogs = context.getResources().getBoolean(
com.android.internal.R.bool.config_disableUsbPermissionDialogs);
}
/**
* Removes access permissions of all packages for the USB accessory.
*
* @param accessory to remove permissions for
*/
void removeAccessoryPermissions(@NonNull UsbAccessory accessory) {
synchronized (mLock) {
mAccessoryPermissionMap.remove(accessory);
}
}
/**
* Removes access permissions of all packages for the USB device.
*
* @param device to remove permissions for
*/
void removeDevicePermissions(@NonNull UsbDevice device) {
synchronized (mLock) {
mDevicePermissionMap.remove(device.getDeviceName());
}
}
/**
* Grants permission for USB device without showing system dialog for package with uid.
*
* @param device to grant permission for
* @param uid to grant permission for
*/
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);
}
}
/**
* Grants permission for USB accessory without showing system dialog for package with uid.
*
* @param accessory to grant permission for
* @param uid to grant permission for
*/
void grantAccessoryPermission(@NonNull UsbAccessory accessory, int uid) {
synchronized (mLock) {
SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory);
if (uidList == null) {
uidList = new SparseBooleanArray(1);
mAccessoryPermissionMap.put(accessory, uidList);
}
uidList.put(uid, true);
}
}
/**
* Returns true if package with uid has permission to access the device.
*
* @param device to check permission for
* @param uid to check permission for
* @return {@code true} if package with uid has permission
*/
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);
}
}
/**
* Returns true if caller has permission to access the accessory.
*
* @param accessory to check permission for
* @param uid to check permission for
* @return {@code true} if caller has permssion
*/
boolean hasPermission(@NonNull UsbAccessory accessory, int uid) {
synchronized (mLock) {
if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) {
return true;
}
SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory);
if (uidList == null) {
return false;
}
return uidList.get(uid);
}
}
boolean hasPermission(UsbDevice device, String packageName, int uid) {
if (isCameraDevicePresent(device)) {
if (!isCameraPermissionGranted(packageName, uid)) {
return false;
}
}
return hasPermission(device, uid);
}
/**
* 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);
}
}
void dump(@NonNull DualDumpOutputStream dump) {
synchronized (mLock) {
for (String deviceName : mDevicePermissionMap.keySet()) {
long devicePermissionToken = dump.start("device_permissions",
UsbUserSettingsManagerProto.DEVICE_PERMISSIONS);
dump.write("device_name", UsbSettingsDevicePermissionProto.DEVICE_NAME, deviceName);
SparseBooleanArray uidList = mDevicePermissionMap.get(deviceName);
int count = uidList.size();
for (int i = 0; i < count; i++) {
dump.write("uids", UsbSettingsDevicePermissionProto.UIDS, uidList.keyAt(i));
}
dump.end(devicePermissionToken);
}
for (UsbAccessory accessory : mAccessoryPermissionMap.keySet()) {
long accessoryPermissionToken = dump.start("accessory_permissions",
UsbUserSettingsManagerProto.ACCESSORY_PERMISSIONS);
dump.write("accessory_description",
UsbSettingsAccessoryPermissionProto.ACCESSORY_DESCRIPTION,
accessory.getDescription());
SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory);
int count = uidList.size();
for (int i = 0; i < count; i++) {
dump.write("uids", UsbSettingsAccessoryPermissionProto.UIDS, uidList.keyAt(i));
}
dump.end(accessoryPermissionToken);
}
}
}
/**
* Check for camera permission of the calling process.
*
* @param packageName Package name of the caller.
* @param uid Linux uid of the calling process.
*
* @return True in case camera permission is available, False otherwise.
*/
private boolean isCameraPermissionGranted(String packageName, int uid) {
int targetSdkVersion = android.os.Build.VERSION_CODES.P;
try {
ApplicationInfo aInfo = mContext.getPackageManager().getApplicationInfo(packageName, 0);
// compare uid with packageName to foil apps pretending to be someone else
if (aInfo.uid != uid) {
Slog.i(LOG_TAG, "Package " + packageName + " does not match caller's uid " + uid);
return false;
}
targetSdkVersion = aInfo.targetSdkVersion;
} catch (PackageManager.NameNotFoundException e) {
Slog.i(LOG_TAG, "Package not found, likely due to invalid package name!");
return false;
}
if (targetSdkVersion >= android.os.Build.VERSION_CODES.P) {
int allowed = mContext.checkCallingPermission(android.Manifest.permission.CAMERA);
if (android.content.pm.PackageManager.PERMISSION_DENIED == allowed) {
Slog.i(LOG_TAG, "Camera permission required for USB video class devices");
return false;
}
}
return true;
}
public void checkPermission(UsbDevice device, String packageName, int uid) {
if (!hasPermission(device, packageName, uid)) {
throw new SecurityException("User has not given " + uid + "/" + packageName
+ " permission to access device " + device.getDeviceName());
}
}
public void checkPermission(UsbAccessory accessory, int uid) {
if (!hasPermission(accessory, uid)) {
throw new SecurityException("User has not given " + uid + " permission to accessory "
+ accessory);
}
}
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 = mContext.getPackageManager().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");
}
requestPermissionDialog(device, accessory, canBeDefault, packageName, uid, mContext, pi);
}
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(mContext, 0, intent);
} catch (PendingIntent.CanceledException e) {
if (DEBUG) Slog.d(LOG_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(mContext, 0, intent);
} catch (PendingIntent.CanceledException e) {
if (DEBUG) Slog.d(LOG_TAG, "requestPermission PendingIntent was cancelled");
}
return;
}
}
requestPermissionDialog(device, null,
mUsbUserSettingsManager.canBeDefault(device, packageName), packageName, pi, uid);
}
public void requestPermission(UsbAccessory accessory, String packageName, PendingIntent pi,
int uid) {
// respond immediately if permission has already been granted
if (hasPermission(accessory, uid)) {
Intent intent = new Intent();
intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true);
try {
pi.send(mContext, 0, intent);
} catch (PendingIntent.CanceledException e) {
if (DEBUG) Slog.d(LOG_TAG, "requestPermission PendingIntent was cancelled");
}
return;
}
requestPermissionDialog(null, accessory,
mUsbUserSettingsManager.canBeDefault(accessory, packageName), packageName, pi, uid);
}
/**
* Check whether a particular device or any of its interfaces
* is of class VIDEO.
*
* @param device The device that needs to get scanned
* @return True in case a VIDEO device or interface is present,
* False otherwise.
*/
private boolean isCameraDevicePresent(UsbDevice device) {
if (device.getDeviceClass() == UsbConstants.USB_CLASS_VIDEO) {
return true;
}
for (int i = 0; i < device.getInterfaceCount(); i++) {
UsbInterface iface = device.getInterface(i);
if (iface.getInterfaceClass() == UsbConstants.USB_CLASS_VIDEO) {
return true;
}
}
return false;
}
}