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 */
16package com.android.hardware.usb.externalmanagementtest;
17
18import android.content.BroadcastReceiver;
19import android.content.Context;
20import android.content.Intent;
21import android.content.IntentFilter;
22import android.hardware.usb.UsbDevice;
23import android.hardware.usb.UsbDeviceConnection;
24import android.hardware.usb.UsbManager;
25import android.os.Handler;
26import android.os.HandlerThread;
27import android.os.Looper;
28import android.os.Message;
29import android.util.Log;
30
31import java.util.LinkedList;
32
33import dalvik.system.CloseGuard;
34
35public 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            mCloseGuard.warnIfOpen();
93            release();
94        } finally {
95            super.finalize();
96        }
97    }
98
99    public void startDeviceReset(UsbDevice device) {
100        mHandler.requestDeviceReset(device);
101    }
102
103    public void startAoap(AoapSwitchRequest request) {
104        mHandler.requestAoap(request);
105    }
106
107    private void doHandleDeviceReset(UsbDevice device) {
108        boolean isInAoap = AoapInterface.isDeviceInAoapMode(device);
109        UsbDevice completedDevice = null;
110        if (isInAoap) {
111            completedDevice = resetUsbDeviceAndConfirmModeChange(device);
112        } else {
113            UsbDeviceConnection conn = openConnection(device);
114            if (conn == null) {
115                throw new RuntimeException("cannot open conneciton for device: " + device);
116            } else {
117                try {
118                    if (!conn.resetDevice()) {
119                        throw new RuntimeException("resetDevice failed for devie: " + device);
120                    } else {
121                        completedDevice = device;
122                    }
123                } finally {
124                    conn.close();
125                }
126            }
127        }
128        mListener.onDeviceResetComplete(completedDevice);
129    }
130
131    private void doHandleAoapStart(AoapSwitchRequest request) {
132        UsbDevice device = request.device;
133        boolean isInAoap = AoapInterface.isDeviceInAoapMode(device);
134        if (isInAoap) {
135            device = resetUsbDeviceAndConfirmModeChange(device);
136            if (device == null) {
137                mListener.onAoapStartComplete(null);
138                return;
139            }
140        }
141        UsbDeviceConnection connection = openConnection(device);
142        AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_MANUFACTURER,
143                request.manufacturer);
144        AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_MODEL,
145                request.model);
146        AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_DESCRIPTION,
147                request.description);
148        AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_VERSION,
149                request.version);
150        AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_URI, request.uri);
151        AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_SERIAL, request.serial);
152        AoapInterface.sendAoapStart(connection);
153        device = resetUsbDeviceAndConfirmModeChange(device);
154        if (device == null) {
155            mListener.onAoapStartComplete(null);
156        }
157        if (!AoapInterface.isDeviceInAoapMode(device)) {
158            Log.w(TAG, "Device not in AOAP mode after switching: " + device);
159            mListener.onAoapStartComplete(device);
160        }
161        mListener.onAoapStartComplete(device);
162    }
163
164    private UsbDevice resetUsbDeviceAndConfirmModeChange(UsbDevice device) {
165        int retry = 0;
166        boolean removalDetected = false;
167        while (retry < MAX_USB_STATE_CHANGE_WAIT) {
168            UsbDeviceConnection connNow = openConnection(device);
169            if (connNow == null) {
170                removalDetected = true;
171                break;
172            }
173            connNow.resetDevice();
174            connNow.close();
175            synchronized (mUsbConnectionChangeWait) {
176                try {
177                    mUsbConnectionChangeWait.wait(USB_STATE_CHANGE_WAIT_TIMEOUT_MS);
178                } catch (InterruptedException e) {
179                    break;
180                }
181                if (mShouldQuit) {
182                    return null;
183                }
184                if (isDeviceRemovedLocked(device)) {
185                    removalDetected = true;
186                    break;
187                }
188            }
189            retry++;
190        }
191        if (!removalDetected) {
192            Log.w(TAG, "resetDevice failed for device, device still in the same mode: " + device);
193            return null;
194        }
195        retry = 0;
196        UsbDevice newlyAttached = null;
197        while (retry < MAX_USB_STATE_CHANGE_WAIT) {
198            synchronized (mUsbConnectionChangeWait) {
199                try {
200                    mUsbConnectionChangeWait.wait(USB_STATE_CHANGE_WAIT_TIMEOUT_MS);
201                } catch (InterruptedException e) {
202                    break;
203                }
204                if (mShouldQuit) {
205                    return null;
206                }
207                newlyAttached = checkDeviceAttachedLocked(device);
208            }
209            if (newlyAttached != null) {
210                break;
211            }
212            retry++;
213        }
214        if (newlyAttached == null) {
215            Log.w(TAG, "resetDevice failed for device, device disconnected: " + device);
216            return null;
217        }
218        return newlyAttached;
219    }
220
221    private boolean isDeviceRemovedLocked(UsbDevice device) {
222        for (UsbDevice removed : mDevicesRemoved) {
223            if (UsbUtil.isDevicesMatching(device, removed)) {
224                mDevicesRemoved.clear();
225                return true;
226            }
227        }
228        mDevicesRemoved.clear();
229        return false;
230    }
231
232    private UsbDevice checkDeviceAttachedLocked(UsbDevice device) {
233        for (UsbDevice attached : mDevicesAdded) {
234            if (UsbUtil.isTheSameDevice(device, attached)) {
235                mDevicesAdded.clear();
236                return attached;
237            }
238        }
239        mDevicesAdded.clear();
240        return null;
241    }
242
243    public UsbDeviceConnection openConnection(UsbDevice device) {
244        mUsbManager.grantPermission(device);
245        return mUsbManager.openDevice(device);
246    }
247
248    private void handleUsbDeviceAttached(UsbDevice device) {
249        synchronized (mUsbConnectionChangeWait) {
250            mDevicesAdded.add(device);
251            mUsbConnectionChangeWait.notifyAll();
252        }
253    }
254
255    private void handleUsbDeviceDetached(UsbDevice device) {
256        synchronized (mUsbConnectionChangeWait) {
257            mDevicesRemoved.add(device);
258            mUsbConnectionChangeWait.notifyAll();
259        }
260    }
261
262    private class UsbStateHandler extends Handler {
263        private final int MSG_RESET_DEVICE = 1;
264        private final int MSG_AOAP = 2;
265
266        private UsbStateHandler(Looper looper) {
267            super(looper);
268        }
269
270        private void requestDeviceReset(UsbDevice device) {
271            Message msg = obtainMessage(MSG_RESET_DEVICE, device);
272            sendMessage(msg);
273        }
274
275        private void requestAoap(AoapSwitchRequest request) {
276            Message msg = obtainMessage(MSG_AOAP, request);
277            sendMessage(msg);
278        }
279
280        @Override
281        public void handleMessage(Message msg) {
282            switch (msg.what) {
283                case MSG_RESET_DEVICE:
284                    doHandleDeviceReset((UsbDevice) msg.obj);
285                    break;
286                case MSG_AOAP:
287                    doHandleAoapStart((AoapSwitchRequest) msg.obj);
288                    break;
289            }
290        }
291    }
292
293    private class UsbDeviceBroadcastReceiver extends BroadcastReceiver {
294        @Override
295        public void onReceive(Context context, Intent intent) {
296            if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(intent.getAction())) {
297                UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE);
298                handleUsbDeviceDetached(device);
299            } else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) {
300                UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE);
301                handleUsbDeviceAttached(device);
302            }
303        }
304    }
305
306    public static class AoapSwitchRequest {
307        public final UsbDevice device;
308        public final String manufacturer;
309        public final String model;
310        public final String description;
311        public final String version;
312        public final String uri;
313        public final String serial;
314
315        public AoapSwitchRequest(UsbDevice device, String manufacturer, String model,
316                String description, String version, String uri, String serial) {
317            this.device = device;
318            this.manufacturer = manufacturer;
319            this.model = model;
320            this.description = description;
321            this.version = version;
322            this.uri = uri;
323            this.serial = serial;
324        }
325    }
326}
327