| /* |
| * Copyright (C) 2021 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.systemui.usb; |
| |
| import static android.Manifest.permission.RECORD_AUDIO; |
| |
| import android.app.Activity; |
| import android.app.PendingIntent; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.PermissionChecker; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ResolveInfo; |
| import android.hardware.usb.IUsbManager; |
| import android.hardware.usb.UsbAccessory; |
| import android.hardware.usb.UsbDevice; |
| import android.hardware.usb.UsbManager; |
| import android.os.IBinder; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.os.UserHandle; |
| import android.util.Log; |
| |
| /** |
| * Helper class to separate model and view for USB permission and confirm dialogs. |
| */ |
| public class UsbDialogHelper { |
| private static final String TAG = UsbDialogHelper.class.getSimpleName(); |
| private static final String EXTRA_RESOLVE_INFO = "rinfo"; |
| |
| private final UsbDevice mDevice; |
| private final UsbAccessory mAccessory; |
| private final ResolveInfo mResolveInfo; |
| private final String mPackageName; |
| private final CharSequence mAppName; |
| private final Context mContext; |
| private final PendingIntent mPendingIntent; |
| private final IUsbManager mUsbService; |
| private final int mUid; |
| private final boolean mCanBeDefault; |
| |
| private UsbDisconnectedReceiver mDisconnectedReceiver; |
| private boolean mIsUsbDevice; |
| private boolean mResponseSent; |
| |
| /** |
| * @param context The Context of the caller. |
| * @param intent The intent of the caller. |
| * @throws IllegalStateException Thrown if both UsbDevice and UsbAccessory are null or if the |
| * query for the matching ApplicationInfo is unsuccessful. |
| */ |
| public UsbDialogHelper(Context context, Intent intent) throws IllegalStateException { |
| mContext = context; |
| mDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); |
| mAccessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); |
| mCanBeDefault = intent.getBooleanExtra(UsbManager.EXTRA_CAN_BE_DEFAULT, false); |
| if (mDevice == null && mAccessory == null) { |
| throw new IllegalStateException("Device and accessory are both null."); |
| } |
| if (mDevice != null) { |
| mIsUsbDevice = true; |
| } |
| mResolveInfo = intent.getParcelableExtra(EXTRA_RESOLVE_INFO); |
| PackageManager packageManager = mContext.getPackageManager(); |
| if (mResolveInfo != null) { |
| // If a ResolveInfo is provided it will be used to determine the activity to start |
| mUid = mResolveInfo.activityInfo.applicationInfo.uid; |
| mPackageName = mResolveInfo.activityInfo.packageName; |
| mPendingIntent = null; |
| } else { |
| mUid = intent.getIntExtra(Intent.EXTRA_UID, -1); |
| mPackageName = intent.getStringExtra(UsbManager.EXTRA_PACKAGE); |
| mPendingIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT); |
| } |
| try { |
| ApplicationInfo aInfo = packageManager.getApplicationInfo(mPackageName, 0); |
| mAppName = aInfo.loadLabel(packageManager); |
| } catch (PackageManager.NameNotFoundException e) { |
| throw new IllegalStateException("unable to look up package name", e); |
| } |
| IBinder b = ServiceManager.getService(Context.USB_SERVICE); |
| mUsbService = IUsbManager.Stub.asInterface(b); |
| } |
| |
| /** |
| * Registers UsbDisconnectedReceiver to dismiss dialog automatically when device or accessory |
| * gets disconnected |
| * @param activity The activity to finish when device / accessory gets disconnected. |
| */ |
| public void registerUsbDisconnectedReceiver(Activity activity) { |
| if (mIsUsbDevice) { |
| mDisconnectedReceiver = new UsbDisconnectedReceiver(activity, mDevice); |
| } else { |
| mDisconnectedReceiver = new UsbDisconnectedReceiver(activity, mAccessory); |
| } |
| } |
| |
| /** |
| * Unregisters the UsbDisconnectedReceiver. To be called when the activity is destroyed. |
| * @param activity The activity registered to finish when device / accessory gets disconnected. |
| */ |
| public void unregisterUsbDisconnectedReceiver(Activity activity) { |
| if (mDisconnectedReceiver != null) { |
| try { |
| activity.unregisterReceiver(mDisconnectedReceiver); |
| } catch (Exception e) { |
| // pass |
| } |
| mDisconnectedReceiver = null; |
| } |
| } |
| |
| /** |
| * @return True if the intent contains a UsbDevice which can capture audio. |
| */ |
| public boolean deviceHasAudioCapture() { |
| return mDevice != null && mDevice.getHasAudioCapture(); |
| } |
| |
| /** |
| * @return True if the intent contains a UsbDevice which can play audio. |
| */ |
| public boolean deviceHasAudioPlayback() { |
| return mDevice != null && mDevice.getHasAudioPlayback(); |
| } |
| |
| /** |
| * @return True if the package has RECORD_AUDIO permission specified in its manifest. |
| */ |
| public boolean packageHasAudioRecordingPermission() { |
| return PermissionChecker.checkPermissionForPreflight(mContext, RECORD_AUDIO, |
| PermissionChecker.PID_UNKNOWN, mUid, mPackageName) |
| == android.content.pm.PackageManager.PERMISSION_GRANTED; |
| } |
| |
| /** |
| * @return True if the intent contains a UsbDevice. |
| */ |
| public boolean isUsbDevice() { |
| return mIsUsbDevice; |
| } |
| |
| /** |
| * @return True if the intent contains a UsbAccessory. |
| */ |
| public boolean isUsbAccessory() { |
| return !mIsUsbDevice; |
| } |
| |
| /** |
| * Grants USB permission to the device / accessory to the calling uid. |
| */ |
| public void grantUidAccessPermission() { |
| try { |
| if (mIsUsbDevice) { |
| mUsbService.grantDevicePermission(mDevice, mUid); |
| } else { |
| mUsbService.grantAccessoryPermission(mAccessory, mUid); |
| } |
| } catch (RemoteException e) { |
| Log.e(TAG, "IUsbService connection failed", e); |
| } |
| } |
| |
| /** |
| * Sets the package as default for the device / accessory. |
| */ |
| public void setDefaultPackage() { |
| final int userId = UserHandle.myUserId(); |
| try { |
| if (mIsUsbDevice) { |
| mUsbService.setDevicePackage(mDevice, mPackageName, userId); |
| } else { |
| mUsbService.setAccessoryPackage(mAccessory, mPackageName, userId); |
| } |
| } catch (RemoteException e) { |
| Log.e(TAG, "IUsbService connection failed", e); |
| } |
| } |
| |
| /** |
| * Clears the default package of the device / accessory. |
| */ |
| public void clearDefaultPackage() { |
| final int userId = UserHandle.myUserId(); |
| try { |
| if (mIsUsbDevice) { |
| mUsbService.setDevicePackage(mDevice, null, userId); |
| } else { |
| mUsbService.setAccessoryPackage(mAccessory, null, userId); |
| } |
| } catch (RemoteException e) { |
| Log.e(TAG, "IUsbService connection failed", e); |
| } |
| } |
| |
| /** |
| * Starts the activity which was selected to handle the device / accessory. |
| */ |
| public void confirmDialogStartActivity() { |
| final int userId = UserHandle.myUserId(); |
| Intent intent; |
| |
| if (mIsUsbDevice) { |
| intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED); |
| intent.putExtra(UsbManager.EXTRA_DEVICE, mDevice); |
| } else { |
| intent = new Intent(UsbManager.ACTION_USB_ACCESSORY_ATTACHED); |
| intent.putExtra(UsbManager.EXTRA_ACCESSORY, mAccessory); |
| } |
| intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| intent.setComponent( |
| new ComponentName(mResolveInfo.activityInfo.packageName, |
| mResolveInfo.activityInfo.name)); |
| try { |
| mContext.startActivityAsUser(intent, new UserHandle(userId)); |
| } catch (Exception e) { |
| Log.e(TAG, "Unable to start activity", e); |
| } |
| } |
| |
| /** |
| * Sends the result of the permission dialog via the provided PendingIntent. |
| * |
| * @param permissionGranted True if the user pressed ok in the permission dialog. |
| */ |
| public void sendPermissionDialogResponse(boolean permissionGranted) { |
| if (!mResponseSent) { |
| // send response via pending intent |
| Intent intent = new Intent(); |
| if (mIsUsbDevice) { |
| intent.putExtra(UsbManager.EXTRA_DEVICE, mDevice); |
| } else { |
| intent.putExtra(UsbManager.EXTRA_ACCESSORY, mAccessory); |
| } |
| intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, permissionGranted); |
| try { |
| mPendingIntent.send(mContext, 0, intent); |
| mResponseSent = true; |
| } catch (PendingIntent.CanceledException e) { |
| Log.w(TAG, "PendingIntent was cancelled"); |
| } |
| } |
| } |
| |
| /** |
| * @return A description of the device / accessory |
| */ |
| public String getDeviceDescription() { |
| String desc; |
| if (mIsUsbDevice) { |
| desc = mDevice.getProductName(); |
| if (desc == null) { |
| desc = mDevice.getDeviceName(); |
| } |
| } else { |
| // UsbAccessory |
| desc = mAccessory.getDescription(); |
| if (desc == null) { |
| desc = String.format("%s %s", mAccessory.getManufacturer(), mAccessory.getModel()); |
| } |
| } |
| return desc; |
| } |
| |
| /** |
| * Whether the calling package can set as default handler of the USB device or accessory. |
| * In case of a UsbAccessory this is the case if the calling package has an intent filter for |
| * {@link UsbManager#ACTION_USB_ACCESSORY_ATTACHED} with a usb-accessory filter matching the |
| * attached accessory. In case of a UsbDevice this is the case if the calling package has an |
| * intent filter for {@link UsbManager#ACTION_USB_DEVICE_ATTACHED} with a usb-device filter |
| * matching the attached device. |
| * |
| * @return True if the package can be default for the USB device. |
| */ |
| public boolean canBeDefault() { |
| return mCanBeDefault; |
| } |
| |
| /** |
| * @return The name of the app which requested permission or the name of the app which will be |
| * opened if the user allows it to handle the USB device. |
| */ |
| public CharSequence getAppName() { |
| return mAppName; |
| } |
| } |