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