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