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 */
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;
28import com.android.internal.annotations.GuardedBy;
29import java.util.ArrayList;
30import java.util.List;
31
32/**
33 * Controller used to handle USB device connections.
34 * TODO: Support handling multiple new USB devices at the same time.
35 */
36public final class UsbHostController
37        implements UsbDeviceHandlerResolver.UsbDeviceHandlerResolverCallback {
38
39    /**
40     * Callbacks for controller
41     */
42    public interface UsbHostControllerCallbacks {
43        /** Host controller ready for shutdown */
44        void shutdown();
45        /** Change of processing state */
46        void processingStateChanged(boolean processing);
47        /** Title of processing changed */
48        void titleChanged(String title);
49        /** Options for USB device changed */
50        void optionsUpdated(List<UsbDeviceSettings> options);
51    }
52
53    private static final String TAG = UsbHostController.class.getSimpleName();
54    private static final boolean LOCAL_LOGD = true;
55    private static final boolean LOCAL_LOGV = true;
56
57
58    private final List<UsbDeviceSettings> mEmptyList = new ArrayList<>();
59    private final Context mContext;
60    private final UsbHostControllerCallbacks mCallback;
61    private final UsbSettingsStorage mUsbSettingsStorage;
62    private final UsbManager mUsbManager;
63    private final UsbDeviceHandlerResolver mUsbResolver;
64    private final UsbHostControllerHandler mHandler;
65
66    private final BroadcastReceiver mUsbBroadcastReceiver = new BroadcastReceiver() {
67        @Override
68        public void onReceive(Context context, Intent intent) {
69            if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(intent.getAction())) {
70                UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE);
71                unsetActiveDeviceIfMatch(device);
72            } else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) {
73                UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE);
74                setActiveDeviceIfMatch(device);
75            }
76        }
77    };
78
79    @GuardedBy("this")
80    private UsbDevice mActiveDevice;
81
82    public UsbHostController(Context context, UsbHostControllerCallbacks callbacks) {
83        mContext = context;
84        mCallback = callbacks;
85        mHandler = new UsbHostControllerHandler(Looper.myLooper());
86        mUsbSettingsStorage = new UsbSettingsStorage(context);
87        mUsbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
88        mUsbResolver = new UsbDeviceHandlerResolver(mUsbManager, mContext, this);
89        IntentFilter filter = new IntentFilter();
90        filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
91        filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
92        context.registerReceiver(mUsbBroadcastReceiver, filter);
93
94    }
95
96    private synchronized void setActiveDeviceIfMatch(UsbDevice device) {
97        if (mActiveDevice != null && device != null
98                && UsbUtil.isDevicesMatching(device, mActiveDevice)) {
99            mActiveDevice = device;
100        }
101    }
102
103    private synchronized void unsetActiveDeviceIfMatch(UsbDevice device) {
104        mHandler.requestDeviceRemoved();
105        if (mActiveDevice != null && device != null
106                && UsbUtil.isDevicesMatching(device, mActiveDevice)) {
107            mActiveDevice = null;
108        }
109    }
110
111    private synchronized boolean startDeviceProcessingIfNull(UsbDevice device) {
112        if (mActiveDevice == null) {
113            mActiveDevice = device;
114            return true;
115        }
116        return false;
117    }
118
119    private synchronized void stopDeviceProcessing() {
120        mActiveDevice = null;
121    }
122
123    private synchronized UsbDevice getActiveDevice() {
124        return mActiveDevice;
125    }
126
127    private boolean deviceMatchedActiveDevice(UsbDevice device) {
128        UsbDevice activeDevice = getActiveDevice();
129        return activeDevice != null && UsbUtil.isDevicesMatching(activeDevice, device);
130    }
131
132    private String generateTitle() {
133        String manufacturer = mActiveDevice.getManufacturerName();
134        String product = mActiveDevice.getProductName();
135        if (manufacturer == null && product == null) {
136            return mContext.getString(R.string.usb_unknown_device);
137        }
138        if (manufacturer != null && product != null) {
139            return manufacturer + " " + product;
140        }
141        if (manufacturer != null) {
142            return manufacturer;
143        }
144        return product;
145    }
146
147    /**
148     * Processes device new device.
149     * <p>
150     * It will load existing settings or resolve supported handlers.
151     */
152    public void processDevice(UsbDevice device) {
153        if (!startDeviceProcessingIfNull(device)) {
154            Log.w(TAG, "Currently, other device is being processed");
155        }
156        mCallback.optionsUpdated(mEmptyList);
157        mCallback.processingStateChanged(true);
158
159        UsbDeviceSettings settings = mUsbSettingsStorage.getSettings(device);
160        if (settings != null && mUsbResolver.dispatch(
161                    mActiveDevice, settings.getHandler(), settings.getAoap())) {
162            if (LOCAL_LOGV) {
163                Log.v(TAG, "Usb Device: " + device + " was sent to component: "
164                        + settings.getHandler());
165            }
166            return;
167        }
168        mCallback.titleChanged(generateTitle());
169        mUsbResolver.resolve(device);
170    }
171
172    /**
173     * Applies device settings.
174     */
175    public void applyDeviceSettings(UsbDeviceSettings settings) {
176        mUsbSettingsStorage.saveSettings(settings);
177        mUsbResolver.dispatch(getActiveDevice(), settings.getHandler(), settings.getAoap());
178    }
179
180    /**
181     * Release object.
182     */
183    public void release() {
184        mContext.unregisterReceiver(mUsbBroadcastReceiver);
185        mUsbResolver.release();
186    }
187
188    @Override
189    public void onHandlersResolveCompleted(
190            UsbDevice device, List<UsbDeviceSettings> handlers) {
191        if (LOCAL_LOGD) {
192            Log.d(TAG, "onHandlersResolveComplete: " + device);
193        }
194        if (deviceMatchedActiveDevice(device)) {
195            mCallback.processingStateChanged(false);
196            if (handlers.isEmpty()) {
197                onDeviceDispatched();
198            } else if (handlers.size() == 1) {
199                applyDeviceSettings(handlers.get(0));
200            } else {
201                mCallback.optionsUpdated(handlers);
202            }
203        } else {
204            Log.w(TAG, "Handlers ignored as they came for inactive device");
205        }
206    }
207
208    @Override
209    public void onDeviceDispatched() {
210        stopDeviceProcessing();
211        mCallback.shutdown();
212    }
213
214    void doHandleDeviceRemoved() {
215        if (getActiveDevice() == null) {
216            if (LOCAL_LOGD) {
217                Log.d(TAG, "USB device detached");
218            }
219            stopDeviceProcessing();
220            mCallback.shutdown();
221        }
222    }
223
224    private class UsbHostControllerHandler extends Handler {
225        private static final int MSG_DEVICE_REMOVED = 1;
226
227        private static final int DEVICE_REMOVE_TIMEOUT_MS = 500;
228
229        private UsbHostControllerHandler(Looper looper) {
230            super(looper);
231        }
232
233        private void requestDeviceRemoved() {
234            sendEmptyMessageDelayed(MSG_DEVICE_REMOVED, DEVICE_REMOVE_TIMEOUT_MS);
235        }
236
237        @Override
238        public void handleMessage(Message msg) {
239            switch (msg.what) {
240                case MSG_DEVICE_REMOVED:
241                    doHandleDeviceRemoved();
242                    break;
243                default:
244                    Log.w(TAG, "Unhandled message: " + msg);
245                    super.handleMessage(msg);
246            }
247        }
248    }
249
250}
251