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