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 = false;
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 mUsbDeviceResolverHandler;
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         mUsbDeviceResolverHandler.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(UsbDevice failedDevice)113         void onFailure(UsbDevice failedDevice);
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                     mUsbDeviceResolverHandler.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(device);
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         mUsbDeviceResolverHandler.requestCompleteDeviceDispatch();
172         return true;
173     }
174 
createHandlerThread()175     private void createHandlerThread() {
176         mHandlerThread = new HandlerThread(TAG);
177         mHandlerThread.start();
178         mUsbDeviceResolverHandler = 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             byte[] digest = MessageDigest.getInstance("MD5").digest(serial.getBytes());
247             StringBuilder sb = new StringBuilder(digest.length * 2);
248             for (byte b : digest) {
249                 sb.append(String.format("%02x", b));
250             }
251             return sb.toString();
252         } catch (NoSuchAlgorithmException e) {
253             Log.w(TAG, "could not create MD5 for serial number: " + serial);
254             return Integer.toString(serial.hashCode());
255         }
256     }
257 
deviceProbingComplete(UsbDevice device, List<UsbDeviceSettings> settings)258     private void deviceProbingComplete(UsbDevice device, List<UsbDeviceSettings> settings) {
259         if (LOCAL_LOGD) {
260             Log.d(TAG, "deviceProbingComplete");
261         }
262         mDeviceCallback.onHandlersResolveCompleted(device, settings);
263     }
264 
getDeviceMatches( UsbDevice device, Intent intent, boolean forAoap)265     private List<UsbHandlerPackage> getDeviceMatches(
266             UsbDevice device, Intent intent, boolean forAoap) {
267         List<UsbHandlerPackage> matches = new ArrayList<>();
268         List<ResolveInfo> resolveInfos =
269                 mPackageManager.queryIntentActivities(intent, PackageManager.GET_META_DATA);
270         for (ResolveInfo resolveInfo : resolveInfos) {
271             final String packageName = resolveInfo.activityInfo.packageName;
272             if (forAoap && !hasAoapPermission(packageName)) {
273                 Log.w(TAG, "Package " + packageName + " does not hold "
274                         + AOAP_HANDLE_PERMISSION + " permission. Ignore the package.");
275                 continue;
276             }
277 
278             UsbDeviceFilter filter = packageMatches(resolveInfo.activityInfo,
279                     intent.getAction(), device, forAoap);
280             if (filter != null) {
281                 ActivityInfo ai = resolveInfo.activityInfo;
282                 ComponentName activity = new ComponentName(ai.packageName, ai.name);
283                 ComponentName aoapService = filter.mAoapService == null
284                         ? null : ComponentName.unflattenFromString(filter.mAoapService);
285 
286                 if (aoapService != null && !checkServiceRequiresPermission(aoapService)) {
287                     continue;
288                 }
289 
290                 if (aoapService != null || !forAoap) {
291                     matches.add(new UsbHandlerPackage(activity, aoapService));
292                 }
293             }
294         }
295         return matches;
296     }
297 
checkServiceRequiresPermission(ComponentName serviceName)298     private boolean checkServiceRequiresPermission(ComponentName serviceName) {
299         Intent intent = new Intent();
300         intent.setComponent(serviceName);
301         boolean found = false;
302         for (ResolveInfo info : mPackageManager.queryIntentServices(intent, 0)) {
303             if (info.serviceInfo != null) {
304                 found = true;
305                 if ((Manifest.permission.MANAGE_USB.equals(info.serviceInfo.permission))) {
306                     return true;
307                 }
308             }
309         }
310         if (found) {
311             Log.w(TAG, "Component " + serviceName + " must be protected with "
312                     + Manifest.permission.MANAGE_USB + " permission");
313         } else {
314             Log.w(TAG, "Component " + serviceName + " not found");
315         }
316         return false;
317     }
318 
hasAoapPermission(String packageName)319     private boolean hasAoapPermission(String packageName) {
320         return mPackageManager
321                 .checkPermission(AOAP_HANDLE_PERMISSION, packageName) == PERMISSION_GRANTED;
322     }
323 
packageMatches(ActivityInfo ai, String metaDataName, UsbDevice device, boolean forAoap)324     private UsbDeviceFilter packageMatches(ActivityInfo ai, String metaDataName, UsbDevice device,
325             boolean forAoap) {
326         if (LOCAL_LOGD) {
327             Log.d(TAG, "packageMatches ai: " + ai + "metaDataName: " + metaDataName + " forAoap: "
328                     + forAoap);
329         }
330         String filterTagName = forAoap ? "usb-aoap-accessory" : "usb-device";
331         try (XmlResourceParser parser = ai.loadXmlMetaData(mPackageManager, metaDataName)) {
332             if (parser == null) {
333                 Log.w(TAG, "no meta-data for " + ai);
334                 return null;
335             }
336 
337             XmlUtils.nextElement(parser);
338             while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
339                 String tagName = parser.getName();
340                 if (device != null && filterTagName.equals(tagName)) {
341                     UsbDeviceFilter filter = UsbDeviceFilter.read(parser, forAoap);
342                     if (forAoap || filter.matches(device)) {
343                         return filter;
344                     }
345                 }
346                 XmlUtils.nextElement(parser);
347             }
348         } catch (Exception e) {
349             Log.w(TAG, "Unable to load component info " + ai.toString(), e);
350         }
351         return null;
352     }
353 
354     private class UsbDeviceResolverHandler extends Handler {
355         private static final int MSG_RESOLVE_HANDLERS = 0;
356         private static final int MSG_COMPLETE_DISPATCH = 3;
357 
UsbDeviceResolverHandler(Looper looper)358         private UsbDeviceResolverHandler(Looper looper) {
359             super(looper);
360         }
361 
requestResolveHandlers(UsbDevice device)362         void requestResolveHandlers(UsbDevice device) {
363             Message msg = obtainMessage(MSG_RESOLVE_HANDLERS, device);
364             sendMessage(msg);
365         }
366 
requestCompleteDeviceDispatch()367         void requestCompleteDeviceDispatch() {
368             sendEmptyMessage(MSG_COMPLETE_DISPATCH);
369         }
370 
371         @Override
handleMessage(Message msg)372         public void handleMessage(Message msg) {
373             switch (msg.what) {
374                 case MSG_RESOLVE_HANDLERS:
375                     doHandleResolveHandlers((UsbDevice) msg.obj);
376                     break;
377                 case MSG_COMPLETE_DISPATCH:
378                     mDeviceCallback.onDeviceDispatched();
379                     break;
380                 default:
381                     Log.w(TAG, "Unsupported message: " + msg);
382             }
383         }
384     }
385 
386     private static class UsbHandlerPackage {
387         final ComponentName mActivity;
388         final @Nullable ComponentName mAoapService;
389 
UsbHandlerPackage(ComponentName activity, @Nullable ComponentName aoapService)390         UsbHandlerPackage(ComponentName activity, @Nullable ComponentName aoapService) {
391             mActivity = activity;
392             mAoapService = aoapService;
393         }
394     }
395 }
396