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         boolean aoapSupported = AoapInterface.isSupported(mContext, device, connection);
233         connection.close();
234 
235         return aoapSupported;
236     }
237 
238     @Override
onHandlersResolveCompleted( UsbDevice device, List<UsbDeviceSettings> handlers)239     public void onHandlersResolveCompleted(
240             UsbDevice device, List<UsbDeviceSettings> handlers) {
241         if (LOCAL_LOGD) {
242             Log.d(TAG, "onHandlersResolveComplete: " + device);
243         }
244         if (deviceMatchedActiveDevice(device)) {
245             if (handlers.isEmpty()) {
246                 onDeviceDispatched();
247             } else if (handlers.size() == 1) {
248                 applyDeviceSettings(handlers.get(0));
249             } else {
250                 if (isDeviceAoapPossible(device)) {
251                     // Device supports AOAP mode, if we have just single AOAP handler then use it
252                     // instead of showing disambiguation dialog to the user.
253                     UsbDeviceSettings aoapHandler = getSingleAoapDeviceHandlerOrNull(handlers);
254                     if (aoapHandler != null) {
255                         applyDeviceSettings(aoapHandler);
256                         return;
257                     }
258                 }
259                 mCallback.optionsUpdated(handlers);
260             }
261         } else {
262             Log.w(TAG, "Handlers ignored as they came for inactive device");
263         }
264     }
265 
getSingleAoapDeviceHandlerOrNull(List<UsbDeviceSettings> handlers)266     private UsbDeviceSettings getSingleAoapDeviceHandlerOrNull(List<UsbDeviceSettings> handlers) {
267         UsbDeviceSettings aoapHandler = null;
268         for (UsbDeviceSettings handler : handlers) {
269             if (handler.isAaop()) {
270                 if (aoapHandler != null) { // Found multiple AOAP handlers.
271                     return null;
272                 }
273                 aoapHandler = handler;
274             }
275         }
276         return aoapHandler;
277     }
278 
279     @Override
onDeviceDispatched()280     public void onDeviceDispatched() {
281         stopDeviceProcessing();
282         mCallback.shutdown();
283     }
284 
doHandleDeviceRemoved()285     void doHandleDeviceRemoved() {
286         if (getActiveDevice() == null) {
287             if (LOCAL_LOGD) {
288                 Log.d(TAG, "USB device detached");
289             }
290             stopDeviceProcessing();
291             mCallback.shutdown();
292         }
293     }
294 
295     private class UsbHostControllerHandlerDispatchData {
296         private final UsbDevice mUsbDevice;
297         private final UsbDeviceSettings mUsbDeviceSettings;
298 
299         public int mRetries = 0;
300         public boolean mCanResolve = true;
301 
UsbHostControllerHandlerDispatchData( UsbDevice usbDevice, UsbDeviceSettings usbDeviceSettings, int retries, boolean canResolve)302         public UsbHostControllerHandlerDispatchData(
303                 UsbDevice usbDevice, UsbDeviceSettings usbDeviceSettings,
304                 int retries, boolean canResolve) {
305             mUsbDevice = usbDevice;
306             mUsbDeviceSettings = usbDeviceSettings;
307             mRetries = retries;
308             mCanResolve = canResolve;
309         }
310 
getUsbDevice()311         public UsbDevice getUsbDevice() {
312             return mUsbDevice;
313         }
314 
getUsbDeviceSettings()315         public UsbDeviceSettings getUsbDeviceSettings() {
316             return mUsbDeviceSettings;
317         }
318     }
319 
320     private class UsbHostControllerHandler extends Handler {
321         private static final int MSG_DEVICE_REMOVED = 1;
322         private static final int MSG_DEVICE_DISPATCH = 2;
323 
324         private static final int DEVICE_REMOVE_TIMEOUT_MS = 500;
325 
326         // Used to get the device that we are trying to connect to, if mActiveDevice is removed and
327         // startAoap fails afterwards. Used during USB enumeration when retrying to startAoap when
328         // there are multiple devices attached.
329         private int mLastDeviceId = 0;
330         private int mStartAoapRetries = 1;
331 
UsbHostControllerHandler(Looper looper)332         private UsbHostControllerHandler(Looper looper) {
333             super(looper);
334         }
335 
requestDeviceRemoved()336         private void requestDeviceRemoved() {
337             sendEmptyMessageDelayed(MSG_DEVICE_REMOVED, DEVICE_REMOVE_TIMEOUT_MS);
338         }
339 
onFailure(UsbDevice failedDevice)340         private void onFailure(UsbDevice failedDevice) {
341             if (mStartAoapRetries == 0) {
342                 Log.w(TAG, "Reached maximum retry count for startAoap. Giving up Aoa handshake.");
343                 return;
344             }
345             mStartAoapRetries--;
346 
347             UsbDeviceConnection connection = UsbUtil.openConnection(mUsbManager, failedDevice);
348             if (connection != null) {
349                 Log.d(TAG, "Resetting USB device.");
350                 connection.resetDevice();
351             }
352 
353             Log.d(TAG, "Restarting USB enumeration.");
354             for (UsbDevice device : mUsbManager.getDeviceList().values()) {
355                 if (mLastDeviceId == device.getDeviceId()) {
356                     processDevice(device);
357                     return;
358                 }
359             }
360         }
361 
362         @Override
handleMessage(Message msg)363         public void handleMessage(Message msg) {
364             switch (msg.what) {
365                 case MSG_DEVICE_REMOVED:
366                     doHandleDeviceRemoved();
367                     break;
368                 case MSG_DEVICE_DISPATCH:
369                     UsbHostControllerHandlerDispatchData data =
370                             (UsbHostControllerHandlerDispatchData) msg.obj;
371                     UsbDevice device = data.getUsbDevice();
372                     mLastDeviceId = device.getDeviceId();
373                     UsbDeviceSettings settings = data.getUsbDeviceSettings();
374                     if (!mUsbResolver.dispatch(device, settings.getHandler(), settings.isAaop(),
375                             this::onFailure)) {
376                         if (data.mRetries > 0) {
377                             --data.mRetries;
378                             Message nextMessage = Message.obtain(msg);
379                             mHandler.sendMessageDelayed(nextMessage, DISPATCH_RETRY_DELAY_MS);
380                         } else if (data.mCanResolve) {
381                             resolveDevice(device);
382                         }
383                     } else if (LOCAL_LOGV) {
384                         Log.v(TAG, "Usb Device: " + data.getUsbDevice() + " was sent to component: "
385                                 + settings.getHandler());
386                     }
387                     break;
388                 default:
389                     Log.w(TAG, "Unhandled message: " + msg);
390                     super.handleMessage(msg);
391             }
392         }
393     }
394 
395 }
396