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