summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKevin Crossan2016-11-28 20:40:43 -0600
committerKevin Crossan2016-11-29 15:59:33 -0600
commitd428549b58b2df5015bff81d79747265ee8be536 (patch)
tree463f90628b0abd1db7225d311cfee9ede3103fc4 /car-usb-handler
parentee412315d30eb1b8a0e8a4ea232f78ecc53b7486 (diff)
downloadplatform-packages-services-car-d428549b58b2df5015bff81d79747265ee8be536.tar.gz
platform-packages-services-car-d428549b58b2df5015bff81d79747265ee8be536.tar.xz
platform-packages-services-car-d428549b58b2df5015bff81d79747265ee8be536.zip
Implement AOAP USB handler with simplified probing.
The existing USB handler, part of the Kitchen Sink application, was unreliable and hard to follow. This new handler simplifies the code flow and only probes new USB devices by querying whether they support AOAP by sending a USB control message. Further tests (such as switching the device into AOAP) are not done by this app. This new handler also attempts to handle any already connected AOAP devices on system boot. This enables projection to start automatically for compatible devices. NOTE: because devices are not switched into AOAP mode during probing, this means that IUsbAoapSupportCheckService.isDeviceSupported() is now called with a UsbDevice that is _not_ in AOAP mode. Fixes: 33185277 Test: Plugged the following MDs in and projection started: N5, N5X, N6P, Pixel, Galaxy Note Edge, Moto X gen2, Xperia Z5, HTC M8 Test: Plugged in a phone before the system booted completely, and projection started Change-Id: Ice200f661bd85e6eebc97c95bcd23910d4dc25e6
Diffstat (limited to 'car-usb-handler')
-rw-r--r--car-usb-handler/Android.mk35
-rw-r--r--car-usb-handler/AndroidManifest.xml33
-rw-r--r--car-usb-handler/proguard.flags2
-rw-r--r--car-usb-handler/res/drawable-hdpi/ic_launcher.pngbin0 -> 9397 bytes
-rw-r--r--car-usb-handler/res/drawable-ldpi/ic_launcher.pngbin0 -> 2729 bytes
-rw-r--r--car-usb-handler/res/drawable-mdpi/ic_launcher.pngbin0 -> 5237 bytes
-rw-r--r--car-usb-handler/res/drawable-xhdpi/ic_launcher.pngbin0 -> 14383 bytes
-rw-r--r--car-usb-handler/res/layout/usb_handler_row.xml30
-rw-r--r--car-usb-handler/res/layout/usb_host.xml40
-rw-r--r--car-usb-handler/res/values/strings.xml29
-rw-r--r--car-usb-handler/res/xml/usb_manager_prefs.xml18
-rw-r--r--car-usb-handler/src/android/car/usb/handler/AoapInterface.java146
-rw-r--r--car-usb-handler/src/android/car/usb/handler/BootUsbScanner.java40
-rw-r--r--car-usb-handler/src/android/car/usb/handler/UsbDeviceHandlerResolver.java670
-rw-r--r--car-usb-handler/src/android/car/usb/handler/UsbDevicePreference.java71
-rw-r--r--car-usb-handler/src/android/car/usb/handler/UsbDeviceSettings.java135
-rw-r--r--car-usb-handler/src/android/car/usb/handler/UsbHostController.java241
-rw-r--r--car-usb-handler/src/android/car/usb/handler/UsbHostManagementActivity.java191
-rw-r--r--car-usb-handler/src/android/car/usb/handler/UsbSettingsStorage.java196
-rw-r--r--car-usb-handler/src/android/car/usb/handler/UsbUtil.java91
20 files changed, 1968 insertions, 0 deletions
diff --git a/car-usb-handler/Android.mk b/car-usb-handler/Android.mk
new file mode 100644
index 00000000..c0508540
--- /dev/null
+++ b/car-usb-handler/Android.mk
@@ -0,0 +1,35 @@
1# Copyright (C) 2016 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14#
15#
16
17LOCAL_PATH:= $(call my-dir)
18
19include $(CLEAR_VARS)
20
21LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
22LOCAL_SRC_FILES := $(call all-java-files-under, src)
23
24LOCAL_PACKAGE_NAME := CarUsbHandler
25
26# Each update should be signed by OEMs
27LOCAL_CERTIFICATE := platform
28LOCAL_PRIVILEGED_MODULE := true
29
30LOCAL_PROGUARD_FLAG_FILES := proguard.flags
31LOCAL_PROGUARD_ENABLED := disabled
32
33LOCAL_JAVA_LIBRARIES += android.car
34
35include $(BUILD_PACKAGE)
diff --git a/car-usb-handler/AndroidManifest.xml b/car-usb-handler/AndroidManifest.xml
new file mode 100644
index 00000000..be64f82d
--- /dev/null
+++ b/car-usb-handler/AndroidManifest.xml
@@ -0,0 +1,33 @@
1<?xml version="1.0" encoding="utf-8"?>
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-->
16<manifest xmlns:android="http://schemas.android.com/apk/res/android"
17 xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
18 package="android.car.usb.handler" >
19 <uses-sdk android:minSdkVersion="25" />
20 <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
21 <uses-permission android:name="android.permission.MANAGE_USB" />
22 <application android:label="@string/app_name" android:icon="@drawable/ic_launcher"
23 android:directBootAware="true" >
24 <activity android:name=".UsbHostManagementActivity"
25 android:theme="@android:style/Theme.Material.Light.Dialog"
26 android:launchMode="singleTop" />
27 <receiver android:name=".BootUsbScanner" >
28 <intent-filter>
29 <action android:name="android.intent.action.BOOT_COMPLETED" />
30 </intent-filter>
31 </receiver>
32 </application>
33</manifest>
diff --git a/car-usb-handler/proguard.flags b/car-usb-handler/proguard.flags
new file mode 100644
index 00000000..565df26b
--- /dev/null
+++ b/car-usb-handler/proguard.flags
@@ -0,0 +1,2 @@
1-verbose
2-keep @com.android.internal.annotations.VisibleForTesting class *
diff --git a/car-usb-handler/res/drawable-hdpi/ic_launcher.png b/car-usb-handler/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 00000000..96a442e5
--- /dev/null
+++ b/car-usb-handler/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/car-usb-handler/res/drawable-ldpi/ic_launcher.png b/car-usb-handler/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 00000000..99238729
--- /dev/null
+++ b/car-usb-handler/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/car-usb-handler/res/drawable-mdpi/ic_launcher.png b/car-usb-handler/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 00000000..359047df
--- /dev/null
+++ b/car-usb-handler/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/car-usb-handler/res/drawable-xhdpi/ic_launcher.png b/car-usb-handler/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..71c6d760
--- /dev/null
+++ b/car-usb-handler/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/car-usb-handler/res/layout/usb_handler_row.xml b/car-usb-handler/res/layout/usb_handler_row.xml
new file mode 100644
index 00000000..cc34e5ec
--- /dev/null
+++ b/car-usb-handler/res/layout/usb_handler_row.xml
@@ -0,0 +1,30 @@
1<?xml version="1.0" encoding="utf-8"?>
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-->
16<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
17 android:layout_width="match_parent"
18 android:layout_height="match_parent"
19 android:orientation="horizontal" >
20 <ImageView
21 android:id="@+id/usb_handler_icon"
22 android:layout_width="60dp"
23 android:layout_height="60dp"
24 android:padding="5dp" />
25 <TextView
26 android:id="@+id/usb_handler_title"
27 android:layout_width="wrap_content"
28 android:layout_height="wrap_content"
29 android:orientation="vertical" />
30</LinearLayout>
diff --git a/car-usb-handler/res/layout/usb_host.xml b/car-usb-handler/res/layout/usb_host.xml
new file mode 100644
index 00000000..fbcc608e
--- /dev/null
+++ b/car-usb-handler/res/layout/usb_host.xml
@@ -0,0 +1,40 @@
1<?xml version="1.0" encoding="utf-8"?>
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-->
16<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
17 android:layout_width="match_parent"
18 android:layout_height="match_parent"
19 android:orientation="vertical" >
20 <LinearLayout
21 android:id="@+id/usb_handlers_progress"
22 android:layout_width="match_parent"
23 android:layout_height="match_parent"
24 android:orientation="horizontal"
25 android:visibility="gone"
26 android:gravity="center">
27 <ProgressBar
28 android:layout_width="wrap_content"
29 android:layout_height="wrap_content"/>
30 <TextView
31 android:text="@+string/usb_resolving_handlers"
32 android:layout_width="wrap_content"
33 android:layout_height="wrap_content"
34 android:orientation="vertical" />
35 </LinearLayout>
36 <ListView
37 android:id="@+id/usb_handlers_list"
38 android:layout_width="match_parent"
39 android:layout_height="wrap_content" />
40</LinearLayout>
diff --git a/car-usb-handler/res/values/strings.xml b/car-usb-handler/res/values/strings.xml
new file mode 100644
index 00000000..22426489
--- /dev/null
+++ b/car-usb-handler/res/values/strings.xml
@@ -0,0 +1,29 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (C) 2015 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-->
16<resources>
17 <string name="app_name">USB Handler</string>
18
19 <!-- USB Manager Settings -->
20 <string name="usb_title">USB Devices settings</string>
21 <string name="usb_description">Customize your USB Devices settings</string>
22 <string name="usb_available_devices">Connected devices</string>
23 <string name="usb_saved_devices">Saved devices</string>
24 <string name="usb_pref_delete_title">Remove handling app for USB device</string>
25 <string name="usb_pref_delete_message">Are you sure you wan to delete dafault handling app for %1$s?</string>
26 <string name="usb_pref_delete_yes">Yes</string>
27 <string name="usb_pref_delete_cancel">Cancel</string>
28 <string name="usb_resolving_handlers">Getting supported handlers</string>
29</resources>
diff --git a/car-usb-handler/res/xml/usb_manager_prefs.xml b/car-usb-handler/res/xml/usb_manager_prefs.xml
new file mode 100644
index 00000000..b0901240
--- /dev/null
+++ b/car-usb-handler/res/xml/usb_manager_prefs.xml
@@ -0,0 +1,18 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!--
3 ~ Copyright (C) 2016 The Android Open Source Project
4 ~
5 ~ Licensed under the Apache License, Version 2.0 (the "License");
6 ~ you may not use this file except in compliance with the License.
7 ~ You may obtain a copy of the License at
8 ~
9 ~ http://www.apache.org/licenses/LICENSE-2.0
10 ~
11 ~ Unless required by applicable law or agreed to in writing, software
12 ~ distributed under the License is distributed on an "AS IS" BASIS,
13 ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 ~ See the License for the specific language governing permissions and
15 ~ limitations under the License.
16 -->
17<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
18</PreferenceScreen>
diff --git a/car-usb-handler/src/android/car/usb/handler/AoapInterface.java b/car-usb-handler/src/android/car/usb/handler/AoapInterface.java
new file mode 100644
index 00000000..e4d843f7
--- /dev/null
+++ b/car-usb-handler/src/android/car/usb/handler/AoapInterface.java
@@ -0,0 +1,146 @@
1/**
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * <p>Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
6 *
7 * <p>http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * <p>Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 * express or implied. See the License for the specific language governing permissions and
12 * limitations under the License.
13 */
14package android.car.usb.handler;
15
16import android.hardware.usb.UsbConstants;
17import android.hardware.usb.UsbDevice;
18import android.hardware.usb.UsbDeviceConnection;
19import android.util.Log;
20import java.io.IOException;
21
22final class AoapInterface {
23 /**
24 * Use Google Vendor ID when in accessory mode
25 */
26 public static final int USB_ACCESSORY_VENDOR_ID = 0x18D1;
27
28 /**
29 * Product ID to use when in accessory mode
30 */
31 public static final int USB_ACCESSORY_PRODUCT_ID = 0x2D00;
32
33 /**
34 * Product ID to use when in accessory mode and adb is enabled
35 */
36 public static final int USB_ACCESSORY_ADB_PRODUCT_ID = 0x2D01;
37
38 /**
39 * Indexes for strings sent by the host via ACCESSORY_SEND_STRING
40 */
41 public static final int ACCESSORY_STRING_MANUFACTURER = 0;
42 public static final int ACCESSORY_STRING_MODEL = 1;
43 public static final int ACCESSORY_STRING_DESCRIPTION = 2;
44 public static final int ACCESSORY_STRING_VERSION = 3;
45 public static final int ACCESSORY_STRING_URI = 4;
46 public static final int ACCESSORY_STRING_SERIAL = 5;
47
48 /**
49 * Control request for retrieving device's protocol version
50 *
51 * requestType: USB_DIR_IN | USB_TYPE_VENDOR
52 * request: ACCESSORY_GET_PROTOCOL
53 * value: 0
54 * index: 0
55 * data version number (16 bits little endian)
56 * 1 for original accessory support
57 * 2 adds HID and device to host audio support
58 */
59 public static final int ACCESSORY_GET_PROTOCOL = 51;
60
61 /**
62 * Control request for host to send a string to the device
63 *
64 * requestType: USB_DIR_OUT | USB_TYPE_VENDOR
65 * request: ACCESSORY_SEND_STRING
66 * value: 0
67 * index: string ID
68 * data zero terminated UTF8 string
69 *
70 * The device can later retrieve these strings via the
71 * ACCESSORY_GET_STRING_* ioctls
72 */
73 public static final int ACCESSORY_SEND_STRING = 52;
74
75 /**
76 * Control request for starting device in accessory mode.
77 * The host sends this after setting all its strings to the device.
78 *
79 * requestType: USB_DIR_OUT | USB_TYPE_VENDOR
80 * request: ACCESSORY_START
81 * value: 0
82 * index: 0
83 * data none
84 */
85 public static final int ACCESSORY_START = 53;
86
87 /**
88 * Max payload size for AOAP. Limited by driver.
89 */
90 public static final int MAX_PAYLOAD_SIZE = 16384;
91
92 /**
93 * Accessory write timeout.
94 */
95 public static final int AOAP_TIMEOUT_MS = 2000;
96
97 private static final String TAG = AoapInterface.class.getSimpleName();
98
99 public static int getProtocol(UsbDeviceConnection conn) {
100 byte[] buffer = new byte[2];
101 int len = conn.controlTransfer(
102 UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_VENDOR,
103 ACCESSORY_GET_PROTOCOL, 0, 0, buffer, 2, AOAP_TIMEOUT_MS);
104 if (len != 2) {
105 return -1;
106 }
107 return (buffer[1] << 8) | buffer[0];
108 }
109
110 public static boolean isSupported(UsbDeviceConnection conn) {
111 return getProtocol(conn) >= 1;
112 }
113
114 public static void sendString(UsbDeviceConnection conn, int index, String string)
115 throws IOException {
116 byte[] buffer = (string + "\0").getBytes();
117 int len = conn.controlTransfer(
118 UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR,
119 ACCESSORY_SEND_STRING, 0, index,
120 buffer, buffer.length, AOAP_TIMEOUT_MS);
121 if (len != buffer.length) {
122 throw new IOException("Failed to send string " + index + ": \"" + string + "\"");
123 } else {
124 Log.i(TAG, "Sent string " + index + ": \"" + string + "\"");
125 }
126 }
127
128 public static void sendAoapStart(UsbDeviceConnection conn) throws IOException {
129 int len = conn.controlTransfer(
130 UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR,
131 ACCESSORY_START, 0, 0, null, 0, AOAP_TIMEOUT_MS);
132 if (len < 0) {
133 throw new IOException("Control transfer for accessory start failed: " + len);
134 }
135 }
136
137 public static boolean isDeviceInAoapMode(UsbDevice device) {
138 if (device == null) {
139 return false;
140 }
141 final int vid = device.getVendorId();
142 final int pid = device.getProductId();
143 return vid == USB_ACCESSORY_VENDOR_ID
144 && (pid == USB_ACCESSORY_PRODUCT_ID || pid == USB_ACCESSORY_ADB_PRODUCT_ID);
145 }
146}
diff --git a/car-usb-handler/src/android/car/usb/handler/BootUsbScanner.java b/car-usb-handler/src/android/car/usb/handler/BootUsbScanner.java
new file mode 100644
index 00000000..5255f165
--- /dev/null
+++ b/car-usb-handler/src/android/car/usb/handler/BootUsbScanner.java
@@ -0,0 +1,40 @@
1package android.car.usb.handler;
2
3import android.content.BroadcastReceiver;
4import android.content.Context;
5import android.content.Intent;
6import android.hardware.usb.UsbDevice;
7import android.hardware.usb.UsbDeviceConnection;
8import android.hardware.usb.UsbManager;
9
10public class BootUsbScanner extends BroadcastReceiver {
11 @Override
12 public void onReceive(Context context, Intent intent) {
13 // TODO: move probing of devices to a service, since AoapInterface.isSupported() could take
14 // up to 2 seconds and many USB devices could be connected.
15 UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
16 for (UsbDevice device : manager.getDeviceList().values()) {
17 if (AoapInterface.isDeviceInAoapMode(device)) {
18 // This could happen if we reboot. We should try to handle this accessory.
19 handle(context, device);
20 } else {
21 UsbDeviceConnection connection = UsbUtil.openConnection(manager, device);
22 try {
23 if (AoapInterface.isSupported(connection)) {
24 handle(context, device);
25 }
26 } finally {
27 connection.close();
28 }
29 }
30 }
31 }
32
33 private void handle(Context context, UsbDevice device) {
34 Intent manageDevice = new Intent(context, UsbHostManagementActivity.class);
35 manageDevice.setAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
36 manageDevice.putExtra(UsbManager.EXTRA_DEVICE, device);
37 manageDevice.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
38 context.startActivity(manageDevice);
39 }
40}
diff --git a/car-usb-handler/src/android/car/usb/handler/UsbDeviceHandlerResolver.java b/car-usb-handler/src/android/car/usb/handler/UsbDeviceHandlerResolver.java
new file mode 100644
index 00000000..c0eebbea
--- /dev/null
+++ b/car-usb-handler/src/android/car/usb/handler/UsbDeviceHandlerResolver.java
@@ -0,0 +1,670 @@
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.car.IUsbAoapSupportCheckService;
19import android.content.ComponentName;
20import android.content.Context;
21import android.content.Intent;
22import android.content.ServiceConnection;
23import android.content.pm.ActivityInfo;
24import android.content.pm.PackageManager;
25import android.content.pm.PackageManager.NameNotFoundException;
26import android.content.pm.ResolveInfo;
27import android.content.res.XmlResourceParser;
28import android.hardware.usb.UsbDevice;
29import android.hardware.usb.UsbDeviceConnection;
30import android.hardware.usb.UsbInterface;
31import android.hardware.usb.UsbManager;
32import android.os.Handler;
33import android.os.HandlerThread;
34import android.os.IBinder;
35import android.os.Looper;
36import android.os.Message;
37import android.os.RemoteException;
38import android.util.Log;
39import android.util.Pair;
40import com.android.internal.util.XmlUtils;
41import java.io.IOException;
42import java.util.ArrayList;
43import java.util.LinkedList;
44import java.util.List;
45import java.util.Queue;
46import org.xmlpull.v1.XmlPullParser;
47
48/** Resolves supported handlers for USB device. */
49public final class UsbDeviceHandlerResolver {
50 private static final String TAG = UsbDeviceHandlerResolver.class.getSimpleName();
51 private static final boolean LOCAL_LOGD = true;
52
53 /**
54 * Callbacks for device resolver.
55 */
56 public interface UsbDeviceHandlerResolverCallback {
57 /** Handlers are resolved */
58 void onHandlersResolveCompleted(
59 UsbDevice device, List<UsbDeviceSettings> availableSettings);
60 /** Device was dispatched */
61 void onDeviceDispatched();
62 }
63
64 private final UsbManager mUsbManager;
65 private final PackageManager mPackageManager;
66 private final UsbDeviceHandlerResolverCallback mDeviceCallback;
67 private final Context mContext;
68 private final HandlerThread mHandlerThread;
69 private final UsbDeviceResolverHandler mHandler;
70
71 private class DeviceContext {
72 public final UsbDevice usbDevice;
73 public final UsbDeviceConnection connection;
74 public final UsbDeviceSettings settings;
75 public final List<UsbDeviceSettings> activeDeviceSettings;
76 public final Queue<Pair<ResolveInfo, DeviceFilter>> mActiveDeviceOptions =
77 new LinkedList<>();
78
79 private volatile IUsbAoapSupportCheckService mUsbAoapSupportCheckService;
80 private final ServiceConnection mServiceConnection = new ServiceConnection() {
81 @Override
82 public void onServiceConnected(ComponentName className, IBinder service) {
83 Log.i(TAG, "onServiceConnected: " + className);
84 mUsbAoapSupportCheckService = IUsbAoapSupportCheckService.Stub.asInterface(service);
85 mHandler.requestOnServiceConnectionStateChanged(DeviceContext.this);
86 }
87
88 @Override
89 public void onServiceDisconnected(ComponentName className) {
90 Log.i(TAG, "onServiceDisconnected: " + className);
91 mUsbAoapSupportCheckService = null;
92 mHandler.requestOnServiceConnectionStateChanged(DeviceContext.this);
93 }
94 };
95
96 public DeviceContext(UsbDevice usbDevice, UsbDeviceSettings settings,
97 List<UsbDeviceSettings> activeDeviceSettings) {
98 this.usbDevice = usbDevice;
99 this.settings = settings;
100 this.activeDeviceSettings = activeDeviceSettings;
101 connection = UsbUtil.openConnection(mUsbManager, usbDevice);
102 }
103 }
104
105 // This class is used to describe a USB device.
106 // When used in HashMaps all values must be specified,
107 // but wildcards can be used for any of the fields in
108 // the package meta-data.
109 private static class DeviceFilter {
110 // USB Vendor ID (or -1 for unspecified)
111 public final int mVendorId;
112 // USB Product ID (or -1 for unspecified)
113 public final int mProductId;
114 // USB device or interface class (or -1 for unspecified)
115 public final int mClass;
116 // USB device subclass (or -1 for unspecified)
117 public final int mSubclass;
118 // USB device protocol (or -1 for unspecified)
119 public final int mProtocol;
120 // USB device manufacturer name string (or null for unspecified)
121 public final String mManufacturerName;
122 // USB device product name string (or null for unspecified)
123 public final String mProductName;
124 // USB device serial number string (or null for unspecified)
125 public final String mSerialNumber;
126
127 // USB device in AOAP mode manufacturer
128 public final String mAoapManufacturer;
129 // USB device in AOAP mode model
130 public final String mAoapModel;
131 // USB device in AOAP mode description string
132 public final String mAoapDescription;
133 // USB device in AOAP mode version
134 public final String mAoapVersion;
135 // USB device in AOAP mode URI
136 public final String mAoapUri;
137 // USB device in AOAP mode serial
138 public final String mAoapSerial;
139 // USB device in AOAP mode verification service
140 public final String mAoapService;
141
142 DeviceFilter(int vid, int pid, int clasz, int subclass, int protocol,
143 String manufacturer, String product, String serialnum,
144 String aoapManufacturer, String aoapModel, String aoapDescription,
145 String aoapVersion, String aoapUri, String aoapSerial,
146 String aoapService) {
147 mVendorId = vid;
148 mProductId = pid;
149 mClass = clasz;
150 mSubclass = subclass;
151 mProtocol = protocol;
152 mManufacturerName = manufacturer;
153 mProductName = product;
154 mSerialNumber = serialnum;
155
156 mAoapManufacturer = aoapManufacturer;
157 mAoapModel = aoapModel;
158 mAoapDescription = aoapDescription;
159 mAoapVersion = aoapVersion;
160 mAoapUri = aoapUri;
161 mAoapSerial = aoapSerial;
162 mAoapService = aoapService;
163 }
164
165 DeviceFilter(UsbDevice device) {
166 mVendorId = device.getVendorId();
167 mProductId = device.getProductId();
168 mClass = device.getDeviceClass();
169 mSubclass = device.getDeviceSubclass();
170 mProtocol = device.getDeviceProtocol();
171 mManufacturerName = device.getManufacturerName();
172 mProductName = device.getProductName();
173 mSerialNumber = device.getSerialNumber();
174 mAoapManufacturer = null;
175 mAoapModel = null;
176 mAoapDescription = null;
177 mAoapVersion = null;
178 mAoapUri = null;
179 mAoapSerial = null;
180 mAoapService = null;
181 }
182
183 public static DeviceFilter read(XmlPullParser parser, boolean aoapData) {
184 int vendorId = -1;
185 int productId = -1;
186 int deviceClass = -1;
187 int deviceSubclass = -1;
188 int deviceProtocol = -1;
189 String manufacturerName = null;
190 String productName = null;
191 String serialNumber = null;
192
193 String aoapManufacturer = null;
194 String aoapModel = null;
195 String aoapDescription = null;
196 String aoapVersion = null;
197 String aoapUri = null;
198 String aoapSerial = null;
199 String aoapService = null;
200
201 int count = parser.getAttributeCount();
202 for (int i = 0; i < count; i++) {
203 String name = parser.getAttributeName(i);
204 String value = parser.getAttributeValue(i);
205 // Attribute values are ints or strings
206 if (!aoapData && "manufacturer-name".equals(name)) {
207 manufacturerName = value;
208 } else if (!aoapData && "product-name".equals(name)) {
209 productName = value;
210 } else if (!aoapData && "serial-number".equals(name)) {
211 serialNumber = value;
212 } else if (aoapData && "manufacturer".equals(name)) {
213 aoapManufacturer = value;
214 } else if (aoapData && "model".equals(name)) {
215 aoapModel = value;
216 } else if (aoapData && "description".equals(name)) {
217 aoapDescription = value;
218 } else if (aoapData && "version".equals(name)) {
219 aoapVersion = value;
220 } else if (aoapData && "uri".equals(name)) {
221 aoapUri = value;
222 } else if (aoapData && "serial".equals(name)) {
223 aoapSerial = value;
224 } else if (aoapData && "service".equals(name)) {
225 aoapService = value;
226 } else if (!aoapData) {
227 int intValue = -1;
228 int radix = 10;
229 if (value != null && value.length() > 2 && value.charAt(0) == '0'
230 && (value.charAt(1) == 'x' || value.charAt(1) == 'X')) {
231 // allow hex values starting with 0x or 0X
232 radix = 16;
233 value = value.substring(2);
234 }
235 try {
236 intValue = Integer.parseInt(value, radix);
237 } catch (NumberFormatException e) {
238 Log.e(TAG, "invalid number for field " + name, e);
239 continue;
240 }
241 if ("vendor-id".equals(name)) {
242 vendorId = intValue;
243 } else if ("product-id".equals(name)) {
244 productId = intValue;
245 } else if ("class".equals(name)) {
246 deviceClass = intValue;
247 } else if ("subclass".equals(name)) {
248 deviceSubclass = intValue;
249 } else if ("protocol".equals(name)) {
250 deviceProtocol = intValue;
251 }
252 }
253 }
254 return new DeviceFilter(vendorId, productId,
255 deviceClass, deviceSubclass, deviceProtocol,
256 manufacturerName, productName, serialNumber, aoapManufacturer,
257 aoapModel, aoapDescription, aoapVersion, aoapUri, aoapSerial,
258 aoapService);
259 }
260
261 private boolean matches(int clasz, int subclass, int protocol) {
262 return ((mClass == -1 || clasz == mClass)
263 && (mSubclass == -1 || subclass == mSubclass)
264 && (mProtocol == -1 || protocol == mProtocol));
265 }
266
267 public boolean matches(UsbDevice device) {
268 if (mVendorId != -1 && device.getVendorId() != mVendorId) {
269 return false;
270 }
271 if (mProductId != -1 && device.getProductId() != mProductId) {
272 return false;
273 }
274 if (mManufacturerName != null && device.getManufacturerName() == null) {
275 return false;
276 }
277 if (mProductName != null && device.getProductName() == null) {
278 return false;
279 }
280 if (mSerialNumber != null && device.getSerialNumber() == null) {
281 return false;
282 }
283 if (mManufacturerName != null && device.getManufacturerName() != null
284 && !mManufacturerName.equals(device.getManufacturerName())) {
285 return false;
286 }
287 if (mProductName != null && device.getProductName() != null
288 && !mProductName.equals(device.getProductName())) {
289 return false;
290 }
291 if (mSerialNumber != null && device.getSerialNumber() != null
292 && !mSerialNumber.equals(device.getSerialNumber())) {
293 return false;
294 }
295
296 // check device class/subclass/protocol
297 if (matches(device.getDeviceClass(), device.getDeviceSubclass(),
298 device.getDeviceProtocol())) {
299 return true;
300 }
301
302 // if device doesn't match, check the interfaces
303 int count = device.getInterfaceCount();
304 for (int i = 0; i < count; i++) {
305 UsbInterface intf = device.getInterface(i);
306 if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(),
307 intf.getInterfaceProtocol())) {
308 return true;
309 }
310 }
311
312 return false;
313 }
314
315 @Override
316 public boolean equals(Object obj) {
317 // can't compare if we have wildcard strings
318 if (mVendorId == -1 || mProductId == -1
319 || mClass == -1 || mSubclass == -1 || mProtocol == -1) {
320 return false;
321 }
322 if (obj instanceof DeviceFilter) {
323 DeviceFilter filter = (DeviceFilter) obj;
324
325 if (filter.mVendorId != mVendorId
326 || filter.mProductId != mProductId
327 || filter.mClass != mClass
328 || filter.mSubclass != mSubclass
329 || filter.mProtocol != mProtocol) {
330 return false;
331 }
332 if ((filter.mManufacturerName != null && mManufacturerName == null)
333 || (filter.mManufacturerName == null && mManufacturerName != null)
334 || (filter.mProductName != null && mProductName == null)
335 || (filter.mProductName == null && mProductName != null)
336 || (filter.mSerialNumber != null && mSerialNumber == null)
337 || (filter.mSerialNumber == null && mSerialNumber != null)) {
338 return false;
339 }
340 if ((filter.mManufacturerName != null && mManufacturerName != null
341 && !mManufacturerName.equals(filter.mManufacturerName))
342 || (filter.mProductName != null && mProductName != null
343 && !mProductName.equals(filter.mProductName))
344 || (filter.mSerialNumber != null && mSerialNumber != null
345 && !mSerialNumber.equals(filter.mSerialNumber))) {
346 return false;
347 }
348 return true;
349 }
350 if (obj instanceof UsbDevice) {
351 UsbDevice device = (UsbDevice) obj;
352 if (device.getVendorId() != mVendorId
353 || device.getProductId() != mProductId
354 || device.getDeviceClass() != mClass
355 || device.getDeviceSubclass() != mSubclass
356 || device.getDeviceProtocol() != mProtocol) {
357 return false;
358 }
359 if ((mManufacturerName != null && device.getManufacturerName() == null)
360 || (mManufacturerName == null && device.getManufacturerName() != null)
361 || (mProductName != null && device.getProductName() == null)
362 || (mProductName == null && device.getProductName() != null)
363 || (mSerialNumber != null && device.getSerialNumber() == null)
364 || (mSerialNumber == null && device.getSerialNumber() != null)) {
365 return false;
366 }
367 if ((device.getManufacturerName() != null
368 && !mManufacturerName.equals(device.getManufacturerName()))
369 || (device.getProductName() != null
370 && !mProductName.equals(device.getProductName()))
371 || (device.getSerialNumber() != null
372 && !mSerialNumber.equals(device.getSerialNumber()))) {
373 return false;
374 }
375 return true;
376 }
377 return false;
378 }
379
380 @Override
381 public int hashCode() {
382 return (((mVendorId << 16) | mProductId)
383 ^ ((mClass << 16) | (mSubclass << 8) | mProtocol));
384 }
385
386 @Override
387 public String toString() {
388 return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId
389 + ",mClass=" + mClass + ",mSubclass=" + mSubclass
390 + ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName
391 + ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber + "]";
392 }
393 }
394
395 public UsbDeviceHandlerResolver(UsbManager manager, Context context,
396 UsbDeviceHandlerResolverCallback deviceListener) {
397 mUsbManager = manager;
398 mContext = context;
399 mDeviceCallback = deviceListener;
400 mHandlerThread = new HandlerThread(TAG);
401 mHandlerThread.start();
402 mHandler = new UsbDeviceResolverHandler(mHandlerThread.getLooper());
403 mPackageManager = context.getPackageManager();
404 }
405
406 /**
407 * Releases current object.
408 */
409 public void release() {
410 if (mHandlerThread != null) {
411 mHandlerThread.quitSafely();
412 }
413 }
414
415 /**
416 * Resolves handlers for USB device.
417 */
418 public void resolve(UsbDevice device) {
419 mHandler.requestResolveHandlers(device);
420 }
421
422 /**
423 * Dispatches device to component.
424 */
425 public boolean dispatch(UsbDevice device, ComponentName component, boolean inAoap) {
426 if (LOCAL_LOGD) {
427 Log.d(TAG, "dispatch: " + device + " component: " + component + " inAoap: " + inAoap);
428 }
429
430 ActivityInfo activityInfo;
431 try {
432 activityInfo = mPackageManager.getActivityInfo(component, PackageManager.GET_META_DATA);
433 } catch (NameNotFoundException e) {
434 Log.e(TAG, "Activity not found: " + component);
435 return false;
436 }
437
438 Intent intent = createDeviceAttachedIntent(device);
439 if (inAoap) {
440 if (AoapInterface.isDeviceInAoapMode(device)) {
441 mDeviceCallback.onDeviceDispatched();
442 } else {
443 DeviceFilter filter =
444 packageMatches(activityInfo, intent.getAction(), device, true);
445 if (filter != null) {
446 requestAoapSwitch(device, filter);
447 return true;
448 }
449 }
450 }
451
452 intent.setComponent(component);
453 mUsbManager.grantPermission(device, activityInfo.applicationInfo.uid);
454
455 mContext.startActivity(intent);
456 mHandler.requestCompleteDeviceDispatch();
457 return true;
458 }
459
460 private static Intent createDeviceAttachedIntent(UsbDevice device) {
461 Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
462 intent.putExtra(UsbManager.EXTRA_DEVICE, device);
463 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
464 return intent;
465 }
466
467 private void doHandleResolveHandlers(UsbDevice device) {
468 if (LOCAL_LOGD) {
469 Log.d(TAG, "doHandleResolveHandlers: " + device);
470 }
471
472 Intent intent = createDeviceAttachedIntent(device);
473 List<Pair<ResolveInfo, DeviceFilter>> matches = getDeviceMatches(device, intent, false);
474 if (LOCAL_LOGD) {
475 Log.d(TAG, "matches size: " + matches.size());
476 }
477 List<UsbDeviceSettings> settings = new ArrayList<>(matches.size());
478 for (Pair<ResolveInfo, DeviceFilter> info : matches) {
479 UsbDeviceSettings setting = UsbDeviceSettings.constructSettings(device);
480 setting.setHandler(
481 new ComponentName(
482 info.first.activityInfo.packageName, info.first.activityInfo.name));
483 settings.add(setting);
484 }
485 DeviceContext deviceContext =
486 new DeviceContext(device, UsbDeviceSettings.constructSettings(device), settings);
487 if (AoapInterface.isSupported(deviceContext.connection)) {
488 deviceContext.mActiveDeviceOptions.addAll(getDeviceMatches(device, intent, true));
489 queryNextAoapHandler(deviceContext);
490 } else {
491 deviceProbingComplete(deviceContext);
492 }
493 }
494
495 private void queryNextAoapHandler(DeviceContext context) {
496 Pair<ResolveInfo, DeviceFilter> option = context.mActiveDeviceOptions.peek();
497 if (option == null) {
498 Log.w(TAG, "No more options left.");
499 deviceProbingComplete(context);
500 return;
501 }
502 Intent serviceIntent = new Intent();
503 serviceIntent.setComponent(ComponentName.unflattenFromString(option.second.mAoapService));
504 boolean bound = mContext.bindService(serviceIntent, context.mServiceConnection,
505 Context.BIND_AUTO_CREATE);
506 if (bound) {
507 mHandler.requestServiceConnectionTimeout();
508 } else {
509 if (LOCAL_LOGD) {
510 Log.d(TAG, "Failed to bind to the service");
511 }
512 context.mActiveDeviceOptions.poll();
513 queryNextAoapHandler(context);
514 }
515 }
516
517 private void requestAoapSwitch(UsbDevice device, DeviceFilter filter) {
518 UsbDeviceConnection connection = UsbUtil.openConnection(mUsbManager, device);
519 try {
520 UsbUtil.sendAoapAccessoryStart(
521 connection,
522 filter.mAoapManufacturer,
523 filter.mAoapModel,
524 filter.mAoapDescription,
525 filter.mAoapVersion,
526 filter.mAoapUri,
527 filter.mAoapSerial);
528 } catch (IOException e) {
529 Log.w(TAG, "Failed to switch device into AOAP mode", e);
530 }
531 connection.close();
532 }
533
534 private void deviceProbingComplete(DeviceContext context) {
535 if (LOCAL_LOGD) {
536 Log.d(TAG, "deviceProbingComplete");
537 }
538 mDeviceCallback.onHandlersResolveCompleted(context.usbDevice, context.activeDeviceSettings);
539 }
540
541 private void doHandleServiceConnectionStateChanged(DeviceContext context) {
542 if (LOCAL_LOGD) {
543 Log.d(TAG, "doHandleServiceConnectionStateChanged: "
544 + context.mUsbAoapSupportCheckService);
545 }
546 if (context.mUsbAoapSupportCheckService != null) {
547 boolean deviceSupported = false;
548 try {
549 deviceSupported =
550 context.mUsbAoapSupportCheckService.isDeviceSupported(context.usbDevice);
551 } catch (RemoteException e) {
552 Log.e(TAG, "Call to remote service failed", e);
553 }
554 if (deviceSupported) {
555 Pair<ResolveInfo, DeviceFilter> option = context.mActiveDeviceOptions.peek();
556
557 UsbDeviceSettings setting = UsbDeviceSettings.constructSettings(context.settings);
558 setting.setHandler(
559 new ComponentName(
560 option.first.activityInfo.packageName, option.first.activityInfo.name));
561 setting.setAoap(true);
562 context.activeDeviceSettings.add(setting);
563 }
564 mContext.unbindService(context.mServiceConnection);
565 }
566 context.mActiveDeviceOptions.poll();
567 queryNextAoapHandler(context);
568 }
569
570 private List<Pair<ResolveInfo, DeviceFilter>> getDeviceMatches(
571 UsbDevice device, Intent intent, boolean forAoap) {
572 List<Pair<ResolveInfo, DeviceFilter>> matches = new ArrayList<>();
573 List<ResolveInfo> resolveInfos =
574 mPackageManager.queryIntentActivities(intent, PackageManager.GET_META_DATA);
575 for (ResolveInfo resolveInfo : resolveInfos) {
576 DeviceFilter filter = packageMatches(resolveInfo.activityInfo,
577 intent.getAction(), device, forAoap);
578 if (filter != null) {
579 matches.add(Pair.create(resolveInfo, filter));
580 }
581 }
582 return matches;
583 }
584
585 private DeviceFilter packageMatches(ActivityInfo ai, String metaDataName, UsbDevice device,
586 boolean forAoap) {
587 if (LOCAL_LOGD) {
588 Log.d(TAG, "packageMatches ai: " + ai + "metaDataName: " + metaDataName + " forAoap: "
589 + forAoap);
590 }
591 String filterTagName = forAoap ? "usb-aoap-accessory" : "usb-device";
592 XmlResourceParser parser = null;
593 try {
594 parser = ai.loadXmlMetaData(mPackageManager, metaDataName);
595 if (parser == null) {
596 Log.w(TAG, "no meta-data for " + ai);
597 return null;
598 }
599
600 XmlUtils.nextElement(parser);
601 while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
602 String tagName = parser.getName();
603 if (device != null && filterTagName.equals(tagName)) {
604 DeviceFilter filter = DeviceFilter.read(parser, forAoap);
605 if (forAoap || filter.matches(device)) {
606 return filter;
607 }
608 }
609 XmlUtils.nextElement(parser);
610 }
611 } catch (Exception e) {
612 Log.w(TAG, "Unable to load component info " + ai.toString(), e);
613 } finally {
614 if (parser != null) parser.close();
615 }
616 return null;
617 }
618
619 private class UsbDeviceResolverHandler extends Handler {
620 private static final int MSG_RESOLVE_HANDLERS = 0;
621 private static final int MSG_SERVICE_CONNECTION_STATE_CHANGE = 1;
622 private static final int MSG_SERVICE_CONNECTION_TIMEOUT = 2;
623 private static final int MSG_COMPLETE_DISPATCH = 3;
624
625 private static final long CONNECT_TIMEOUT_MS = 5000;
626
627 private UsbDeviceResolverHandler(Looper looper) {
628 super(looper);
629 }
630
631 public void requestResolveHandlers(UsbDevice device) {
632 Message msg = obtainMessage(MSG_RESOLVE_HANDLERS, device);
633 sendMessage(msg);
634 }
635
636 public void requestOnServiceConnectionStateChanged(DeviceContext deviceContext) {
637 sendMessage(obtainMessage(MSG_SERVICE_CONNECTION_STATE_CHANGE, deviceContext));
638 }
639
640 public void requestServiceConnectionTimeout() {
641 sendEmptyMessageDelayed(MSG_SERVICE_CONNECTION_TIMEOUT, CONNECT_TIMEOUT_MS);
642 }
643
644 public void requestCompleteDeviceDispatch() {
645 sendEmptyMessage(MSG_COMPLETE_DISPATCH);
646 }
647
648 @Override
649 public void handleMessage(Message msg) {
650 switch (msg.what) {
651 case MSG_RESOLVE_HANDLERS:
652 doHandleResolveHandlers((UsbDevice) msg.obj);
653 break;
654 case MSG_SERVICE_CONNECTION_STATE_CHANGE:
655 removeMessages(MSG_SERVICE_CONNECTION_TIMEOUT);
656 doHandleServiceConnectionStateChanged((DeviceContext) msg.obj);
657 break;
658 case MSG_SERVICE_CONNECTION_TIMEOUT:
659 Log.i(TAG, "Service connection timeout");
660 doHandleServiceConnectionStateChanged(null);
661 break;
662 case MSG_COMPLETE_DISPATCH:
663 mDeviceCallback.onDeviceDispatched();
664 break;
665 default:
666 Log.w(TAG, "Unsupported message: " + msg);
667 }
668 }
669 }
670}
diff --git a/car-usb-handler/src/android/car/usb/handler/UsbDevicePreference.java b/car-usb-handler/src/android/car/usb/handler/UsbDevicePreference.java
new file mode 100644
index 00000000..e4b6a846
--- /dev/null
+++ b/car-usb-handler/src/android/car/usb/handler/UsbDevicePreference.java
@@ -0,0 +1,71 @@
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.app.AlertDialog;
19import android.content.Context;
20import android.content.DialogInterface;
21import android.preference.Preference;
22
23/**
24 * Setting preference used for USB devices.
25 */
26public final class UsbDevicePreference extends Preference
27 implements Preference.OnPreferenceClickListener {
28
29 /**
30 * Callbacks to handle preference changes.
31 */
32 public interface UsbDevicePreferenceCallback {
33 /** Preference deleted */
34 void onUsbDevicePreferenceDelete(Preference preference, UsbDeviceSettings settings);
35 }
36
37 private final UsbDeviceSettings mUsbDeviceSettings;
38 private final UsbDevicePreferenceCallback mCallback;
39
40 public UsbDevicePreference(Context context, UsbDeviceSettings usbDeviceSettings,
41 UsbDevicePreferenceCallback callback) {
42 super(context);
43 mCallback = callback;
44 mUsbDeviceSettings = usbDeviceSettings;
45 setTitle(usbDeviceSettings.getDeviceName());
46 if (usbDeviceSettings.getHandler() != null) {
47 setSummary(usbDeviceSettings.getHandler().flattenToShortString());
48 }
49 setOnPreferenceClickListener(this);
50 }
51
52 @Override
53 public boolean onPreferenceClick(final Preference preference) {
54 new AlertDialog.Builder(getContext())
55 .setTitle(R.string.usb_pref_delete_title)
56 .setMessage(String.format(
57 getContext().getResources().getString(R.string.usb_pref_delete_message),
58 mUsbDeviceSettings.getDeviceName()))
59 .setIcon(android.R.drawable.ic_dialog_alert)
60 .setPositiveButton(R.string.usb_pref_delete_yes,
61 new DialogInterface.OnClickListener() {
62 @Override
63 public void onClick(DialogInterface dialog, int whichButton) {
64 mCallback.onUsbDevicePreferenceDelete(
65 preference, mUsbDeviceSettings);
66 }})
67 .setNegativeButton(R.string.usb_pref_delete_cancel, null)
68 .show();
69 return true;
70 }
71}
diff --git a/car-usb-handler/src/android/car/usb/handler/UsbDeviceSettings.java b/car-usb-handler/src/android/car/usb/handler/UsbDeviceSettings.java
new file mode 100644
index 00000000..50844140
--- /dev/null
+++ b/car-usb-handler/src/android/car/usb/handler/UsbDeviceSettings.java
@@ -0,0 +1,135 @@
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.ComponentName;
19import android.hardware.usb.UsbDevice;
20import com.android.internal.util.Preconditions;
21
22/**
23 * Settings for USB device.
24 * @hide
25 */
26public final class UsbDeviceSettings {
27
28 private final String mSerialNumber;
29 private final int mVid;
30 private final int mPid;
31 private String mDeviceName;
32 private ComponentName mHandler;
33 private boolean mAoap;
34 private boolean mDefaultHandler;
35
36 UsbDeviceSettings(String serialNumber, int vid, int pid) {
37 Preconditions.checkNotNull(serialNumber);
38
39 mSerialNumber = serialNumber;
40 mVid = vid;
41 mPid = pid;
42 }
43
44 public String getSerialNumber() {
45 return mSerialNumber;
46 }
47
48 public int getVid() {
49 return mVid;
50 }
51
52 public int getPid() {
53 return mPid;
54 }
55
56 public void setDeviceName(String deviceName) {
57 mDeviceName = deviceName;
58 }
59
60 public String getDeviceName() {
61 return mDeviceName;
62 }
63
64 public void setHandler(ComponentName handler) {
65 mHandler = handler;
66 }
67
68 public ComponentName getHandler() {
69 return mHandler;
70 }
71
72 public void setAoap(boolean aoap) {
73 mAoap = aoap;
74 }
75
76 public boolean getAoap() {
77 return mAoap;
78 }
79
80 public void setDefaultHandler(boolean defaultHandler) {
81 mDefaultHandler = defaultHandler;
82 }
83
84 public boolean isDefaultHandler() {
85 return mDefaultHandler;
86 }
87
88 @Override
89 public String toString() {
90 return "UsbDeviceSettings{serial=" + mSerialNumber + ", vid=" + mVid + ", pid=" + mPid
91 + ", name=" + mDeviceName + ", handler=" + mHandler.toString() + ", aoap=" + mAoap
92 + ", default=" + mDefaultHandler + "}";
93 }
94
95 /**
96 * Checks if setting matches {@code UsbDevice}.
97 */
98 public boolean matchesDevice(UsbDevice device) {
99 return getSerialNumber().equals(device.getSerialNumber());
100 }
101
102 /**
103 * Creates settings from {@code UsbDevice}.
104 */
105 public static UsbDeviceSettings constructSettings(UsbDevice device) {
106 UsbDeviceSettings settings = new UsbDeviceSettings(
107 device.getSerialNumber(), device.getVendorId(), device.getProductId());
108 settings.setDeviceName(device.getProductName());
109 return settings;
110 }
111
112 /**
113 * Creates settings from other settings.
114 * <p>
115 * Only basic properties are inherited.
116 */
117 public static UsbDeviceSettings constructSettings(UsbDeviceSettings origSettings) {
118 UsbDeviceSettings settings = new UsbDeviceSettings(
119 origSettings.getSerialNumber(), origSettings.getVid(), origSettings.getPid());
120 settings.setDeviceName(origSettings.getDeviceName());
121 return settings;
122 }
123
124 /**
125 * Creates settings.
126 */
127 public static UsbDeviceSettings constructSettings(String serialNumber, int vid, int pid,
128 String deviceName, ComponentName handler, boolean aoap) {
129 UsbDeviceSettings settings = new UsbDeviceSettings(serialNumber, vid, pid);
130 settings.setDeviceName(deviceName);
131 settings.setHandler(handler);
132 settings.setAoap(aoap);
133 return settings;
134 }
135}
diff --git a/car-usb-handler/src/android/car/usb/handler/UsbHostController.java b/car-usb-handler/src/android/car/usb/handler/UsbHostController.java
new file mode 100644
index 00000000..b705928f
--- /dev/null
+++ b/car-usb-handler/src/android/car/usb/handler/UsbHostController.java
@@ -0,0 +1,241 @@
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 unsetActiveDeviceIfSerialMatch(device);
72 } else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) {
73 UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE);
74 setActiveDeviceIfSerialMatch(device);
75 }
76 }
77 };
78
79 @GuardedBy("this")
80 private UsbDevice mActiveDevice;
81
82 @GuardedBy("this")
83 private String mProcessingDeviceSerial;
84
85 public UsbHostController(Context context, UsbHostControllerCallbacks callbacks) {
86 mContext = context;
87 mCallback = callbacks;
88 mHandler = new UsbHostControllerHandler(Looper.myLooper());
89 mUsbSettingsStorage = new UsbSettingsStorage(context);
90 mUsbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
91 mUsbResolver = new UsbDeviceHandlerResolver(mUsbManager, mContext, this);
92 IntentFilter filter = new IntentFilter();
93 filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
94 filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
95 context.registerReceiver(mUsbBroadcastReceiver, filter);
96
97 }
98
99 private synchronized void setActiveDeviceIfSerialMatch(UsbDevice device) {
100 if (device != null && device.getSerialNumber() != null
101 && device.getSerialNumber().equals(mProcessingDeviceSerial)) {
102 mActiveDevice = device;
103 }
104 }
105
106 private synchronized void unsetActiveDeviceIfSerialMatch(UsbDevice device) {
107 mHandler.requestDeviceRemoved();
108 if (mActiveDevice != null && mActiveDevice.getSerialNumber() != null
109 && mActiveDevice.getSerialNumber().equals(device.getSerialNumber())) {
110 mActiveDevice = null;
111 }
112 }
113
114 private synchronized boolean startDeviceProcessingIfNull(UsbDevice device) {
115 if (mActiveDevice == null) {
116 mActiveDevice = device;
117 mProcessingDeviceSerial = device.getSerialNumber();
118 return true;
119 }
120 return false;
121 }
122
123 private synchronized void stopDeviceProcessing() {
124 mActiveDevice = null;
125 mProcessingDeviceSerial = null;
126 }
127
128 private synchronized UsbDevice getActiveDevice() {
129 return mActiveDevice;
130 }
131
132 private boolean deviceMatchedActiveDevice(UsbDevice device) {
133 UsbDevice activeDevice = getActiveDevice();
134 return activeDevice != null && activeDevice.getSerialNumber() != null
135 && activeDevice.getSerialNumber().equals(device.getSerialNumber());
136 }
137
138 /**
139 * Processes device new device.
140 * <p>
141 * It will load existing settings or resolve supported handlers.
142 */
143 public void processDevice(UsbDevice device) {
144 if (!startDeviceProcessingIfNull(device)) {
145 Log.w(TAG, "Currently, other device is being processed");
146 }
147 mCallback.optionsUpdated(mEmptyList);
148 mCallback.processingStateChanged(true);
149
150 UsbDeviceSettings settings = mUsbSettingsStorage.getSettings(device.getSerialNumber());
151 if (settings != null && mUsbResolver.dispatch(
152 mActiveDevice, settings.getHandler(), settings.getAoap())) {
153 if (LOCAL_LOGV) {
154 Log.v(TAG, "Usb Device: " + device + " was sent to component: "
155 + settings.getHandler());
156 }
157 return;
158 }
159 mCallback.titleChanged(device.getManufacturerName() + " " + device.getProductName());
160 mUsbResolver.resolve(device);
161 }
162
163 /**
164 * Applies device settings.
165 */
166 public void applyDeviceSettings(UsbDeviceSettings settings) {
167 mUsbSettingsStorage.saveSettings(settings);
168 mUsbResolver.dispatch(getActiveDevice(), settings.getHandler(), settings.getAoap());
169 }
170
171 /**
172 * Release object.
173 */
174 public void release() {
175 mContext.unregisterReceiver(mUsbBroadcastReceiver);
176 mUsbResolver.release();
177 }
178
179 @Override
180 public void onHandlersResolveCompleted(
181 UsbDevice device, List<UsbDeviceSettings> handlers) {
182 if (LOCAL_LOGD) {
183 Log.d(TAG, "onHandlersResolveComplete: " + device);
184 }
185 if (deviceMatchedActiveDevice(device)) {
186 mCallback.processingStateChanged(false);
187 if (handlers.isEmpty()) {
188 onDeviceDispatched();
189 } else if (handlers.size() == 1) {
190 applyDeviceSettings(handlers.get(0));
191 } else {
192 mCallback.optionsUpdated(handlers);
193 }
194 } else {
195 Log.w(TAG, "Handlers ignored as they came for inactive device");
196 }
197 }
198
199 @Override
200 public void onDeviceDispatched() {
201 stopDeviceProcessing();
202 mCallback.shutdown();
203 }
204
205 void doHandleDeviceRemoved() {
206 if (getActiveDevice() == null) {
207 if (LOCAL_LOGD) {
208 Log.d(TAG, "USB device detached");
209 }
210 stopDeviceProcessing();
211 mCallback.shutdown();
212 }
213 }
214
215 private class UsbHostControllerHandler extends Handler {
216 private static final int MSG_DEVICE_REMOVED = 1;
217
218 private static final int DEVICE_REMOVE_TIMEOUT_MS = 500;
219
220 private UsbHostControllerHandler(Looper looper) {
221 super(looper);
222 }
223
224 private void requestDeviceRemoved() {
225 sendEmptyMessageDelayed(MSG_DEVICE_REMOVED, DEVICE_REMOVE_TIMEOUT_MS);
226 }
227
228 @Override
229 public void handleMessage(Message msg) {
230 switch (msg.what) {
231 case MSG_DEVICE_REMOVED:
232 doHandleDeviceRemoved();
233 break;
234 default:
235 Log.w(TAG, "Unhandled message: " + msg);
236 super.handleMessage(msg);
237 }
238 }
239 }
240
241}
diff --git a/car-usb-handler/src/android/car/usb/handler/UsbHostManagementActivity.java b/car-usb-handler/src/android/car/usb/handler/UsbHostManagementActivity.java
new file mode 100644
index 00000000..387ae62c
--- /dev/null
+++ b/car-usb-handler/src/android/car/usb/handler/UsbHostManagementActivity.java
@@ -0,0 +1,191 @@
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.annotation.Nullable;
19import android.app.Activity;
20import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.content.pm.ApplicationInfo;
24import android.content.pm.PackageManager;
25import android.content.pm.PackageManager.NameNotFoundException;
26import android.hardware.usb.UsbDevice;
27import android.hardware.usb.UsbManager;
28import android.os.Bundle;
29import android.util.Log;
30import android.view.View;
31import android.view.ViewGroup;
32import android.widget.AdapterView;
33import android.widget.ArrayAdapter;
34import android.widget.ImageView;
35import android.widget.LinearLayout;
36import android.widget.ListView;
37import android.widget.TextView;
38import java.util.List;
39
40/**
41 * Activity to handle USB device attached.
42 * <p>
43 * When user plugs in USB device: a) Device was used before and user selected handler for it. In
44 * this case handler will be launched. b) Device has not handler assigned. In this case supported
45 * handlers will be captured, and user will be presented with choice to assign default handler.
46 * After that handler will be launched.
47 */
48public class UsbHostManagementActivity extends Activity
49 implements UsbHostController.UsbHostControllerCallbacks {
50 private static final String TAG = UsbHostManagementActivity.class.getSimpleName();
51
52 private HandlersAdapter mListAdapter;
53 private ListView mHandlersList;
54 private LinearLayout mProgressInfo;
55 private UsbHostController mController;
56 private PackageManager mPackageManager;
57
58 private final AdapterView.OnItemClickListener mHandlerClickListener =
59 new AdapterView.OnItemClickListener() {
60 @Override
61 public void onItemClick(AdapterView<?> parent, final View view, int position, long id) {
62 UsbDeviceSettings settings = (UsbDeviceSettings) parent.getItemAtPosition(position);
63 settings.setDefaultHandler(true);
64 mController.applyDeviceSettings(settings);
65 }
66 };
67
68 @Override
69 public void onCreate(Bundle savedInstanceState) {
70 super.onCreate(savedInstanceState);
71 setContentView(R.layout.usb_host);
72 mHandlersList = (ListView) findViewById(R.id.usb_handlers_list);
73 mProgressInfo = (LinearLayout) findViewById(R.id.usb_handlers_progress);
74 mListAdapter = new HandlersAdapter(this);
75 mHandlersList.setAdapter(mListAdapter);
76 mHandlersList.setOnItemClickListener(mHandlerClickListener);
77 mController = new UsbHostController(this, this);
78 mPackageManager = getPackageManager();
79 }
80
81 @Override
82 public void onDestroy() {
83 super.onDestroy();
84 mController.release();
85 }
86
87 @Override
88 public void onResume() {
89 super.onResume();
90 UsbDevice connectedDevice = getDevice();
91 if (connectedDevice != null) {
92 mController.processDevice(connectedDevice);
93 } else {
94 finish();
95 }
96 }
97
98 @Override
99 public void shutdown() {
100 runOnUiThread(new Runnable() {
101 @Override
102 public void run() {
103 finish();
104 }
105 });
106 }
107
108 @Override
109 public void processingStateChanged(final boolean processing) {
110 runOnUiThread(new Runnable() {
111 @Override
112 public void run() {
113 mProgressInfo.setVisibility(processing ? View.VISIBLE : View.GONE);
114 }
115 });
116 }
117
118 @Override
119 public void titleChanged(final String title) {
120 runOnUiThread(new Runnable() {
121 @Override
122 public void run() {
123 setTitle(title);
124 }
125 });
126 }
127
128 @Override
129 public void optionsUpdated(final List<UsbDeviceSettings> options) {
130 runOnUiThread(new Runnable() {
131 @Override
132 public void run() {
133 mListAdapter.clear();
134 mListAdapter.addAll(options);
135 }
136 });
137 }
138
139
140 @Override
141 public void onNewIntent(Intent intent) {
142 super.onNewIntent(intent);
143 setIntent(intent);
144 }
145
146 @Nullable
147 private UsbDevice getDevice() {
148 if (!UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(getIntent().getAction())) {
149 return null;
150 }
151 return (UsbDevice) getIntent().getParcelableExtra(UsbManager.EXTRA_DEVICE);
152 }
153
154 private class HandlersAdapter extends ArrayAdapter<UsbDeviceSettings> {
155 class HandlerHolder {
156 public TextView mAppName;
157 public ImageView mAppIcon;
158 }
159
160 HandlersAdapter(Context context) {
161 super(context, R.layout.usb_handler_row);
162 }
163
164 @Override
165 public View getView(int position, View convertView, ViewGroup parent) {
166 View rowView = convertView;
167 if (rowView == null) {
168 rowView = getLayoutInflater().inflate(R.layout.usb_handler_row, null);
169 HandlerHolder holder = new HandlerHolder();
170 holder.mAppName = (TextView) rowView.findViewById(R.id.usb_handler_title);
171 holder.mAppIcon = (ImageView) rowView.findViewById(R.id.usb_handler_icon);
172 rowView.setTag(holder);
173 }
174
175 HandlerHolder holder = (HandlerHolder) rowView.getTag();
176 ComponentName handler = getItem(position).getHandler();
177
178 try {
179 ApplicationInfo appInfo =
180 mPackageManager.getApplicationInfo(handler.getPackageName(), 0);
181 holder.mAppName.setText(appInfo.loadLabel(mPackageManager));
182 holder.mAppIcon.setImageDrawable(appInfo.loadIcon(mPackageManager));
183 } catch (NameNotFoundException e) {
184 Log.e(TAG, "Handling package not found: " + handler.getPackageName());
185 holder.mAppName.setText(handler.flattenToShortString());
186 holder.mAppIcon.setImageResource(android.R.color.transparent);
187 }
188 return rowView;
189 }
190 }
191}
diff --git a/car-usb-handler/src/android/car/usb/handler/UsbSettingsStorage.java b/car-usb-handler/src/android/car/usb/handler/UsbSettingsStorage.java
new file mode 100644
index 00000000..157c92f9
--- /dev/null
+++ b/car-usb-handler/src/android/car/usb/handler/UsbSettingsStorage.java
@@ -0,0 +1,196 @@
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.annotation.Nullable;
19import android.content.ComponentName;
20import android.content.ContentValues;
21import android.content.Context;
22import android.database.Cursor;
23import android.database.sqlite.SQLiteDatabase;
24import android.database.sqlite.SQLiteOpenHelper;
25import android.util.Log;
26import java.util.ArrayList;
27import java.util.List;
28
29/**
30 * Provides API to persist USB device settings.
31 */
32public final class UsbSettingsStorage {
33 private static final String TAG = UsbSettingsStorage.class.getSimpleName();
34
35 private static final String TABLE_USB_SETTINGS = "usb_devices";
36 private static final String COLUMN_SERIAL = "serial";
37 private static final String COLUMN_VID = "vid";
38 private static final String COLUMN_PID = "pid";
39 private static final String COLUMN_NAME = "name";
40 private static final String COLUMN_HANDLER = "handler";
41 private static final String COLUMN_AOAP = "aoap";
42 private static final String COLUMN_DEFAULT_HANDLER = "default_handler";
43
44 private final UsbSettingsDbHelper mDbHelper;
45
46 public UsbSettingsStorage(Context context) {
47 mDbHelper = new UsbSettingsDbHelper(context);
48 }
49
50 /**
51 * Returns settings for {@serialNumber} or null if it doesn't exist.
52 */
53 @Nullable
54 public UsbDeviceSettings getSettings(String serialNumber) {
55 try (SQLiteDatabase db = mDbHelper.getReadableDatabase();
56 Cursor resultCursor = db.query(
57 TABLE_USB_SETTINGS,
58 null,
59 COLUMN_SERIAL + " = ?",
60 new String[]{serialNumber},
61 null,
62 null,
63 null)) {
64 if (resultCursor.getCount() > 1) {
65 throw new RuntimeException("Querying for serial number: " + serialNumber
66 + " returned " + resultCursor.getCount() + " results");
67 }
68 if (resultCursor.getCount() == 0) {
69 Log.w(TAG, "Usb setting missing for device serial: " + serialNumber);
70 return null;
71 }
72 List<UsbDeviceSettings> settings = constructSettings(resultCursor);
73 return settings.get(0);
74 }
75 }
76
77 /**
78 * Saves or updates settings for USB device.
79 */
80 public void saveSettings(UsbDeviceSettings settings) {
81 try (SQLiteDatabase db = mDbHelper.getWritableDatabase()) {
82 long result = db.replace(
83 TABLE_USB_SETTINGS,
84 null,
85 settingsToContentValues(settings));
86 if (result == -1) {
87 Log.e(TAG, "Failed to save settings: " + settings);
88 }
89 }
90 }
91
92 /**
93 * Delete settings for USB device.
94 */
95 public void deleteSettings(String serialNumber, int vid, int pid) {
96 try (SQLiteDatabase db = mDbHelper.getWritableDatabase()) {
97 int result = db.delete(
98 TABLE_USB_SETTINGS,
99 COLUMN_SERIAL + " = ? AND " + COLUMN_VID + " = ? AND " + COLUMN_PID
100 + " = ?",
101 new String[]{serialNumber, Integer.toString(vid), Integer.toString(pid)});
102 if (result == 0) {
103 Log.w(TAG, "No settings with serialNumber: " + serialNumber
104 + " vid: " + vid + " pid: " + pid);
105 }
106 if (result > 1) {
107 Log.e(TAG, "Deleted multiple rows (" + result + ") for serialNumber: "
108 + serialNumber + " vid: " + vid + " pid: " + pid);
109 }
110 }
111 }
112
113 /**
114 * Returns all saved settings.
115 */
116 public List<UsbDeviceSettings> getAllSettings() {
117 try (SQLiteDatabase db = mDbHelper.getReadableDatabase();
118 Cursor resultCursor = db.query(
119 TABLE_USB_SETTINGS,
120 null,
121 null,
122 null,
123 null,
124 null,
125 null)) {
126 return constructSettings(resultCursor);
127 }
128 }
129
130 private List<UsbDeviceSettings> constructSettings(Cursor cursor) {
131 if (!cursor.isBeforeFirst()) {
132 throw new RuntimeException("Cursor is not reset to before first element");
133 }
134 int serialNumberColumnId = cursor.getColumnIndex(COLUMN_SERIAL);
135 int vidColumnId = cursor.getColumnIndex(COLUMN_VID);
136 int pidColumnId = cursor.getColumnIndex(COLUMN_PID);
137 int deviceNameColumnId = cursor.getColumnIndex(COLUMN_NAME);
138 int handlerColumnId = cursor.getColumnIndex(COLUMN_HANDLER);
139 int aoapColumnId = cursor.getColumnIndex(COLUMN_AOAP);
140 List<UsbDeviceSettings> results = new ArrayList<>(cursor.getCount());
141 while (cursor.moveToNext()) {
142 results.add(UsbDeviceSettings.constructSettings(
143 cursor.getString(serialNumberColumnId),
144 cursor.getInt(vidColumnId),
145 cursor.getInt(pidColumnId),
146 cursor.getString(deviceNameColumnId),
147 ComponentName.unflattenFromString(
148 cursor.getString(handlerColumnId)),
149 cursor.getInt(aoapColumnId) != 0));
150 }
151 return results;
152 }
153
154 /**
155 * Converts {@code UsbDeviceSettings} to {@code ContentValues}.
156 */
157 public ContentValues settingsToContentValues(UsbDeviceSettings settings) {
158 ContentValues contentValues = new ContentValues();
159 contentValues.put(COLUMN_SERIAL, settings.getSerialNumber());
160 contentValues.put(COLUMN_VID, settings.getVid());
161 contentValues.put(COLUMN_PID, settings.getPid());
162 contentValues.put(COLUMN_NAME, settings.getDeviceName());
163 contentValues.put(COLUMN_HANDLER, settings.getHandler().flattenToShortString());
164 contentValues.put(COLUMN_AOAP, settings.getAoap() ? 1 : 0);
165 contentValues.put(COLUMN_DEFAULT_HANDLER, settings.isDefaultHandler() ? 1 : 0);
166 return contentValues;
167 }
168
169
170 private static class UsbSettingsDbHelper extends SQLiteOpenHelper {
171 private static final int DATABASE_VERSION = 1;
172 private static final String DATABASE_NAME = "usb_devices.db";
173
174 UsbSettingsDbHelper(Context context) {
175 super(context, DATABASE_NAME, null, DATABASE_VERSION);
176 }
177
178 @Override
179 public void onCreate(SQLiteDatabase db) {
180 db.execSQL("CREATE TABLE " + TABLE_USB_SETTINGS + " ("
181 + COLUMN_SERIAL + " TEXT,"
182 + COLUMN_VID + " INTEGER,"
183 + COLUMN_PID + " INTEGER,"
184 + COLUMN_NAME + " TEXT, "
185 + COLUMN_HANDLER + " TEXT,"
186 + COLUMN_AOAP + " INTEGER,"
187 + COLUMN_DEFAULT_HANDLER + " INTEGER," + "PRIMARY KEY (" + COLUMN_SERIAL
188 + "))");
189 }
190
191 @Override
192 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
193 // Do nothing at this point. Not required for v1 database.
194 }
195 }
196}
diff --git a/car-usb-handler/src/android/car/usb/handler/UsbUtil.java b/car-usb-handler/src/android/car/usb/handler/UsbUtil.java
new file mode 100644
index 00000000..939fe3fa
--- /dev/null
+++ b/car-usb-handler/src/android/car/usb/handler/UsbUtil.java
@@ -0,0 +1,91 @@
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.hardware.usb.UsbDevice;
19import android.hardware.usb.UsbDeviceConnection;
20import android.hardware.usb.UsbManager;
21import android.text.TextUtils;
22import java.io.IOException;
23import java.util.ArrayList;
24import java.util.HashMap;
25import java.util.List;
26
27/**
28 * Util methods to work with USB devices.
29 */
30class UsbUtil {
31 public static List<UsbDevice> findAllPossibleAndroidDevices(UsbManager usbManager) {
32 HashMap<String, UsbDevice> devices = usbManager.getDeviceList();
33 ArrayList<UsbDevice> androidDevices = new ArrayList<>(devices.size());
34 for (UsbDevice device : devices.values()) {
35 UsbDeviceConnection connection = openConnection(usbManager, device);
36 if (AoapInterface.isSupported(connection)) {
37 androidDevices.add(device);
38 }
39 connection.close();
40 }
41 return androidDevices;
42 }
43
44 public static UsbDeviceConnection openConnection(UsbManager manager, UsbDevice device) {
45 manager.grantPermission(device);
46 return manager.openDevice(device);
47 }
48
49 public static void sendAoapAccessoryStart(UsbDeviceConnection connection, String manufacturer,
50 String model, String description, String version, String uri, String serial)
51 throws IOException {
52 AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_MANUFACTURER,
53 manufacturer);
54 AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_MODEL,
55 model);
56 AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_DESCRIPTION,
57 description);
58 AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_VERSION,
59 version);
60 AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_URI, uri);
61 AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_SERIAL,serial);
62 AoapInterface.sendAoapStart(connection);
63 }
64
65 public static boolean isTheSameDevice(UsbDevice l, UsbDevice r) {
66 if (TextUtils.equals(l.getManufacturerName(), r.getManufacturerName())
67 && TextUtils.equals(l.getProductName(), r.getProductName())
68 && TextUtils.equals(l.getSerialNumber(), r.getSerialNumber())) {
69 return true;
70 }
71 return false;
72 }
73
74 public static boolean isDevicesMatching(UsbDevice l, UsbDevice r) {
75 if (l.getVendorId() == r.getVendorId() && l.getProductId() == r.getProductId()
76 && TextUtils.equals(l.getSerialNumber(), r.getSerialNumber())) {
77 return true;
78 }
79 return false;
80 }
81
82 public static boolean isDeviceConnected(UsbManager usbManager, UsbDevice device) {
83 HashMap<String, UsbDevice> devices = usbManager.getDeviceList();
84 for (UsbDevice dev : devices.values()) {
85 if (isDevicesMatching(dev, device)) {
86 return true;
87 }
88 }
89 return false;
90 }
91}