blob: 5c61a98091d00f954c26f4b522c48683163c3e7f [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.car.usb.handler;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import java.util.ArrayList;
import java.util.List;
/**
* Controller used to handle USB device connections.
* TODO: Support handling multiple new USB devices at the same time.
*/
public final class UsbHostController
implements UsbDeviceHandlerResolver.UsbDeviceHandlerResolverCallback {
/**
* Callbacks for controller
*/
public interface UsbHostControllerCallbacks {
/** Host controller ready for shutdown */
void shutdown();
/** Change of processing state */
void processingStateChanged(boolean processing);
/** Title of processing changed */
void titleChanged(String title);
/** Options for USB device changed */
void optionsUpdated(List<UsbDeviceSettings> options);
}
private static final String TAG = UsbHostController.class.getSimpleName();
private static final boolean LOCAL_LOGD = true;
private static final boolean LOCAL_LOGV = true;
private final List<UsbDeviceSettings> mEmptyList = new ArrayList<>();
private final Context mContext;
private final UsbHostControllerCallbacks mCallback;
private final UsbSettingsStorage mUsbSettingsStorage;
private final UsbManager mUsbManager;
private final UsbDeviceHandlerResolver mUsbResolver;
private final UsbHostControllerHandler mHandler;
private final BroadcastReceiver mUsbBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(intent.getAction())) {
UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE);
unsetActiveDeviceIfMatch(device);
} else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) {
UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE);
setActiveDeviceIfMatch(device);
}
}
};
@GuardedBy("this")
private UsbDevice mActiveDevice;
public UsbHostController(Context context, UsbHostControllerCallbacks callbacks) {
mContext = context;
mCallback = callbacks;
mHandler = new UsbHostControllerHandler(Looper.myLooper());
mUsbSettingsStorage = new UsbSettingsStorage(context);
mUsbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
mUsbResolver = new UsbDeviceHandlerResolver(mUsbManager, mContext, this);
IntentFilter filter = new IntentFilter();
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
context.registerReceiver(mUsbBroadcastReceiver, filter);
}
private synchronized void setActiveDeviceIfMatch(UsbDevice device) {
if (mActiveDevice != null && device != null
&& UsbUtil.isDevicesMatching(device, mActiveDevice)) {
mActiveDevice = device;
}
}
private synchronized void unsetActiveDeviceIfMatch(UsbDevice device) {
mHandler.requestDeviceRemoved();
if (mActiveDevice != null && device != null
&& UsbUtil.isDevicesMatching(device, mActiveDevice)) {
mActiveDevice = null;
}
}
private synchronized boolean startDeviceProcessingIfNull(UsbDevice device) {
if (mActiveDevice == null) {
mActiveDevice = device;
return true;
}
return false;
}
private synchronized void stopDeviceProcessing() {
mActiveDevice = null;
}
private synchronized UsbDevice getActiveDevice() {
return mActiveDevice;
}
private boolean deviceMatchedActiveDevice(UsbDevice device) {
UsbDevice activeDevice = getActiveDevice();
return activeDevice != null && UsbUtil.isDevicesMatching(activeDevice, device);
}
private String generateTitle() {
String manufacturer = mActiveDevice.getManufacturerName();
String product = mActiveDevice.getProductName();
if (manufacturer == null && product == null) {
return mContext.getString(R.string.usb_unknown_device);
}
if (manufacturer != null && product != null) {
return manufacturer + " " + product;
}
if (manufacturer != null) {
return manufacturer;
}
return product;
}
/**
* Processes device new device.
* <p>
* It will load existing settings or resolve supported handlers.
*/
public void processDevice(UsbDevice device) {
if (!startDeviceProcessingIfNull(device)) {
Log.w(TAG, "Currently, other device is being processed");
}
mCallback.optionsUpdated(mEmptyList);
mCallback.processingStateChanged(true);
UsbDeviceSettings settings = mUsbSettingsStorage.getSettings(device);
if (settings != null && mUsbResolver.dispatch(
mActiveDevice, settings.getHandler(), settings.getAoap())) {
if (LOCAL_LOGV) {
Log.v(TAG, "Usb Device: " + device + " was sent to component: "
+ settings.getHandler());
}
return;
}
mCallback.titleChanged(generateTitle());
mUsbResolver.resolve(device);
}
/**
* Applies device settings.
*/
public void applyDeviceSettings(UsbDeviceSettings settings) {
mUsbSettingsStorage.saveSettings(settings);
mUsbResolver.dispatch(getActiveDevice(), settings.getHandler(), settings.getAoap());
}
/**
* Release object.
*/
public void release() {
mContext.unregisterReceiver(mUsbBroadcastReceiver);
mUsbResolver.release();
}
@Override
public void onHandlersResolveCompleted(
UsbDevice device, List<UsbDeviceSettings> handlers) {
if (LOCAL_LOGD) {
Log.d(TAG, "onHandlersResolveComplete: " + device);
}
if (deviceMatchedActiveDevice(device)) {
mCallback.processingStateChanged(false);
if (handlers.isEmpty()) {
onDeviceDispatched();
} else if (handlers.size() == 1) {
applyDeviceSettings(handlers.get(0));
} else {
mCallback.optionsUpdated(handlers);
}
} else {
Log.w(TAG, "Handlers ignored as they came for inactive device");
}
}
@Override
public void onDeviceDispatched() {
stopDeviceProcessing();
mCallback.shutdown();
}
void doHandleDeviceRemoved() {
if (getActiveDevice() == null) {
if (LOCAL_LOGD) {
Log.d(TAG, "USB device detached");
}
stopDeviceProcessing();
mCallback.shutdown();
}
}
private class UsbHostControllerHandler extends Handler {
private static final int MSG_DEVICE_REMOVED = 1;
private static final int DEVICE_REMOVE_TIMEOUT_MS = 500;
private UsbHostControllerHandler(Looper looper) {
super(looper);
}
private void requestDeviceRemoved() {
sendEmptyMessageDelayed(MSG_DEVICE_REMOVED, DEVICE_REMOVE_TIMEOUT_MS);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DEVICE_REMOVED:
doHandleDeviceRemoved();
break;
default:
Log.w(TAG, "Unhandled message: " + msg);
super.handleMessage(msg);
}
}
}
}