1 /* 2 * Copyright (C) 2021 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.systemui.usb; 18 19 import static android.Manifest.permission.RECORD_AUDIO; 20 21 import android.app.Activity; 22 import android.app.PendingIntent; 23 import android.content.ComponentName; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.PermissionChecker; 27 import android.content.pm.ApplicationInfo; 28 import android.content.pm.PackageManager; 29 import android.content.pm.ResolveInfo; 30 import android.hardware.usb.IUsbManager; 31 import android.hardware.usb.UsbAccessory; 32 import android.hardware.usb.UsbDevice; 33 import android.hardware.usb.UsbManager; 34 import android.os.IBinder; 35 import android.os.RemoteException; 36 import android.os.ServiceManager; 37 import android.os.UserHandle; 38 import android.util.Log; 39 40 /** 41 * Helper class to separate model and view for USB permission and confirm dialogs. 42 */ 43 public class UsbDialogHelper { 44 private static final String TAG = UsbDialogHelper.class.getSimpleName(); 45 private static final String EXTRA_RESOLVE_INFO = "rinfo"; 46 47 private final UsbDevice mDevice; 48 private final UsbAccessory mAccessory; 49 private final ResolveInfo mResolveInfo; 50 private final String mPackageName; 51 private final CharSequence mAppName; 52 private final Context mContext; 53 private final PendingIntent mPendingIntent; 54 private final IUsbManager mUsbService; 55 private final int mUid; 56 private final boolean mCanBeDefault; 57 58 private UsbDisconnectedReceiver mDisconnectedReceiver; 59 private boolean mIsUsbDevice; 60 private boolean mResponseSent; 61 62 /** 63 * @param context The Context of the caller. 64 * @param intent The intent of the caller. 65 * @throws IllegalStateException Thrown if both UsbDevice and UsbAccessory are null or if the 66 * query for the matching ApplicationInfo is unsuccessful. 67 */ UsbDialogHelper(Context context, Intent intent)68 public UsbDialogHelper(Context context, Intent intent) throws IllegalStateException { 69 mContext = context; 70 mDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); 71 mAccessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); 72 mCanBeDefault = intent.getBooleanExtra(UsbManager.EXTRA_CAN_BE_DEFAULT, false); 73 if (mDevice == null && mAccessory == null) { 74 throw new IllegalStateException("Device and accessory are both null."); 75 } 76 if (mDevice != null) { 77 mIsUsbDevice = true; 78 } 79 mResolveInfo = intent.getParcelableExtra(EXTRA_RESOLVE_INFO); 80 PackageManager packageManager = mContext.getPackageManager(); 81 if (mResolveInfo != null) { 82 // If a ResolveInfo is provided it will be used to determine the activity to start 83 mUid = mResolveInfo.activityInfo.applicationInfo.uid; 84 mPackageName = mResolveInfo.activityInfo.packageName; 85 mPendingIntent = null; 86 } else { 87 mUid = intent.getIntExtra(Intent.EXTRA_UID, -1); 88 mPackageName = intent.getStringExtra(UsbManager.EXTRA_PACKAGE); 89 mPendingIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT); 90 } 91 try { 92 ApplicationInfo aInfo = packageManager.getApplicationInfo(mPackageName, 0); 93 mAppName = aInfo.loadLabel(packageManager); 94 } catch (PackageManager.NameNotFoundException e) { 95 throw new IllegalStateException("unable to look up package name", e); 96 } 97 IBinder b = ServiceManager.getService(Context.USB_SERVICE); 98 mUsbService = IUsbManager.Stub.asInterface(b); 99 } 100 101 /** 102 * Registers UsbDisconnectedReceiver to dismiss dialog automatically when device or accessory 103 * gets disconnected 104 * @param activity The activity to finish when device / accessory gets disconnected. 105 */ registerUsbDisconnectedReceiver(Activity activity)106 public void registerUsbDisconnectedReceiver(Activity activity) { 107 if (mIsUsbDevice) { 108 mDisconnectedReceiver = new UsbDisconnectedReceiver(activity, mDevice); 109 } else { 110 mDisconnectedReceiver = new UsbDisconnectedReceiver(activity, mAccessory); 111 } 112 } 113 114 /** 115 * Unregisters the UsbDisconnectedReceiver. To be called when the activity is destroyed. 116 * @param activity The activity registered to finish when device / accessory gets disconnected. 117 */ unregisterUsbDisconnectedReceiver(Activity activity)118 public void unregisterUsbDisconnectedReceiver(Activity activity) { 119 if (mDisconnectedReceiver != null) { 120 try { 121 activity.unregisterReceiver(mDisconnectedReceiver); 122 } catch (Exception e) { 123 // pass 124 } 125 mDisconnectedReceiver = null; 126 } 127 } 128 129 /** 130 * @return True if the intent contains a UsbDevice which can capture audio. 131 */ deviceHasAudioCapture()132 public boolean deviceHasAudioCapture() { 133 return mDevice != null && mDevice.getHasAudioCapture(); 134 } 135 136 /** 137 * @return True if the intent contains a UsbDevice which can play audio. 138 */ deviceHasAudioPlayback()139 public boolean deviceHasAudioPlayback() { 140 return mDevice != null && mDevice.getHasAudioPlayback(); 141 } 142 143 /** 144 * @return True if the package has RECORD_AUDIO permission specified in its manifest. 145 */ packageHasAudioRecordingPermission()146 public boolean packageHasAudioRecordingPermission() { 147 return PermissionChecker.checkPermissionForPreflight(mContext, RECORD_AUDIO, 148 PermissionChecker.PID_UNKNOWN, mUid, mPackageName) 149 == android.content.pm.PackageManager.PERMISSION_GRANTED; 150 } 151 152 /** 153 * @return True if the intent contains a UsbDevice. 154 */ isUsbDevice()155 public boolean isUsbDevice() { 156 return mIsUsbDevice; 157 } 158 159 /** 160 * @return True if the intent contains a UsbAccessory. 161 */ isUsbAccessory()162 public boolean isUsbAccessory() { 163 return !mIsUsbDevice; 164 } 165 166 /** 167 * Grants USB permission to the device / accessory to the calling uid. 168 */ grantUidAccessPermission()169 public void grantUidAccessPermission() { 170 try { 171 if (mIsUsbDevice) { 172 mUsbService.grantDevicePermission(mDevice, mUid); 173 } else { 174 mUsbService.grantAccessoryPermission(mAccessory, mUid); 175 } 176 } catch (RemoteException e) { 177 Log.e(TAG, "IUsbService connection failed", e); 178 } 179 } 180 181 /** 182 * Sets the package as default for the device / accessory. 183 */ setDefaultPackage()184 public void setDefaultPackage() { 185 final int userId = UserHandle.myUserId(); 186 try { 187 if (mIsUsbDevice) { 188 mUsbService.setDevicePackage(mDevice, mPackageName, userId); 189 } else { 190 mUsbService.setAccessoryPackage(mAccessory, mPackageName, userId); 191 } 192 } catch (RemoteException e) { 193 Log.e(TAG, "IUsbService connection failed", e); 194 } 195 } 196 197 /** 198 * Clears the default package of the device / accessory. 199 */ clearDefaultPackage()200 public void clearDefaultPackage() { 201 final int userId = UserHandle.myUserId(); 202 try { 203 if (mIsUsbDevice) { 204 mUsbService.setDevicePackage(mDevice, null, userId); 205 } else { 206 mUsbService.setAccessoryPackage(mAccessory, null, userId); 207 } 208 } catch (RemoteException e) { 209 Log.e(TAG, "IUsbService connection failed", e); 210 } 211 } 212 213 /** 214 * Starts the activity which was selected to handle the device / accessory. 215 */ confirmDialogStartActivity()216 public void confirmDialogStartActivity() { 217 final int userId = UserHandle.myUserId(); 218 Intent intent; 219 220 if (mIsUsbDevice) { 221 intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED); 222 intent.putExtra(UsbManager.EXTRA_DEVICE, mDevice); 223 } else { 224 intent = new Intent(UsbManager.ACTION_USB_ACCESSORY_ATTACHED); 225 intent.putExtra(UsbManager.EXTRA_ACCESSORY, mAccessory); 226 } 227 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 228 intent.setComponent( 229 new ComponentName(mResolveInfo.activityInfo.packageName, 230 mResolveInfo.activityInfo.name)); 231 try { 232 mContext.startActivityAsUser(intent, new UserHandle(userId)); 233 } catch (Exception e) { 234 Log.e(TAG, "Unable to start activity", e); 235 } 236 } 237 238 /** 239 * Sends the result of the permission dialog via the provided PendingIntent. 240 * 241 * @param permissionGranted True if the user pressed ok in the permission dialog. 242 */ sendPermissionDialogResponse(boolean permissionGranted)243 public void sendPermissionDialogResponse(boolean permissionGranted) { 244 if (!mResponseSent) { 245 // send response via pending intent 246 Intent intent = new Intent(); 247 if (mIsUsbDevice) { 248 intent.putExtra(UsbManager.EXTRA_DEVICE, mDevice); 249 } else { 250 intent.putExtra(UsbManager.EXTRA_ACCESSORY, mAccessory); 251 } 252 intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, permissionGranted); 253 try { 254 mPendingIntent.send(mContext, 0, intent); 255 mResponseSent = true; 256 } catch (PendingIntent.CanceledException e) { 257 Log.w(TAG, "PendingIntent was cancelled"); 258 } 259 } 260 } 261 262 /** 263 * @return A description of the device / accessory 264 */ getDeviceDescription()265 public String getDeviceDescription() { 266 String desc; 267 if (mIsUsbDevice) { 268 desc = mDevice.getProductName(); 269 if (desc == null) { 270 desc = mDevice.getDeviceName(); 271 } 272 } else { 273 // UsbAccessory 274 desc = mAccessory.getDescription(); 275 if (desc == null) { 276 desc = String.format("%s %s", mAccessory.getManufacturer(), mAccessory.getModel()); 277 } 278 } 279 return desc; 280 } 281 282 /** 283 * Whether the calling package can set as default handler of the USB device or accessory. 284 * In case of a UsbAccessory this is the case if the calling package has an intent filter for 285 * {@link UsbManager#ACTION_USB_ACCESSORY_ATTACHED} with a usb-accessory filter matching the 286 * attached accessory. In case of a UsbDevice this is the case if the calling package has an 287 * intent filter for {@link UsbManager#ACTION_USB_DEVICE_ATTACHED} with a usb-device filter 288 * matching the attached device. 289 * 290 * @return True if the package can be default for the USB device. 291 */ canBeDefault()292 public boolean canBeDefault() { 293 return mCanBeDefault; 294 } 295 296 /** 297 * @return The name of the app which requested permission or the name of the app which will be 298 * opened if the user allows it to handle the USB device. 299 */ getAppName()300 public CharSequence getAppName() { 301 return mAppName; 302 } 303 } 304