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.systemui.usb;
18 
19 import android.annotation.NonNull;
20 import android.app.AlertDialog;
21 import android.app.PendingIntent;
22 import android.content.Context;
23 import android.content.DialogInterface;
24 import android.content.Intent;
25 import android.content.pm.ActivityInfo;
26 import android.content.pm.ApplicationInfo;
27 import android.content.pm.PackageInfo;
28 import android.content.pm.PackageManager;
29 import android.content.res.XmlResourceParser;
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.Bundle;
35 import android.os.IBinder;
36 import android.os.RemoteException;
37 import android.os.ServiceManager;
38 import android.os.UserHandle;
39 import android.util.Log;
40 import android.view.LayoutInflater;
41 import android.view.View;
42 import android.widget.CheckBox;
43 import android.widget.CompoundButton;
44 import android.widget.TextView;
45 
46 import com.android.internal.app.AlertActivity;
47 import com.android.internal.app.AlertController;
48 import com.android.internal.util.XmlUtils;
49 import android.hardware.usb.AccessoryFilter;
50 import android.hardware.usb.DeviceFilter;
51 import com.android.systemui.R;
52 
53 import org.xmlpull.v1.XmlPullParser;
54 
55 public class UsbPermissionActivity extends AlertActivity
56         implements DialogInterface.OnClickListener, CheckBox.OnCheckedChangeListener {
57 
58     private static final String TAG = "UsbPermissionActivity";
59 
60     private CheckBox mAlwaysUse;
61     private TextView mClearDefaultHint;
62     private UsbDevice mDevice;
63     private UsbAccessory mAccessory;
64     private PendingIntent mPendingIntent;
65     private String mPackageName;
66     private int mUid;
67     private boolean mPermissionGranted;
68     private UsbDisconnectedReceiver mDisconnectedReceiver;
69 
70     @Override
onCreate(Bundle icicle)71     public void onCreate(Bundle icicle) {
72         super.onCreate(icicle);
73 
74        Intent intent = getIntent();
75         mDevice = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
76         mAccessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
77         mPendingIntent = (PendingIntent)intent.getParcelableExtra(Intent.EXTRA_INTENT);
78         mUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
79         mPackageName = intent.getStringExtra("package");
80 
81         PackageManager packageManager = getPackageManager();
82         ApplicationInfo aInfo;
83         try {
84             aInfo = packageManager.getApplicationInfo(mPackageName, 0);
85         } catch (PackageManager.NameNotFoundException e) {
86             Log.e(TAG, "unable to look up package name", e);
87             finish();
88             return;
89         }
90         String appName = aInfo.loadLabel(packageManager).toString();
91 
92         final AlertController.AlertParams ap = mAlertParams;
93         ap.mTitle = appName;
94         if (mDevice == null) {
95             ap.mMessage = getString(R.string.usb_accessory_permission_prompt, appName,
96                     mAccessory.getDescription());
97             mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory);
98         } else {
99             ap.mMessage = getString(R.string.usb_device_permission_prompt, appName,
100                     mDevice.getProductName());
101             mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mDevice);
102         }
103         ap.mPositiveButtonText = getString(android.R.string.ok);
104         ap.mNegativeButtonText = getString(android.R.string.cancel);
105         ap.mPositiveButtonListener = this;
106         ap.mNegativeButtonListener = this;
107 
108         try {
109             PackageInfo packageInfo = packageManager.getPackageInfo(mPackageName,
110                     PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA);
111 
112             if ((mDevice != null && canBeDefault(mDevice, packageInfo))
113                     || (mAccessory != null && canBeDefault(mAccessory, packageInfo))) {
114                 // add "open when" checkbox
115                 LayoutInflater inflater = (LayoutInflater) getSystemService(
116                         Context.LAYOUT_INFLATER_SERVICE);
117                 ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null);
118                 mAlwaysUse = (CheckBox) ap.mView.findViewById(com.android.internal.R.id.alwaysUse);
119                 if (mDevice == null) {
120                     mAlwaysUse.setText(getString(R.string.always_use_accessory, appName,
121                             mAccessory.getDescription()));
122                 } else {
123                     mAlwaysUse.setText(getString(R.string.always_use_device, appName,
124                             mDevice.getProductName()));
125                 }
126                 mAlwaysUse.setOnCheckedChangeListener(this);
127 
128                 mClearDefaultHint = (TextView)ap.mView.findViewById(
129                         com.android.internal.R.id.clearDefaultHint);
130                 mClearDefaultHint.setVisibility(View.GONE);
131             }
132         } catch (PackageManager.NameNotFoundException e) {
133             // ignore
134         }
135 
136         setupAlert();
137 
138     }
139 
140     /**
141      * Can the app be the default for the USB device. I.e. can the app be launched by default if
142      * the device is plugged in.
143      *
144      * @param device The device the app would be default for
145      * @param packageInfo The package info of the app
146      *
147      * @return {@code true} iff the app can be default
148      */
canBeDefault(@onNull UsbDevice device, @NonNull PackageInfo packageInfo)149     private boolean canBeDefault(@NonNull UsbDevice device, @NonNull PackageInfo packageInfo) {
150         ActivityInfo[] activities = packageInfo.activities;
151         if (activities != null) {
152             int numActivities = activities.length;
153             for (int i = 0; i < numActivities; i++) {
154                 ActivityInfo activityInfo = activities[i];
155 
156                 try (XmlResourceParser parser = activityInfo.loadXmlMetaData(getPackageManager(),
157                         UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
158                     if (parser == null) {
159                         continue;
160                     }
161 
162                     XmlUtils.nextElement(parser);
163                     while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
164                         if ("usb-device".equals(parser.getName())) {
165                             DeviceFilter filter = DeviceFilter.read(parser);
166                             if (filter.matches(device)) {
167                                 return true;
168                             }
169                         }
170 
171                         XmlUtils.nextElement(parser);
172                     }
173                 } catch (Exception e) {
174                     Log.w(TAG, "Unable to load component info " + activityInfo.toString(), e);
175                 }
176             }
177         }
178 
179         return false;
180     }
181 
182     /**
183      * Can the app be the default for the USB accessory. I.e. can the app be launched by default if
184      * the accessory is plugged in.
185      *
186      * @param accessory The accessory the app would be default for
187      * @param packageInfo The package info of the app
188      *
189      * @return {@code true} iff the app can be default
190      */
canBeDefault(@onNull UsbAccessory accessory, @NonNull PackageInfo packageInfo)191     private boolean canBeDefault(@NonNull UsbAccessory accessory,
192             @NonNull PackageInfo packageInfo) {
193         ActivityInfo[] activities = packageInfo.activities;
194         if (activities != null) {
195             int numActivities = activities.length;
196             for (int i = 0; i < numActivities; i++) {
197                 ActivityInfo activityInfo = activities[i];
198 
199                 try (XmlResourceParser parser = activityInfo.loadXmlMetaData(getPackageManager(),
200                         UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) {
201                     if (parser == null) {
202                         continue;
203                     }
204 
205                     XmlUtils.nextElement(parser);
206                     while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
207                         if ("usb-accessory".equals(parser.getName())) {
208                             AccessoryFilter filter = AccessoryFilter.read(parser);
209                             if (filter.matches(accessory)) {
210                                 return true;
211                             }
212                         }
213 
214                         XmlUtils.nextElement(parser);
215                     }
216                 } catch (Exception e) {
217                     Log.w(TAG, "Unable to load component info " + activityInfo.toString(), e);
218                 }
219             }
220         }
221 
222         return false;
223     }
224 
225     @Override
onDestroy()226     public void onDestroy() {
227         IBinder b = ServiceManager.getService(USB_SERVICE);
228         IUsbManager service = IUsbManager.Stub.asInterface(b);
229 
230         // send response via pending intent
231         Intent intent = new Intent();
232         try {
233             if (mDevice != null) {
234                 intent.putExtra(UsbManager.EXTRA_DEVICE, mDevice);
235                 if (mPermissionGranted) {
236                     service.grantDevicePermission(mDevice, mUid);
237                     if (mAlwaysUse != null && mAlwaysUse.isChecked()) {
238                         final int userId = UserHandle.getUserId(mUid);
239                         service.setDevicePackage(mDevice, mPackageName, userId);
240                     }
241                 }
242             }
243             if (mAccessory != null) {
244                 intent.putExtra(UsbManager.EXTRA_ACCESSORY, mAccessory);
245                 if (mPermissionGranted) {
246                     service.grantAccessoryPermission(mAccessory, mUid);
247                     if (mAlwaysUse != null && mAlwaysUse.isChecked()) {
248                         final int userId = UserHandle.getUserId(mUid);
249                         service.setAccessoryPackage(mAccessory, mPackageName, userId);
250                     }
251                 }
252             }
253             intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, mPermissionGranted);
254             mPendingIntent.send(this, 0, intent);
255         } catch (PendingIntent.CanceledException e) {
256             Log.w(TAG, "PendingIntent was cancelled");
257         } catch (RemoteException e) {
258             Log.e(TAG, "IUsbService connection failed", e);
259         }
260 
261         if (mDisconnectedReceiver != null) {
262             unregisterReceiver(mDisconnectedReceiver);
263         }
264         super.onDestroy();
265     }
266 
onClick(DialogInterface dialog, int which)267     public void onClick(DialogInterface dialog, int which) {
268         if (which == AlertDialog.BUTTON_POSITIVE) {
269             mPermissionGranted = true;
270         }
271         finish();
272     }
273 
onCheckedChanged(CompoundButton buttonView, boolean isChecked)274     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
275         if (mClearDefaultHint == null) return;
276 
277         if(isChecked) {
278             mClearDefaultHint.setVisibility(View.VISIBLE);
279         } else {
280             mClearDefaultHint.setVisibility(View.GONE);
281         }
282     }
283 }
284