• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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