blob: 2762e6fecea387bd16397d34c41eb40f5b975dc6 [file] [log] [blame]
Kevin Crossand4285492016-11-28 18:40:43 -08001/*
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 */
16package android.car.usb.handler;
17
18import android.content.BroadcastReceiver;
19import android.content.Context;
20import android.content.Intent;
21import android.content.IntentFilter;
22import android.hardware.usb.UsbDevice;
23import android.hardware.usb.UsbManager;
24import android.os.Handler;
25import android.os.Looper;
26import android.os.Message;
27import android.util.Log;
Pavel Maltsev1380d622018-12-04 13:29:05 -080028
Kevin Crossand4285492016-11-28 18:40:43 -080029import com.android.internal.annotations.GuardedBy;
Pavel Maltsev1380d622018-12-04 13:29:05 -080030
Kevin Crossand4285492016-11-28 18:40:43 -080031import java.util.ArrayList;
32import java.util.List;
33
34/**
35 * Controller used to handle USB device connections.
36 * TODO: Support handling multiple new USB devices at the same time.
37 */
38public final class UsbHostController
39 implements UsbDeviceHandlerResolver.UsbDeviceHandlerResolverCallback {
40
41 /**
42 * Callbacks for controller
43 */
44 public interface UsbHostControllerCallbacks {
45 /** Host controller ready for shutdown */
46 void shutdown();
47 /** Change of processing state */
48 void processingStateChanged(boolean processing);
49 /** Title of processing changed */
50 void titleChanged(String title);
51 /** Options for USB device changed */
52 void optionsUpdated(List<UsbDeviceSettings> options);
53 }
54
55 private static final String TAG = UsbHostController.class.getSimpleName();
56 private static final boolean LOCAL_LOGD = true;
57 private static final boolean LOCAL_LOGV = true;
58
tadvana63f42f42019-03-26 17:25:21 -070059 private static final int DISPATCH_RETRY_DELAY_MS = 1000;
60 private static final int DISPATCH_RETRY_ATTEMPTS = 5;
Kevin Crossand4285492016-11-28 18:40:43 -080061
62 private final List<UsbDeviceSettings> mEmptyList = new ArrayList<>();
63 private final Context mContext;
64 private final UsbHostControllerCallbacks mCallback;
65 private final UsbSettingsStorage mUsbSettingsStorage;
66 private final UsbManager mUsbManager;
67 private final UsbDeviceHandlerResolver mUsbResolver;
68 private final UsbHostControllerHandler mHandler;
69
70 private final BroadcastReceiver mUsbBroadcastReceiver = new BroadcastReceiver() {
71 @Override
72 public void onReceive(Context context, Intent intent) {
73 if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(intent.getAction())) {
74 UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE);
Kevin Crossan4f208d82017-03-27 16:06:21 -070075 unsetActiveDeviceIfMatch(device);
Kevin Crossand4285492016-11-28 18:40:43 -080076 } else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) {
77 UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE);
Kevin Crossan4f208d82017-03-27 16:06:21 -070078 setActiveDeviceIfMatch(device);
Kevin Crossand4285492016-11-28 18:40:43 -080079 }
80 }
81 };
82
83 @GuardedBy("this")
84 private UsbDevice mActiveDevice;
85
Kevin Crossand4285492016-11-28 18:40:43 -080086 public UsbHostController(Context context, UsbHostControllerCallbacks callbacks) {
87 mContext = context;
88 mCallback = callbacks;
89 mHandler = new UsbHostControllerHandler(Looper.myLooper());
90 mUsbSettingsStorage = new UsbSettingsStorage(context);
91 mUsbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
92 mUsbResolver = new UsbDeviceHandlerResolver(mUsbManager, mContext, this);
93 IntentFilter filter = new IntentFilter();
94 filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
95 filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
96 context.registerReceiver(mUsbBroadcastReceiver, filter);
Kevin Crossand4285492016-11-28 18:40:43 -080097 }
98
Kevin Crossan4f208d82017-03-27 16:06:21 -070099 private synchronized void setActiveDeviceIfMatch(UsbDevice device) {
100 if (mActiveDevice != null && device != null
101 && UsbUtil.isDevicesMatching(device, mActiveDevice)) {
Kevin Crossand4285492016-11-28 18:40:43 -0800102 mActiveDevice = device;
103 }
104 }
105
Kevin Crossan4f208d82017-03-27 16:06:21 -0700106 private synchronized void unsetActiveDeviceIfMatch(UsbDevice device) {
Kevin Crossand4285492016-11-28 18:40:43 -0800107 mHandler.requestDeviceRemoved();
Kevin Crossan4f208d82017-03-27 16:06:21 -0700108 if (mActiveDevice != null && device != null
109 && UsbUtil.isDevicesMatching(device, mActiveDevice)) {
Kevin Crossand4285492016-11-28 18:40:43 -0800110 mActiveDevice = null;
111 }
112 }
113
114 private synchronized boolean startDeviceProcessingIfNull(UsbDevice device) {
115 if (mActiveDevice == null) {
116 mActiveDevice = device;
Kevin Crossand4285492016-11-28 18:40:43 -0800117 return true;
118 }
119 return false;
120 }
121
122 private synchronized void stopDeviceProcessing() {
123 mActiveDevice = null;
Kevin Crossand4285492016-11-28 18:40:43 -0800124 }
125
126 private synchronized UsbDevice getActiveDevice() {
127 return mActiveDevice;
128 }
129
130 private boolean deviceMatchedActiveDevice(UsbDevice device) {
131 UsbDevice activeDevice = getActiveDevice();
Kevin Crossan4f208d82017-03-27 16:06:21 -0700132 return activeDevice != null && UsbUtil.isDevicesMatching(activeDevice, device);
133 }
134
Pavel Maltsev1380d622018-12-04 13:29:05 -0800135 private static String generateTitle(Context context, UsbDevice usbDevice) {
136 String manufacturer = usbDevice.getManufacturerName();
137 String product = usbDevice.getProductName();
Kevin Crossan4f208d82017-03-27 16:06:21 -0700138 if (manufacturer == null && product == null) {
Pavel Maltsev1380d622018-12-04 13:29:05 -0800139 return context.getString(R.string.usb_unknown_device);
Kevin Crossan4f208d82017-03-27 16:06:21 -0700140 }
141 if (manufacturer != null && product != null) {
142 return manufacturer + " " + product;
143 }
144 if (manufacturer != null) {
145 return manufacturer;
146 }
147 return product;
Kevin Crossand4285492016-11-28 18:40:43 -0800148 }
149
150 /**
151 * Processes device new device.
152 * <p>
153 * It will load existing settings or resolve supported handlers.
154 */
155 public void processDevice(UsbDevice device) {
156 if (!startDeviceProcessingIfNull(device)) {
157 Log.w(TAG, "Currently, other device is being processed");
158 }
159 mCallback.optionsUpdated(mEmptyList);
160 mCallback.processingStateChanged(true);
161
Kevin Crossan4f208d82017-03-27 16:06:21 -0700162 UsbDeviceSettings settings = mUsbSettingsStorage.getSettings(device);
tadvana63f42f42019-03-26 17:25:21 -0700163
164 if (settings == null) {
165 resolveDevice(device);
166 } else {
167 Object obj =
168 new UsbHostControllerHandlerDispatchData(
169 device, settings, DISPATCH_RETRY_ATTEMPTS, true);
170 Message.obtain(mHandler, UsbHostControllerHandler.MSG_DEVICE_DISPATCH, obj)
171 .sendToTarget();
Kevin Crossand4285492016-11-28 18:40:43 -0800172 }
Kevin Crossand4285492016-11-28 18:40:43 -0800173 }
174
175 /**
176 * Applies device settings.
177 */
178 public void applyDeviceSettings(UsbDeviceSettings settings) {
179 mUsbSettingsStorage.saveSettings(settings);
tadvana63f42f42019-03-26 17:25:21 -0700180 Message msg = mHandler.obtainMessage();
181 msg.obj =
182 new UsbHostControllerHandlerDispatchData(
183 getActiveDevice(), settings, DISPATCH_RETRY_ATTEMPTS, false);
184 msg.what = UsbHostControllerHandler.MSG_DEVICE_DISPATCH;
185 msg.sendToTarget();
186 }
187
188 private void resolveDevice(UsbDevice device) {
189 mCallback.titleChanged(generateTitle(mContext, device));
190 mUsbResolver.resolve(device);
Kevin Crossand4285492016-11-28 18:40:43 -0800191 }
192
193 /**
194 * Release object.
195 */
196 public void release() {
197 mContext.unregisterReceiver(mUsbBroadcastReceiver);
198 mUsbResolver.release();
199 }
200
201 @Override
202 public void onHandlersResolveCompleted(
203 UsbDevice device, List<UsbDeviceSettings> handlers) {
204 if (LOCAL_LOGD) {
205 Log.d(TAG, "onHandlersResolveComplete: " + device);
206 }
207 if (deviceMatchedActiveDevice(device)) {
208 mCallback.processingStateChanged(false);
209 if (handlers.isEmpty()) {
210 onDeviceDispatched();
211 } else if (handlers.size() == 1) {
212 applyDeviceSettings(handlers.get(0));
213 } else {
Pavel Maltsev03bc7752019-03-07 15:55:57 -0800214 if (AoapInterface.isDeviceInAoapMode(device)) {
215 // Device is in AOAP mode, if we have just single AOAP handler then use it
216 // instead of showing disambiguation dialog to the user.
217 UsbDeviceSettings aoapHandler = getSingleAoapDeviceHandlerOrNull(handlers);
218 if (aoapHandler != null) {
219 applyDeviceSettings(aoapHandler);
220 return;
221 }
222 }
Kevin Crossand4285492016-11-28 18:40:43 -0800223 mCallback.optionsUpdated(handlers);
224 }
225 } else {
226 Log.w(TAG, "Handlers ignored as they came for inactive device");
227 }
228 }
229
Pavel Maltsev03bc7752019-03-07 15:55:57 -0800230 private UsbDeviceSettings getSingleAoapDeviceHandlerOrNull(List<UsbDeviceSettings> handlers) {
231 UsbDeviceSettings aoapHandler = null;
232 for (UsbDeviceSettings handler : handlers) {
233 if (handler.getAoap()) {
234 if (aoapHandler != null) { // Found multiple AOAP handlers.
235 return null;
236 }
237 aoapHandler = handler;
238 }
239 }
240 return aoapHandler;
241 }
242
Kevin Crossand4285492016-11-28 18:40:43 -0800243 @Override
244 public void onDeviceDispatched() {
245 stopDeviceProcessing();
246 mCallback.shutdown();
247 }
248
249 void doHandleDeviceRemoved() {
250 if (getActiveDevice() == null) {
251 if (LOCAL_LOGD) {
252 Log.d(TAG, "USB device detached");
253 }
254 stopDeviceProcessing();
255 mCallback.shutdown();
256 }
257 }
258
tadvana63f42f42019-03-26 17:25:21 -0700259 private class UsbHostControllerHandlerDispatchData {
260 private final UsbDevice mUsbDevice;
261 private final UsbDeviceSettings mUsbDeviceSettings;
262
263 public int mRetries = 0;
264 public boolean mCanResolve = true;
265
266 public UsbHostControllerHandlerDispatchData(
267 UsbDevice usbDevice, UsbDeviceSettings usbDeviceSettings,
268 int retries, boolean canResolve) {
269 mUsbDevice = usbDevice;
270 mUsbDeviceSettings = usbDeviceSettings;
271 mRetries = retries;
272 mCanResolve = canResolve;
273 }
274
275 public UsbDevice getUsbDevice() {
276 return mUsbDevice;
277 }
278
279 public UsbDeviceSettings getUsbDeviceSettings() {
280 return mUsbDeviceSettings;
281 }
282 }
283
Kevin Crossand4285492016-11-28 18:40:43 -0800284 private class UsbHostControllerHandler extends Handler {
285 private static final int MSG_DEVICE_REMOVED = 1;
tadvana63f42f42019-03-26 17:25:21 -0700286 private static final int MSG_DEVICE_DISPATCH = 2;
Kevin Crossand4285492016-11-28 18:40:43 -0800287
288 private static final int DEVICE_REMOVE_TIMEOUT_MS = 500;
289
290 private UsbHostControllerHandler(Looper looper) {
291 super(looper);
292 }
293
294 private void requestDeviceRemoved() {
295 sendEmptyMessageDelayed(MSG_DEVICE_REMOVED, DEVICE_REMOVE_TIMEOUT_MS);
296 }
297
298 @Override
299 public void handleMessage(Message msg) {
300 switch (msg.what) {
301 case MSG_DEVICE_REMOVED:
302 doHandleDeviceRemoved();
303 break;
tadvana63f42f42019-03-26 17:25:21 -0700304 case MSG_DEVICE_DISPATCH:
305 UsbHostControllerHandlerDispatchData data =
306 (UsbHostControllerHandlerDispatchData) msg.obj;
307 UsbDevice device = data.getUsbDevice();
308 UsbDeviceSettings settings = data.getUsbDeviceSettings();
309 if (!mUsbResolver.dispatch(device, settings.getHandler(), settings.getAoap())) {
310 if (data.mRetries > 0) {
311 --data.mRetries;
312 Message nextMessage = Message.obtain(msg);
313 mHandler.sendMessageDelayed(nextMessage, DISPATCH_RETRY_DELAY_MS);
314 } else if (data.mCanResolve) {
315 resolveDevice(device);
316 }
317 } else if (LOCAL_LOGV) {
318 Log.v(TAG, "Usb Device: " + data.getUsbDevice() + " was sent to component: "
319 + settings.getHandler());
320 }
321 break;
Kevin Crossand4285492016-11-28 18:40:43 -0800322 default:
323 Log.w(TAG, "Unhandled message: " + msg);
324 super.handleMessage(msg);
325 }
326 }
327 }
328
329}