• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.car.usb.handler;
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.Looper;
27 import android.os.Message;
28 import android.os.Parcel;
29 import android.util.Log;
30 
31 import com.android.internal.annotations.GuardedBy;
32 
33 import java.util.ArrayList;
34 import java.util.List;
35 
36 /**
37  * Controller used to handle USB device connections.
38  * TODO: Support handling multiple new USB devices at the same time.
39  */
40 public final class UsbHostController
41         implements UsbDeviceHandlerResolver.UsbDeviceHandlerResolverCallback {
42 
43     /**
44      * Callbacks for controller
45      */
46     public interface UsbHostControllerCallbacks {
47         /** Host controller ready for shutdown */
shutdown()48         void shutdown();
49 
50         /** Change of processing state */
processingStarted()51         void processingStarted();
52 
53         /** Title of processing changed */
titleChanged(String title)54         void titleChanged(String title);
55 
56         /** Options for USB device changed */
optionsUpdated(List<UsbDeviceSettings> options)57         void optionsUpdated(List<UsbDeviceSettings> options);
58     }
59 
60     private static final String TAG = UsbHostController.class.getSimpleName();
61     private static final boolean LOCAL_LOGD = true;
62     private static final boolean LOCAL_LOGV = true;
63 
64     private static final int DISPATCH_RETRY_DELAY_MS = 1000;
65     private static final int DISPATCH_RETRY_ATTEMPTS = 5;
66 
67     private final List<UsbDeviceSettings> mEmptyList = new ArrayList<>();
68     private final Context mContext;
69     private final UsbHostControllerCallbacks mCallback;
70     private final UsbSettingsStorage mUsbSettingsStorage;
71     private final UsbManager mUsbManager;
72     private final UsbDeviceHandlerResolver mUsbResolver;
73     private final UsbHostControllerHandler mHandler;
74 
75     private final BroadcastReceiver mUsbBroadcastReceiver = new BroadcastReceiver() {
76         @Override
77         public void onReceive(Context context, Intent intent) {
78             if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(intent.getAction())) {
79                 UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE);
80                 unsetActiveDeviceIfMatch(device);
81             } else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) {
82                 UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE);
83                 setActiveDeviceIfMatch(device);
84             }
85         }
86     };
87 
88     private final Object mLock = new Object();
89 
90     @GuardedBy("mLock")
91     private UsbDevice mActiveDevice;
92 
UsbHostController(Context context, UsbHostControllerCallbacks callbacks)93     public UsbHostController(Context context, UsbHostControllerCallbacks callbacks) {
94         mContext = context;
95         mCallback = callbacks;
96         mHandler = new UsbHostControllerHandler(Looper.myLooper());
97         mUsbSettingsStorage = new UsbSettingsStorage(context);
98         mUsbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
99         mUsbResolver = new UsbDeviceHandlerResolver(mUsbManager, mContext, this);
100         IntentFilter filter = new IntentFilter();
101         filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
102         filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
103         context.registerReceiver(mUsbBroadcastReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
104     }
105 
setActiveDeviceIfMatch(UsbDevice device)106     private void setActiveDeviceIfMatch(UsbDevice device) {
107         synchronized (mLock) {
108             if (mActiveDevice != null && device != null
109                     && UsbUtil.isDevicesMatching(device, mActiveDevice)) {
110                 mActiveDevice = device;
111             }
112         }
113     }
114 
unsetActiveDeviceIfMatch(UsbDevice device)115     private void unsetActiveDeviceIfMatch(UsbDevice device) {
116         mHandler.requestDeviceRemoved();
117         synchronized (mLock) {
118             if (mActiveDevice != null && device != null
119                     && UsbUtil.isDevicesMatching(device, mActiveDevice)) {
120                 mActiveDevice = null;
121             }
122         }
123     }
124 
startDeviceProcessingIfNull(UsbDevice device)125     private boolean startDeviceProcessingIfNull(UsbDevice device) {
126         synchronized (mLock) {
127             if (mActiveDevice == null) {
128                 mActiveDevice = device;
129                 return true;
130             }
131             return false;
132         }
133     }
134 
stopDeviceProcessing()135     private void stopDeviceProcessing() {
136         synchronized (mLock) {
137             mActiveDevice = null;
138         }
139     }
140 
getActiveDevice()141     private UsbDevice getActiveDevice() {
142         synchronized (mLock) {
143             Parcel parcel = Parcel.obtain();
144             try {
145                 parcel.writeParcelable(mActiveDevice, 0);
146                 parcel.setDataPosition(0);
147                 return parcel.readParcelable(UsbDevice.class.getClassLoader(), UsbDevice.class);
148             } finally {
149                 parcel.recycle();
150             }
151         }
152     }
153 
deviceMatchedActiveDevice(UsbDevice device)154     private boolean deviceMatchedActiveDevice(UsbDevice device) {
155         UsbDevice activeDevice = getActiveDevice();
156         return activeDevice != null && UsbUtil.isDevicesMatching(activeDevice, device);
157     }
158 
generateTitle(Context context, UsbDevice usbDevice)159     private static String generateTitle(Context context, UsbDevice usbDevice) {
160         String manufacturer = usbDevice.getManufacturerName();
161         String product = usbDevice.getProductName();
162         if (manufacturer == null && product == null) {
163             return context.getString(R.string.usb_unknown_device);
164         }
165         if (manufacturer != null && product != null) {
166             return manufacturer + " " + product;
167         }
168         if (manufacturer != null) {
169             return manufacturer;
170         }
171         return product;
172     }
173 
174     /**
175      * Processes device new device.
176      * <p>
177      * It will load existing settings or resolve supported handlers.
178      */
processDevice(UsbDevice device)179     public void processDevice(UsbDevice device) {
180         if (!startDeviceProcessingIfNull(device)) {
181             Log.w(TAG, "Currently, other device is being processed");
182         }
183         mCallback.optionsUpdated(mEmptyList);
184         mCallback.processingStarted();
185 
186         UsbDeviceSettings settings = mUsbSettingsStorage.getSettings(device);
187 
188         if (settings == null) {
189             resolveDevice(device);
190         } else {
191             Object obj =
192                     new UsbHostControllerHandlerDispatchData(
193                             device, settings, DISPATCH_RETRY_ATTEMPTS, true);
194             Message.obtain(mHandler, UsbHostControllerHandler.MSG_DEVICE_DISPATCH, obj)
195                     .sendToTarget();
196         }
197     }
198 
199     /**
200      * Applies device settings.
201      */
applyDeviceSettings(UsbDeviceSettings settings)202     public void applyDeviceSettings(UsbDeviceSettings settings) {
203         mUsbSettingsStorage.saveSettings(settings);
204         Message msg = mHandler.obtainMessage();
205         msg.obj =
206                 new UsbHostControllerHandlerDispatchData(
207                         getActiveDevice(), settings, DISPATCH_RETRY_ATTEMPTS, false);
208         msg.what = UsbHostControllerHandler.MSG_DEVICE_DISPATCH;
209         msg.sendToTarget();
210     }
211 
resolveDevice(UsbDevice device)212     private void resolveDevice(UsbDevice device) {
213         mCallback.titleChanged(generateTitle(mContext, device));
214         mUsbResolver.resolve(device);
215     }
216 
217     /**
218      * Release object.
219      */
release()220     public void release() {
221         mContext.unregisterReceiver(mUsbBroadcastReceiver);
222         mUsbResolver.release();
223     }
224 
isDeviceAoapPossible(UsbDevice device)225     private boolean isDeviceAoapPossible(UsbDevice device) {
226         if (AoapInterface.isDeviceInAoapMode(device)) {
227             return true;
228         }
229 
230         UsbManager usbManager = mContext.getSystemService(UsbManager.class);
231         UsbDeviceConnection connection = UsbUtil.openConnection(usbManager, device);
232         // USB Manager can return null connection if the device is failed to open one.
233         if (connection == null) {
234             return false;
235         }
236         boolean aoapSupported = AoapInterface.isSupported(mContext, device, connection);
237         connection.close();
238 
239         return aoapSupported;
240     }
241 
242     @Override
onHandlersResolveCompleted( UsbDevice device, List<UsbDeviceSettings> handlers)243     public void onHandlersResolveCompleted(
244             UsbDevice device, List<UsbDeviceSettings> handlers) {
245         if (LOCAL_LOGD) {
246             Log.d(TAG, "onHandlersResolveComplete: " + device);
247         }
248         if (deviceMatchedActiveDevice(device)) {
249             if (handlers.isEmpty()) {
250                 onDeviceDispatched();
251             } else if (handlers.size() == 1) {
252                 applyDeviceSettings(handlers.get(0));
253             } else {
254                 if (isDeviceAoapPossible(device)) {
255                     // Device supports AOAP mode, if we have just single AOAP handler then use it
256                     // instead of showing disambiguation dialog to the user.
257                     UsbDeviceSettings aoapHandler = getSingleAoapDeviceHandlerOrNull(handlers);
258                     if (aoapHandler != null) {
259                         applyDeviceSettings(aoapHandler);
260                         return;
261                     }
262                 }
263                 mCallback.optionsUpdated(handlers);
264             }
265         } else {
266             Log.w(TAG, "Handlers ignored as they came for inactive device");
267         }
268     }
269 
getSingleAoapDeviceHandlerOrNull(List<UsbDeviceSettings> handlers)270     private UsbDeviceSettings getSingleAoapDeviceHandlerOrNull(List<UsbDeviceSettings> handlers) {
271         UsbDeviceSettings aoapHandler = null;
272         for (UsbDeviceSettings handler : handlers) {
273             if (handler.isAaop()) {
274                 if (aoapHandler != null) { // Found multiple AOAP handlers.
275                     return null;
276                 }
277                 aoapHandler = handler;
278             }
279         }
280         return aoapHandler;
281     }
282 
283     @Override
onDeviceDispatched()284     public void onDeviceDispatched() {
285         stopDeviceProcessing();
286         mCallback.shutdown();
287     }
288 
doHandleDeviceRemoved()289     void doHandleDeviceRemoved() {
290         if (getActiveDevice() == null) {
291             if (LOCAL_LOGD) {
292                 Log.d(TAG, "USB device detached");
293             }
294             stopDeviceProcessing();
295             mCallback.shutdown();
296         }
297     }
298 
299     private class UsbHostControllerHandlerDispatchData {
300         private final UsbDevice mUsbDevice;
301         private final UsbDeviceSettings mUsbDeviceSettings;
302 
303         public int mRetries = 0;
304         public boolean mCanResolve = true;
305 
UsbHostControllerHandlerDispatchData( UsbDevice usbDevice, UsbDeviceSettings usbDeviceSettings, int retries, boolean canResolve)306         public UsbHostControllerHandlerDispatchData(
307                 UsbDevice usbDevice, UsbDeviceSettings usbDeviceSettings,
308                 int retries, boolean canResolve) {
309             mUsbDevice = usbDevice;
310             mUsbDeviceSettings = usbDeviceSettings;
311             mRetries = retries;
312             mCanResolve = canResolve;
313         }
314 
getUsbDevice()315         public UsbDevice getUsbDevice() {
316             return mUsbDevice;
317         }
318 
getUsbDeviceSettings()319         public UsbDeviceSettings getUsbDeviceSettings() {
320             return mUsbDeviceSettings;
321         }
322     }
323 
324     private class UsbHostControllerHandler extends Handler {
325         private static final int MSG_DEVICE_REMOVED = 1;
326         private static final int MSG_DEVICE_DISPATCH = 2;
327 
328         private static final int DEVICE_REMOVE_TIMEOUT_MS = 500;
329 
330         // Used to get the device that we are trying to connect to, if mActiveDevice is removed and
331         // startAoap fails afterwards. Used during USB enumeration when retrying to startAoap when
332         // there are multiple devices attached.
333         private int mLastDeviceId = 0;
334         private int mStartAoapRetries = 1;
335 
UsbHostControllerHandler(Looper looper)336         private UsbHostControllerHandler(Looper looper) {
337             super(looper);
338         }
339 
requestDeviceRemoved()340         private void requestDeviceRemoved() {
341             sendEmptyMessageDelayed(MSG_DEVICE_REMOVED, DEVICE_REMOVE_TIMEOUT_MS);
342         }
343 
onFailure(UsbDevice failedDevice)344         private void onFailure(UsbDevice failedDevice) {
345             if (mStartAoapRetries == 0) {
346                 Log.w(TAG, "Reached maximum retry count for startAoap. Giving up Aoa handshake.");
347                 return;
348             }
349             mStartAoapRetries--;
350 
351             UsbDeviceConnection connection = UsbUtil.openConnection(mUsbManager, failedDevice);
352             if (connection != null) {
353                 Log.d(TAG, "Resetting USB device.");
354                 connection.resetDevice();
355             }
356 
357             Log.d(TAG, "Restarting USB enumeration.");
358             for (UsbDevice device : mUsbManager.getDeviceList().values()) {
359                 if (mLastDeviceId == device.getDeviceId()) {
360                     processDevice(device);
361                     return;
362                 }
363             }
364         }
365 
366         @Override
handleMessage(Message msg)367         public void handleMessage(Message msg) {
368             switch (msg.what) {
369                 case MSG_DEVICE_REMOVED:
370                     doHandleDeviceRemoved();
371                     break;
372                 case MSG_DEVICE_DISPATCH:
373                     UsbHostControllerHandlerDispatchData data =
374                             (UsbHostControllerHandlerDispatchData) msg.obj;
375                     UsbDevice device = data.getUsbDevice();
376                     mLastDeviceId = device.getDeviceId();
377                     UsbDeviceSettings settings = data.getUsbDeviceSettings();
378                     if (!mUsbResolver.dispatch(device, settings.getHandler(), settings.isAaop(),
379                             this::onFailure)) {
380                         if (data.mRetries > 0) {
381                             --data.mRetries;
382                             Message nextMessage = Message.obtain(msg);
383                             mHandler.sendMessageDelayed(nextMessage, DISPATCH_RETRY_DELAY_MS);
384                         } else if (data.mCanResolve) {
385                             resolveDevice(device);
386                         }
387                     } else if (LOCAL_LOGV) {
388                         Log.v(TAG, "Usb Device: " + data.getUsbDevice() + " was sent to component: "
389                                 + settings.getHandler());
390                     }
391                     break;
392                 default:
393                     Log.w(TAG, "Unhandled message: " + msg);
394                     super.handleMessage(msg);
395             }
396         }
397     }
398 
399 }
400