Home | History | Annotate | Download | only in usb
      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