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 { 38 void onDeviceResetComplete(UsbDevice device); 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 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 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 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 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 102 public void startDeviceReset(UsbDevice device) { 103 mHandler.requestDeviceReset(device); 104 } 105 106 public void startAoap(AoapSwitchRequest request) { 107 mHandler.requestAoap(request); 108 } 109 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 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 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 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 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 246 public UsbDeviceConnection openConnection(UsbDevice device) { 247 mUsbManager.grantPermission(device); 248 return mUsbManager.openDevice(device); 249 } 250 251 private void handleUsbDeviceAttached(UsbDevice device) { 252 synchronized (mUsbConnectionChangeWait) { 253 mDevicesAdded.add(device); 254 mUsbConnectionChangeWait.notifyAll(); 255 } 256 } 257 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 269 private UsbStateHandler(Looper looper) { 270 super(looper); 271 } 272 273 private void requestDeviceReset(UsbDevice device) { 274 Message msg = obtainMessage(MSG_RESET_DEVICE, device); 275 sendMessage(msg); 276 } 277 278 private void requestAoap(AoapSwitchRequest request) { 279 Message msg = obtainMessage(MSG_AOAP, request); 280 sendMessage(msg); 281 } 282 283 @Override 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 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 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