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