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 com.google.android.car.kitchensink.setting.usb; 17 18 import android.content.BroadcastReceiver; 19 import android.content.Context; 20 import android.content.Intent; 21 import android.content.IntentFilter; 22 import android.hardware.usb.UsbDevice; 23 import android.hardware.usb.UsbDeviceConnection; 24 import android.hardware.usb.UsbManager; 25 import android.os.Handler; 26 import android.os.HandlerThread; 27 import android.os.Looper; 28 import android.os.Message; 29 import android.util.Log; 30 31 import dalvik.system.CloseGuard; 32 33 import java.io.IOException; 34 import java.util.LinkedList; 35 36 /** 37 * Controller to change device into AOAP mode and back. 38 */ 39 class UsbDeviceStateController { 40 /** 41 * Listener for USB device mode controller. 42 */ 43 public interface UsbDeviceStateListener { 44 void onDeviceResetComplete(UsbDevice device); 45 void onAoapStartComplete(UsbDevice device); 46 void onAoapStartFailed(UsbDevice device); 47 } 48 49 private static final String TAG = UsbDeviceStateController.class.getSimpleName(); 50 private static final boolean LOCAL_LOGD = true; 51 52 // Because of the bug in UsbDeviceManager total time for AOAP reset should be >10s. 53 // 21*500 = 10.5 s. 54 private static final int MAX_USB_DETACH_CHANGE_WAIT = 21; 55 private static final int MAX_USB_ATTACH_CHANGE_WAIT = 21; 56 private static final long USB_STATE_DETACH_WAIT_TIMEOUT_MS = 500; 57 private static final long USB_STATE_ATTACH_WAIT_TIMEOUT_MS = 500; 58 59 private final Context mContext; 60 private final UsbDeviceStateListener mListener; 61 private final UsbManager mUsbManager; 62 private final HandlerThread mHandlerThread; 63 private final UsbStateHandler mHandler; 64 private final UsbDeviceBroadcastReceiver mUsbStateBroadcastReceiver; 65 private final CloseGuard mCloseGuard = CloseGuard.get(); 66 67 private final Object mUsbConnectionChangeWait = new Object(); 68 private final LinkedList<UsbDevice> mDevicesRemoved = new LinkedList<>(); 69 private final LinkedList<UsbDevice> mDevicesAdded = new LinkedList<>(); 70 private boolean mShouldQuit = false; 71 72 UsbDeviceStateController(Context context, UsbDeviceStateListener listener, 73 UsbManager usbManager) { 74 mContext = context; 75 mListener = listener; 76 mUsbManager = usbManager; 77 mHandlerThread = new HandlerThread(TAG); 78 mHandlerThread.start(); 79 mCloseGuard.open("release"); 80 mHandler = new UsbStateHandler(mHandlerThread.getLooper()); 81 mUsbStateBroadcastReceiver = new UsbDeviceBroadcastReceiver(); 82 } 83 84 public void init() { 85 IntentFilter filter = new IntentFilter(); 86 filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); 87 filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); 88 mContext.registerReceiver(mUsbStateBroadcastReceiver, filter); 89 } 90 91 public void release() { 92 mCloseGuard.close(); 93 mContext.unregisterReceiver(mUsbStateBroadcastReceiver); 94 synchronized (mUsbConnectionChangeWait) { 95 mShouldQuit = true; 96 mUsbConnectionChangeWait.notifyAll(); 97 } 98 mHandlerThread.quit(); 99 } 100 101 @Override 102 protected void finalize() throws Throwable { 103 try { 104 mCloseGuard.warnIfOpen(); 105 boolean release = false; 106 synchronized (mUsbConnectionChangeWait) { 107 release = !mShouldQuit; 108 } 109 if (release) { 110 release(); 111 } 112 } finally { 113 super.finalize(); 114 } 115 } 116 117 public void startDeviceReset(UsbDevice device) { 118 if (LOCAL_LOGD) { 119 Log.d(TAG, "startDeviceReset: " + device); 120 } 121 mHandler.requestDeviceReset(device); 122 } 123 124 public void startAoap(AoapSwitchRequest request) { 125 if (LOCAL_LOGD) { 126 Log.d(TAG, "startAoap: " + request.device); 127 } 128 mHandler.requestAoap(request); 129 } 130 131 private void doHandleDeviceReset(UsbDevice device) { 132 if (LOCAL_LOGD) { 133 Log.d(TAG, "doHandleDeviceReset: " + device); 134 } 135 synchronized (mUsbConnectionChangeWait) { 136 mDevicesRemoved.clear(); 137 mDevicesAdded.clear(); 138 } 139 boolean isInAoap = AoapInterface.isDeviceInAoapMode(device); 140 UsbDevice completedDevice = null; 141 if (isInAoap) { 142 completedDevice = resetUsbDeviceAndConfirmModeChange(device); 143 } else { 144 UsbDeviceConnection conn = openConnection(device); 145 if (conn == null) { 146 throw new RuntimeException("cannot open conneciton for device: " + device); 147 } else { 148 try { 149 if (!conn.resetDevice()) { 150 throw new RuntimeException("resetDevice failed for device: " + device); 151 } else { 152 completedDevice = device; 153 } 154 } finally { 155 conn.close(); 156 } 157 } 158 } 159 mListener.onDeviceResetComplete(completedDevice); 160 } 161 162 private void doHandleAoapStart(AoapSwitchRequest request) { 163 if (LOCAL_LOGD) { 164 Log.d(TAG, "doHandleAoapStart: " + request.device); 165 } 166 UsbDevice device = request.device; 167 boolean isInAoap = AoapInterface.isDeviceInAoapMode(device); 168 if (isInAoap) { 169 device = resetUsbDeviceAndConfirmModeChange(device); 170 if (device == null) { 171 mListener.onAoapStartComplete(null); 172 return; 173 } 174 } 175 synchronized (mUsbConnectionChangeWait) { 176 mDevicesRemoved.clear(); 177 mDevicesAdded.clear(); 178 } 179 UsbDeviceConnection connection = openConnection(device); 180 try { 181 AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_MANUFACTURER, 182 request.manufacturer); 183 AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_MODEL, 184 request.model); 185 AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_DESCRIPTION, 186 request.description); 187 AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_VERSION, 188 request.version); 189 AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_URI, request.uri); 190 AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_SERIAL, 191 request.serial); 192 AoapInterface.sendAoapStart(connection); 193 device = resetUsbDeviceAndConfirmModeChange(device); 194 } catch (IOException e) { 195 Log.w(TAG, "Failed to switch device into AOSP mode", e); 196 } 197 if (device == null) { 198 mListener.onAoapStartComplete(null); 199 connection.close(); 200 return; 201 } 202 if (AoapInterface.isDeviceInAoapMode(device)) { 203 mListener.onAoapStartComplete(device); 204 } else { 205 Log.w(TAG, "Device not in AOAP mode after switching: " + device); 206 mListener.onAoapStartFailed(device); 207 } 208 connection.close(); 209 } 210 211 private UsbDevice resetUsbDeviceAndConfirmModeChange(UsbDevice device) { 212 if (LOCAL_LOGD) { 213 Log.d(TAG, "resetUsbDeviceAndConfirmModeChange: " + device); 214 } 215 int retry = 0; 216 boolean removalDetected = false; 217 while (retry < MAX_USB_DETACH_CHANGE_WAIT) { 218 UsbDeviceConnection connNow = openConnection(device); 219 if (connNow == null) { 220 removalDetected = true; 221 break; 222 } 223 connNow.resetDevice(); 224 connNow.close(); 225 synchronized (mUsbConnectionChangeWait) { 226 try { 227 mUsbConnectionChangeWait.wait(USB_STATE_DETACH_WAIT_TIMEOUT_MS); 228 } catch (InterruptedException e) { 229 break; 230 } 231 if (mShouldQuit) { 232 return null; 233 } 234 if (isDeviceRemovedLocked(device)) { 235 removalDetected = true; 236 break; 237 } 238 } 239 retry++; 240 connNow = null; 241 } 242 if (!removalDetected) { 243 Log.w(TAG, "resetDevice failed for device, device still in the same mode: " + device); 244 return null; 245 } 246 retry = 0; 247 UsbDevice newlyAttached = null; 248 while (retry < MAX_USB_ATTACH_CHANGE_WAIT) { 249 synchronized (mUsbConnectionChangeWait) { 250 try { 251 mUsbConnectionChangeWait.wait(USB_STATE_ATTACH_WAIT_TIMEOUT_MS); 252 } catch (InterruptedException e) { 253 break; 254 } 255 if (mShouldQuit) { 256 return null; 257 } 258 newlyAttached = checkDeviceAttachedLocked(device); 259 } 260 if (newlyAttached != null) { 261 break; 262 } 263 retry++; 264 } 265 if (newlyAttached == null) { 266 Log.w(TAG, "resetDevice failed for device, device disconnected: " + device); 267 return null; 268 } 269 return newlyAttached; 270 } 271 272 private boolean isDeviceRemovedLocked(UsbDevice device) { 273 for (UsbDevice removed : mDevicesRemoved) { 274 if (UsbUtil.isDevicesMatching(device, removed)) { 275 mDevicesRemoved.clear(); 276 return true; 277 } 278 } 279 mDevicesRemoved.clear(); 280 return false; 281 } 282 283 private UsbDevice checkDeviceAttachedLocked(UsbDevice device) { 284 for (UsbDevice attached : mDevicesAdded) { 285 if (UsbUtil.isTheSameDevice(device, attached)) { 286 mDevicesAdded.clear(); 287 return attached; 288 } 289 } 290 mDevicesAdded.clear(); 291 return null; 292 } 293 294 public UsbDeviceConnection openConnection(UsbDevice device) { 295 mUsbManager.grantPermission(device); 296 return mUsbManager.openDevice(device); 297 } 298 299 private void handleUsbDeviceAttached(UsbDevice device) { 300 synchronized (mUsbConnectionChangeWait) { 301 mDevicesAdded.add(device); 302 mUsbConnectionChangeWait.notifyAll(); 303 } 304 } 305 306 private void handleUsbDeviceDetached(UsbDevice device) { 307 synchronized (mUsbConnectionChangeWait) { 308 mDevicesRemoved.add(device); 309 mUsbConnectionChangeWait.notifyAll(); 310 } 311 } 312 313 private class UsbStateHandler extends Handler { 314 private static final int MSG_RESET_DEVICE = 1; 315 private static final int MSG_AOAP = 2; 316 317 private UsbStateHandler(Looper looper) { 318 super(looper); 319 } 320 321 private void requestDeviceReset(UsbDevice device) { 322 Message msg = obtainMessage(MSG_RESET_DEVICE, device); 323 sendMessage(msg); 324 } 325 326 private void requestAoap(AoapSwitchRequest request) { 327 Message msg = obtainMessage(MSG_AOAP, request); 328 sendMessage(msg); 329 } 330 331 @Override 332 public void handleMessage(Message msg) { 333 switch (msg.what) { 334 case MSG_RESET_DEVICE: 335 doHandleDeviceReset((UsbDevice) msg.obj); 336 break; 337 case MSG_AOAP: 338 doHandleAoapStart((AoapSwitchRequest) msg.obj); 339 break; 340 } 341 } 342 } 343 344 private class UsbDeviceBroadcastReceiver extends BroadcastReceiver { 345 @Override 346 public void onReceive(Context context, Intent intent) { 347 if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(intent.getAction())) { 348 UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE); 349 handleUsbDeviceDetached(device); 350 } else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) { 351 UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE); 352 handleUsbDeviceAttached(device); 353 } 354 } 355 } 356 357 public static class AoapSwitchRequest { 358 public final UsbDevice device; 359 public final String manufacturer; 360 public final String model; 361 public final String description; 362 public final String version; 363 public final String uri; 364 public final String serial; 365 366 AoapSwitchRequest(UsbDevice device, String manufacturer, String model, 367 String description, String version, String uri, String serial) { 368 this.device = device; 369 this.manufacturer = manufacturer; 370 this.model = model; 371 this.description = description; 372 this.version = version; 373 this.uri = uri; 374 this.serial = serial; 375 } 376 } 377 } 378