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