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