1 /*
2  * Copyright (C) 2016 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 package android.car.usb.handler;
17 
18 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
19 
20 import android.Manifest;
21 import android.annotation.Nullable;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.pm.ActivityInfo;
26 import android.content.pm.PackageManager;
27 import android.content.pm.PackageManager.NameNotFoundException;
28 import android.content.pm.ResolveInfo;
29 import android.content.res.XmlResourceParser;
30 import android.hardware.usb.UsbDevice;
31 import android.hardware.usb.UsbDeviceConnection;
32 import android.hardware.usb.UsbManager;
33 import android.os.Handler;
34 import android.os.HandlerThread;
35 import android.os.Looper;
36 import android.os.Message;
37 import android.util.Log;
38 
39 import com.android.internal.util.XmlUtils;
40 
41 import org.xmlpull.v1.XmlPullParser;
42 
43 import java.io.IOException;
44 import java.util.ArrayList;
45 import java.util.List;
46 
47 /** Resolves supported handlers for USB device. */
48 public final class UsbDeviceHandlerResolver {
49 
50     private static final String TAG = UsbDeviceHandlerResolver.class.getSimpleName();
51     private static final boolean LOCAL_LOGD = true;
52 
53     private static final String AOAP_HANDLE_PERMISSION =
54             "android.car.permission.CAR_HANDLE_USB_AOAP_DEVICE";
55 
56     /**
57      * Callbacks for device resolver.
58      */
59     public interface UsbDeviceHandlerResolverCallback {
60         /** Handlers are resolved */
onHandlersResolveCompleted( UsbDevice device, List<UsbDeviceSettings> availableSettings)61         void onHandlersResolveCompleted(
62                 UsbDevice device, List<UsbDeviceSettings> availableSettings);
63         /** Device was dispatched */
onDeviceDispatched()64         void onDeviceDispatched();
65     }
66 
67     private final UsbManager mUsbManager;
68     private final PackageManager mPackageManager;
69     private final UsbDeviceHandlerResolverCallback mDeviceCallback;
70     private final Context mContext;
71     private final HandlerThread mHandlerThread;
72     private final UsbDeviceResolverHandler mHandler;
73     private final AoapServiceManager mAoapServiceManager;
74 
UsbDeviceHandlerResolver(UsbManager manager, Context context, UsbDeviceHandlerResolverCallback deviceListener)75     public UsbDeviceHandlerResolver(UsbManager manager, Context context,
76             UsbDeviceHandlerResolverCallback deviceListener) {
77         mUsbManager = manager;
78         mContext = context;
79         mDeviceCallback = deviceListener;
80         mHandlerThread = new HandlerThread(TAG);
81         mHandlerThread.start();
82         mHandler = new UsbDeviceResolverHandler(mHandlerThread.getLooper());
83         mPackageManager = context.getPackageManager();
84         mAoapServiceManager = new AoapServiceManager(mContext.getApplicationContext());
85     }
86 
87     /**
88      * Releases current object.
89      */
release()90     public void release() {
91         if (mHandlerThread != null) {
92             mHandlerThread.quitSafely();
93         }
94     }
95 
96     /**
97      * Resolves handlers for USB device.
98      */
resolve(UsbDevice device)99     public void resolve(UsbDevice device) {
100         mHandler.requestResolveHandlers(device);
101     }
102 
103     /**
104      * Dispatches device to component.
105      */
dispatch(UsbDevice device, ComponentName component, boolean inAoap)106     public boolean dispatch(UsbDevice device, ComponentName component, boolean inAoap) {
107         if (LOCAL_LOGD) {
108             Log.d(TAG, "dispatch: " + device + " component: " + component + " inAoap: " + inAoap);
109         }
110 
111         ActivityInfo activityInfo;
112         try {
113             activityInfo = mPackageManager.getActivityInfo(component, PackageManager.GET_META_DATA);
114         } catch (NameNotFoundException e) {
115             Log.e(TAG, "Activity not found: " + component);
116             return false;
117         }
118 
119         Intent intent = createDeviceAttachedIntent(device);
120         if (inAoap) {
121             if (AoapInterface.isDeviceInAoapMode(device)) {
122                 mDeviceCallback.onDeviceDispatched();
123             } else {
124                 UsbDeviceFilter filter =
125                         packageMatches(activityInfo, intent.getAction(), device, true);
126 
127                 if (filter != null) {
128                     mHandlerThread.getThreadHandler().post(() -> {
129                         if (mAoapServiceManager.canSwitchDeviceToAoap(device,
130                                 ComponentName.unflattenFromString(filter.mAoapService))) {
131                             requestAoapSwitch(device, filter);
132                         } else {
133                             Log.i(TAG, "Ignore AOAP switch for device " + device
134                                     + " handled by " + filter.mAoapService);
135                         }
136                     });
137                     mDeviceCallback.onDeviceDispatched();
138                     return true;
139                 }
140             }
141         }
142 
143         intent.setComponent(component);
144         mUsbManager.grantPermission(device, activityInfo.applicationInfo.uid);
145 
146         mContext.startActivity(intent);
147         mHandler.requestCompleteDeviceDispatch();
148         return true;
149     }
150 
createDeviceAttachedIntent(UsbDevice device)151     private static Intent createDeviceAttachedIntent(UsbDevice device) {
152         Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
153         intent.putExtra(UsbManager.EXTRA_DEVICE, device);
154         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
155         return intent;
156     }
157 
doHandleResolveHandlers(UsbDevice device)158     private void doHandleResolveHandlers(UsbDevice device) {
159         if (LOCAL_LOGD) {
160             Log.d(TAG, "doHandleResolveHandlers: " + device);
161         }
162 
163         Intent intent = createDeviceAttachedIntent(device);
164         List<UsbHandlerPackage> matches = getDeviceMatches(device, intent, false);
165         if (LOCAL_LOGD) {
166             Log.d(TAG, "matches size: " + matches.size());
167         }
168         List<UsbDeviceSettings> settings = new ArrayList<>();
169         for (UsbHandlerPackage pkg : matches) {
170             settings.add(createSettings(device, pkg));
171         }
172 
173         UsbDeviceConnection devConnection = UsbUtil.openConnection(mUsbManager, device);
174         if (devConnection != null && AoapInterface.isSupported(mContext, device, devConnection)) {
175             for (UsbHandlerPackage pkg : getDeviceMatches(device, intent, true)) {
176                 if (mAoapServiceManager.isDeviceSupported(device, pkg.mAoapService)) {
177                     settings.add(createSettings(device, pkg));
178                 }
179             }
180         }
181 
182         deviceProbingComplete(device, settings);
183     }
184 
createSettings(UsbDevice device, UsbHandlerPackage pkg)185     private UsbDeviceSettings createSettings(UsbDevice device, UsbHandlerPackage pkg) {
186         UsbDeviceSettings settings = UsbDeviceSettings.constructSettings(device);
187         settings.setHandler(pkg.mActivity);
188         settings.setAoap(pkg.mAoapService != null);
189         return settings;
190     }
191 
requestAoapSwitch(UsbDevice device, UsbDeviceFilter filter)192     private void requestAoapSwitch(UsbDevice device, UsbDeviceFilter filter) {
193         UsbDeviceConnection connection = UsbUtil.openConnection(mUsbManager, device);
194         if (connection == null) {
195             Log.e(TAG, "Failed to connect to usb device.");
196             return;
197         }
198         try {
199             UsbUtil.sendAoapAccessoryStart(
200                     connection,
201                     filter.mAoapManufacturer,
202                     filter.mAoapModel,
203                     filter.mAoapDescription,
204                     filter.mAoapVersion,
205                     filter.mAoapUri,
206                     filter.mAoapSerial);
207         } catch (IOException e) {
208             Log.w(TAG, "Failed to switch device into AOAP mode", e);
209         }
210         connection.close();
211     }
212 
deviceProbingComplete(UsbDevice device, List<UsbDeviceSettings> settings)213     private void deviceProbingComplete(UsbDevice device, List<UsbDeviceSettings> settings) {
214         if (LOCAL_LOGD) {
215             Log.d(TAG, "deviceProbingComplete");
216         }
217         mDeviceCallback.onHandlersResolveCompleted(device, settings);
218     }
219 
getDeviceMatches( UsbDevice device, Intent intent, boolean forAoap)220     private List<UsbHandlerPackage> getDeviceMatches(
221             UsbDevice device, Intent intent, boolean forAoap) {
222         List<UsbHandlerPackage> matches = new ArrayList<>();
223         List<ResolveInfo> resolveInfos =
224                 mPackageManager.queryIntentActivities(intent, PackageManager.GET_META_DATA);
225         for (ResolveInfo resolveInfo : resolveInfos) {
226             final String packageName = resolveInfo.activityInfo.packageName;
227             if (forAoap && !hasAoapPermission(packageName)) {
228                 Log.w(TAG, "Package " + packageName + " does not hold "
229                         + AOAP_HANDLE_PERMISSION + " permission. Ignore the package.");
230                 continue;
231             }
232 
233             UsbDeviceFilter filter = packageMatches(resolveInfo.activityInfo,
234                     intent.getAction(), device, forAoap);
235             if (filter != null) {
236                 ActivityInfo ai = resolveInfo.activityInfo;
237                 ComponentName activity = new ComponentName(ai.packageName, ai.name);
238                 ComponentName aoapService = filter.mAoapService == null
239                         ? null : ComponentName.unflattenFromString(filter.mAoapService);
240 
241                 if (aoapService != null && !checkServiceRequiresPermission(aoapService)) {
242                     continue;
243                 }
244 
245                 if (aoapService != null || !forAoap) {
246                     matches.add(new UsbHandlerPackage(activity, aoapService));
247                 }
248             }
249         }
250         return matches;
251     }
252 
checkServiceRequiresPermission(ComponentName serviceName)253     private boolean checkServiceRequiresPermission(ComponentName serviceName) {
254         Intent intent = new Intent();
255         intent.setComponent(serviceName);
256         boolean found = false;
257         for (ResolveInfo info : mPackageManager.queryIntentServices(intent, 0)) {
258             if (info.serviceInfo != null) {
259                 found = true;
260                 if ((Manifest.permission.MANAGE_USB.equals(info.serviceInfo.permission))) {
261                     return true;
262                 }
263             }
264         }
265         if (found) {
266             Log.w(TAG, "Component " + serviceName + " must be protected with "
267                     + Manifest.permission.MANAGE_USB + " permission");
268         } else {
269             Log.w(TAG, "Component " + serviceName + " not found");
270         }
271         return false;
272     }
273 
hasAoapPermission(String packageName)274     private boolean hasAoapPermission(String packageName) {
275         return mPackageManager
276                 .checkPermission(AOAP_HANDLE_PERMISSION, packageName) == PERMISSION_GRANTED;
277     }
278 
packageMatches(ActivityInfo ai, String metaDataName, UsbDevice device, boolean forAoap)279     private UsbDeviceFilter packageMatches(ActivityInfo ai, String metaDataName, UsbDevice device,
280             boolean forAoap) {
281         if (LOCAL_LOGD) {
282             Log.d(TAG, "packageMatches ai: " + ai + "metaDataName: " + metaDataName + " forAoap: "
283                     + forAoap);
284         }
285         String filterTagName = forAoap ? "usb-aoap-accessory" : "usb-device";
286         try (XmlResourceParser parser = ai.loadXmlMetaData(mPackageManager, metaDataName)) {
287             if (parser == null) {
288                 Log.w(TAG, "no meta-data for " + ai);
289                 return null;
290             }
291 
292             XmlUtils.nextElement(parser);
293             while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
294                 String tagName = parser.getName();
295                 if (device != null && filterTagName.equals(tagName)) {
296                     UsbDeviceFilter filter = UsbDeviceFilter.read(parser, forAoap);
297                     if (forAoap || filter.matches(device)) {
298                         return filter;
299                     }
300                 }
301                 XmlUtils.nextElement(parser);
302             }
303         } catch (Exception e) {
304             Log.w(TAG, "Unable to load component info " + ai.toString(), e);
305         }
306         return null;
307     }
308 
309     private class UsbDeviceResolverHandler extends Handler {
310         private static final int MSG_RESOLVE_HANDLERS = 0;
311         private static final int MSG_COMPLETE_DISPATCH = 3;
312 
UsbDeviceResolverHandler(Looper looper)313         private UsbDeviceResolverHandler(Looper looper) {
314             super(looper);
315         }
316 
requestResolveHandlers(UsbDevice device)317         void requestResolveHandlers(UsbDevice device) {
318             Message msg = obtainMessage(MSG_RESOLVE_HANDLERS, device);
319             sendMessage(msg);
320         }
321 
requestCompleteDeviceDispatch()322         void requestCompleteDeviceDispatch() {
323             sendEmptyMessage(MSG_COMPLETE_DISPATCH);
324         }
325 
326         @Override
handleMessage(Message msg)327         public void handleMessage(Message msg) {
328             switch (msg.what) {
329                 case MSG_RESOLVE_HANDLERS:
330                     doHandleResolveHandlers((UsbDevice) msg.obj);
331                     break;
332                 case MSG_COMPLETE_DISPATCH:
333                     mDeviceCallback.onDeviceDispatched();
334                     break;
335                 default:
336                     Log.w(TAG, "Unsupported message: " + msg);
337             }
338         }
339     }
340 
341     private static class UsbHandlerPackage {
342         final ComponentName mActivity;
343         final @Nullable ComponentName mAoapService;
344 
UsbHandlerPackage(ComponentName activity, @Nullable ComponentName aoapService)345         UsbHandlerPackage(ComponentName activity, @Nullable ComponentName aoapService) {
346             mActivity = activity;
347             mAoapService = aoapService;
348         }
349     }
350 }
351