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 com.google.android.car.kitchensink.setting.usb;
17
18import android.content.BroadcastReceiver;
19import android.content.Context;
20import android.content.Intent;
21import android.content.IntentFilter;
22import android.content.pm.PackageManager;
23import android.hardware.usb.UsbDevice;
24import android.hardware.usb.UsbManager;
25import android.os.Handler;
26import android.os.Looper;
27import android.os.Message;
28import android.util.Log;
29
30import com.android.internal.annotations.GuardedBy;
31
32import java.util.ArrayList;
33import java.util.List;
34
35/**
36 * Controller used to handle USB device connections.
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
59
60    private final List<UsbDeviceSettings> mEmptyList = new ArrayList<>();
61    private final Context mContext;
62    private final UsbHostControllerCallbacks mCallback;
63    private final UsbSettingsStorage mUsbSettingsStorage;
64    private final UsbManager mUsbManager;
65    private final PackageManager mPackageManager;
66    private final UsbDeviceHandlerResolver mUsbReslover;
67    private final UsbHostControllerHandler mHandler;
68
69    private final BroadcastReceiver mUsbBroadcastReceiver = new  BroadcastReceiver() {
70        @Override
71        public void onReceive(Context context, Intent intent) {
72            if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(intent.getAction())) {
73                UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE);
74                unsetActiveDeviceIfSerialMatch(device);
75            } else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) {
76                UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE);
77                setActiveDeviceIfSerialMatch(device);
78            }
79        }
80    };
81
82    @GuardedBy("this")
83    private UsbDevice mActiveDevice;
84
85    @GuardedBy("this")
86    private String mProcessingDeviceSerial;
87
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        mPackageManager = context.getPackageManager();
95        mUsbReslover = new UsbDeviceHandlerResolver(mUsbManager, mContext, this);
96        IntentFilter filter = new IntentFilter();
97        filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
98        filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
99        context.registerReceiver(mUsbBroadcastReceiver, filter);
100
101    }
102
103    private synchronized void setActiveDeviceIfSerialMatch(UsbDevice device) {
104        if (device != null && device.getSerialNumber() != null
105                && device.getSerialNumber().equals(mProcessingDeviceSerial)) {
106            mActiveDevice = device;
107        }
108    }
109
110    private synchronized void unsetActiveDeviceIfSerialMatch(UsbDevice device) {
111        mHandler.requestDeviceRemoved();
112        if (mActiveDevice != null && mActiveDevice.getSerialNumber() != null
113                && mActiveDevice.getSerialNumber().equals(device.getSerialNumber())) {
114            mActiveDevice = null;
115        }
116    }
117
118    private synchronized boolean startDeviceProcessingIfNull(UsbDevice device) {
119        if (mActiveDevice == null) {
120            mActiveDevice = device;
121            mProcessingDeviceSerial = device.getSerialNumber();
122            return true;
123        }
124        return false;
125    }
126
127    private synchronized void stopDeviceProcessing() {
128        mActiveDevice = null;
129        mProcessingDeviceSerial = null;
130    }
131
132    private synchronized UsbDevice getActiveDevice() {
133        return mActiveDevice;
134    }
135
136    private boolean deviceMatchedActiveDevice(UsbDevice device) {
137        UsbDevice activeDevice = getActiveDevice();
138        return activeDevice != null && activeDevice.getSerialNumber() != null
139                && activeDevice.getSerialNumber().equals(device.getSerialNumber());
140    }
141
142    /**
143     * Processes device new device.
144     * <p>
145     * It will load existing settings or resolve supported handlers.
146     */
147    public void processDevice(UsbDevice device) {
148        if (!startDeviceProcessingIfNull(device)) {
149            Log.w(TAG, "Currently, other device is being processed");
150        }
151        mCallback.optionsUpdated(mEmptyList);
152        mCallback.processingStateChanged(true);
153
154        UsbDeviceSettings settings = mUsbSettingsStorage.getSettings(
155                device.getSerialNumber(), device.getVendorId(), device.getProductId());
156        if (settings != null && mUsbReslover.dispatch(
157                    mActiveDevice, settings.getHandler(), settings.getAoap())) {
158            if (LOCAL_LOGV) {
159                Log.v(TAG, "Usb Device: " + device + " was sent to component: "
160                        + settings.getHandler());
161            }
162            return;
163        }
164        mCallback.titleChanged(device.getManufacturerName() + " " + device.getProductName());
165        mUsbReslover.resolve(device);
166    }
167
168    /**
169     * Applies device settings.
170     */
171    public void applyDeviceSettings(UsbDeviceSettings settings) {
172        mUsbSettingsStorage.saveSettings(settings);
173        mUsbReslover.dispatch(getActiveDevice(), settings.getHandler(), settings.getAoap());
174    }
175
176    /**
177     * Release object.
178     */
179    public void release() {
180        mContext.unregisterReceiver(mUsbBroadcastReceiver);
181        mUsbReslover.release();
182    }
183
184    @Override
185    public void onHandlersResolveCompleted(
186            UsbDevice device, List<UsbDeviceSettings> handlers) {
187        if (LOCAL_LOGD) {
188            Log.d(TAG, "onHandlersResolveComplete: " + device);
189        }
190        if (deviceMatchedActiveDevice(device)) {
191            mCallback.processingStateChanged(false);
192            if (handlers.isEmpty()) {
193                onDeviceDispatched();
194            } else {
195                mCallback.optionsUpdated(handlers);
196            }
197        } else {
198            Log.w(TAG, "Handlers ignored as they came for inactive device");
199        }
200    }
201
202    @Override
203    public void onDeviceDispatched() {
204        stopDeviceProcessing();
205        mCallback.shutdown();
206    }
207
208    void doHandleDeviceRemoved() {
209        if (getActiveDevice() == null) {
210            if (LOCAL_LOGD) {
211                Log.d(TAG, "USB device detached");
212            }
213            stopDeviceProcessing();
214            mCallback.shutdown();
215        }
216    }
217
218    private static Intent createDeviceAttachedIntent(UsbDevice device) {
219        Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
220        intent.putExtra(UsbManager.EXTRA_DEVICE, device);
221        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
222        return intent;
223    }
224
225    private class UsbHostControllerHandler extends Handler {
226        private static final int MSG_DEVICE_REMOVED = 1;
227
228        private static final int DEVICE_REMOVE_TIMEOUT_MS = 500;
229
230        private UsbHostControllerHandler(Looper looper) {
231            super(looper);
232        }
233
234        private void requestDeviceRemoved() {
235            sendEmptyMessageDelayed(MSG_DEVICE_REMOVED, DEVICE_REMOVE_TIMEOUT_MS);
236        }
237
238        @Override
239        public void handleMessage(Message msg) {
240            switch (msg.what) {
241                case MSG_DEVICE_REMOVED:
242                    doHandleDeviceRemoved();
243                    break;
244                default:
245                    Log.w(TAG, "Unhandled message: " + msg);
246                    super.handleMessage(msg);
247            }
248        }
249    }
250
251
252}
253