blob: 460fd4d5eb7cff712dca2c94163078cd0d6a282d [file] [log] [blame]
Mike Lockwood24236072010-06-23 17:36:36 -04001/*
2 * Copyright (C) 2010 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
17package com.android.server;
18
19import android.content.ContentResolver;
20import android.content.Context;
21import android.content.Intent;
Mike Lockwoode7d511e2010-12-30 13:39:37 -050022import android.hardware.IUsbManager;
23import android.hardware.UsbConstants;
24import android.hardware.UsbDevice;
25import android.hardware.UsbEndpoint;
26import android.hardware.UsbInterface;
Mike Lockwood770126a2010-12-09 22:30:37 -080027import android.hardware.UsbManager;
Mike Lockwood24236072010-06-23 17:36:36 -040028import android.net.Uri;
Mike Lockwoode7d511e2010-12-30 13:39:37 -050029import android.os.Bundle;
Mike Lockwood24236072010-06-23 17:36:36 -040030import android.os.Handler;
31import android.os.Message;
Mike Lockwoode7d511e2010-12-30 13:39:37 -050032import android.os.Parcelable;
33import android.os.ParcelFileDescriptor;
Mike Lockwood24236072010-06-23 17:36:36 -040034import android.os.UEventObserver;
35import android.provider.Settings;
36import android.util.Log;
37import android.util.Slog;
38
39import java.io.File;
40import java.io.FileNotFoundException;
41import java.io.FileReader;
42import java.util.ArrayList;
Mike Lockwoode7d511e2010-12-30 13:39:37 -050043import java.util.HashMap;
Mike Lockwood24236072010-06-23 17:36:36 -040044
45/**
Mike Lockwood770126a2010-12-09 22:30:37 -080046 * <p>UsbService monitors for changes to USB state.
Mike Lockwood24236072010-06-23 17:36:36 -040047 */
Mike Lockwoode7d511e2010-12-30 13:39:37 -050048class UsbService extends IUsbManager.Stub {
Mike Lockwood770126a2010-12-09 22:30:37 -080049 private static final String TAG = UsbService.class.getSimpleName();
Mike Lockwood24236072010-06-23 17:36:36 -040050 private static final boolean LOG = false;
51
Mike Lockwoodb92df0f2010-12-10 16:19:32 -080052 private static final String USB_CONNECTED_MATCH =
53 "DEVPATH=/devices/virtual/switch/usb_connected";
54 private static final String USB_CONFIGURATION_MATCH =
55 "DEVPATH=/devices/virtual/switch/usb_configuration";
56 private static final String USB_FUNCTIONS_MATCH =
57 "DEVPATH=/devices/virtual/usb_composite/";
58 private static final String USB_CONNECTED_PATH =
59 "/sys/class/switch/usb_connected/state";
60 private static final String USB_CONFIGURATION_PATH =
61 "/sys/class/switch/usb_configuration/state";
62 private static final String USB_COMPOSITE_CLASS_PATH =
63 "/sys/class/usb_composite";
Mike Lockwood24236072010-06-23 17:36:36 -040064
65 private static final int MSG_UPDATE = 0;
66
Mike Lockwoodb92df0f2010-12-10 16:19:32 -080067 // Delay for debouncing USB disconnects.
68 // We often get rapid connect/disconnect events when enabling USB functions,
69 // which need debouncing.
70 private static final int UPDATE_DELAY = 1000;
71
72 // current connected and configuration state
73 private int mConnected;
74 private int mConfiguration;
75
76 // last broadcasted connected and configuration state
77 private int mLastConnected = -1;
78 private int mLastConfiguration = -1;
Mike Lockwood24236072010-06-23 17:36:36 -040079
Mike Lockwoode7d511e2010-12-30 13:39:37 -050080 // lists of enabled and disabled USB functions (for USB device mode)
Mike Lockwood24236072010-06-23 17:36:36 -040081 private final ArrayList<String> mEnabledFunctions = new ArrayList<String>();
82 private final ArrayList<String> mDisabledFunctions = new ArrayList<String>();
83
Mike Lockwoode7d511e2010-12-30 13:39:37 -050084 private final HashMap<String,UsbDevice> mDevices = new HashMap<String,UsbDevice>();
85
Mike Lockwooda8e3a892011-02-01 13:46:50 -050086 // USB busses to exclude from USB host support
87 private final String[] mHostBlacklist;
88
Mike Lockwood24236072010-06-23 17:36:36 -040089 private boolean mSystemReady;
90
91 private final Context mContext;
92
Mike Lockwood770126a2010-12-09 22:30:37 -080093 private final UEventObserver mUEventObserver = new UEventObserver() {
94 @Override
95 public void onUEvent(UEventObserver.UEvent event) {
96 if (Log.isLoggable(TAG, Log.VERBOSE)) {
97 Slog.v(TAG, "USB UEVENT: " + event.toString());
98 }
Mike Lockwood24236072010-06-23 17:36:36 -040099
Mike Lockwood770126a2010-12-09 22:30:37 -0800100 synchronized (this) {
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800101 String name = event.get("SWITCH_NAME");
102 String state = event.get("SWITCH_STATE");
103 if (name != null && state != null) {
Mike Lockwood770126a2010-12-09 22:30:37 -0800104 try {
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800105 int intState = Integer.parseInt(state);
106 if ("usb_connected".equals(name)) {
107 mConnected = intState;
Mike Lockwood770126a2010-12-09 22:30:37 -0800108 // trigger an Intent broadcast
109 if (mSystemReady) {
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800110 // debounce disconnects
111 update(mConnected == 0);
112 }
113 } else if ("usb_configuration".equals(name)) {
114 mConfiguration = intState;
115 // trigger an Intent broadcast
116 if (mSystemReady) {
117 update(mConnected == 0);
Mike Lockwood770126a2010-12-09 22:30:37 -0800118 }
Mike Lockwood24236072010-06-23 17:36:36 -0400119 }
Mike Lockwood770126a2010-12-09 22:30:37 -0800120 } catch (NumberFormatException e) {
121 Slog.e(TAG, "Could not parse switch state from event " + event);
Mike Lockwood24236072010-06-23 17:36:36 -0400122 }
Mike Lockwood770126a2010-12-09 22:30:37 -0800123 } else {
124 String function = event.get("FUNCTION");
125 String enabledStr = event.get("ENABLED");
126 if (function != null && enabledStr != null) {
127 // Note: we do not broadcast a change when a function is enabled or disabled.
128 // We just record the state change for the next broadcast.
129 boolean enabled = "1".equals(enabledStr);
130 if (enabled) {
131 if (!mEnabledFunctions.contains(function)) {
132 mEnabledFunctions.add(function);
133 }
134 mDisabledFunctions.remove(function);
135 } else {
136 if (!mDisabledFunctions.contains(function)) {
137 mDisabledFunctions.add(function);
138 }
139 mEnabledFunctions.remove(function);
Mike Lockwood24236072010-06-23 17:36:36 -0400140 }
Mike Lockwood24236072010-06-23 17:36:36 -0400141 }
142 }
143 }
144 }
Mike Lockwood770126a2010-12-09 22:30:37 -0800145 };
146
147 public UsbService(Context context) {
148 mContext = context;
Mike Lockwooda8e3a892011-02-01 13:46:50 -0500149 mHostBlacklist = context.getResources().getStringArray(
150 com.android.internal.R.array.config_usbHostBlacklist);
151
Mike Lockwood770126a2010-12-09 22:30:37 -0800152 init(); // set initial status
153
David 'Digit' Turner49db8532011-01-17 00:19:37 +0100154 if (mConfiguration >= 0) {
155 mUEventObserver.startObserving(USB_CONNECTED_MATCH);
156 mUEventObserver.startObserving(USB_CONFIGURATION_MATCH);
157 mUEventObserver.startObserving(USB_FUNCTIONS_MATCH);
158 }
Mike Lockwood24236072010-06-23 17:36:36 -0400159 }
Mike Lockwood770126a2010-12-09 22:30:37 -0800160
Mike Lockwood24236072010-06-23 17:36:36 -0400161 private final void init() {
162 char[] buffer = new char[1024];
163
David 'Digit' Turner49db8532011-01-17 00:19:37 +0100164 mConfiguration = -1;
Mike Lockwood24236072010-06-23 17:36:36 -0400165 try {
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800166 FileReader file = new FileReader(USB_CONNECTED_PATH);
Mike Lockwood24236072010-06-23 17:36:36 -0400167 int len = file.read(buffer, 0, 1024);
Brian Carlstromfd9ddd12010-11-04 11:24:58 -0700168 file.close();
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800169 mConnected = Integer.valueOf((new String(buffer, 0, len)).trim());
170
171 file = new FileReader(USB_CONFIGURATION_PATH);
172 len = file.read(buffer, 0, 1024);
173 file.close();
174 mConfiguration = Integer.valueOf((new String(buffer, 0, len)).trim());
Mike Lockwood24236072010-06-23 17:36:36 -0400175
176 } catch (FileNotFoundException e) {
David 'Digit' Turner49db8532011-01-17 00:19:37 +0100177 Slog.i(TAG, "This kernel does not have USB configuration switch support");
Mike Lockwood24236072010-06-23 17:36:36 -0400178 } catch (Exception e) {
179 Slog.e(TAG, "" , e);
180 }
David 'Digit' Turner49db8532011-01-17 00:19:37 +0100181 if (mConfiguration < 0)
182 return;
Mike Lockwood24236072010-06-23 17:36:36 -0400183
184 try {
185 File[] files = new File(USB_COMPOSITE_CLASS_PATH).listFiles();
186 for (int i = 0; i < files.length; i++) {
187 File file = new File(files[i], "enable");
188 FileReader reader = new FileReader(file);
189 int len = reader.read(buffer, 0, 1024);
Brian Carlstromfd9ddd12010-11-04 11:24:58 -0700190 reader.close();
Mike Lockwood24236072010-06-23 17:36:36 -0400191 int value = Integer.valueOf((new String(buffer, 0, len)).trim());
192 String functionName = files[i].getName();
193 if (value == 1) {
194 mEnabledFunctions.add(functionName);
195 } else {
196 mDisabledFunctions.add(functionName);
197 }
198 }
199 } catch (FileNotFoundException e) {
200 Slog.w(TAG, "This kernel does not have USB composite class support");
201 } catch (Exception e) {
202 Slog.e(TAG, "" , e);
203 }
204 }
205
Mike Lockwooda8e3a892011-02-01 13:46:50 -0500206 private boolean isBlackListed(String deviceName) {
207 int count = mHostBlacklist.length;
208 for (int i = 0; i < count; i++) {
209 if (deviceName.startsWith(mHostBlacklist[i])) {
210 return true;
211 }
212 }
213 return false;
214 }
215
Mike Lockwood8b682ad2011-02-01 15:53:11 -0500216 private boolean isBlackListed(int clazz, int subClass, int protocol) {
217 // blacklist hubs
218 if (clazz == UsbConstants.USB_CLASS_HUB) return true;
219
220 // blacklist HID boot devices (mouse and keyboard)
221 if (clazz == UsbConstants.USB_CLASS_HID &&
222 subClass == UsbConstants.USB_INTERFACE_SUBCLASS_BOOT) {
223 return true;
224 }
225
226 return false;
227 }
228
Mike Lockwoode7d511e2010-12-30 13:39:37 -0500229 // called from JNI in monitorUsbHostBus()
230 private void usbDeviceAdded(String deviceName, int vendorID, int productID,
231 int deviceClass, int deviceSubclass, int deviceProtocol,
232 /* array of quintuples containing id, class, subclass, protocol
233 and number of endpoints for each interface */
234 int[] interfaceValues,
235 /* array of quadruples containing address, attributes, max packet size
236 and interval for each endpoint */
237 int[] endpointValues) {
238
Mike Lockwood8b682ad2011-02-01 15:53:11 -0500239 if (isBlackListed(deviceName) ||
240 isBlackListed(deviceClass, deviceSubclass, deviceProtocol)) {
Mike Lockwooda8e3a892011-02-01 13:46:50 -0500241 return;
242 }
243
Mike Lockwoode7d511e2010-12-30 13:39:37 -0500244 synchronized (mDevices) {
245 if (mDevices.get(deviceName) != null) {
246 Log.w(TAG, "device already on mDevices list: " + deviceName);
247 return;
248 }
249
250 int numInterfaces = interfaceValues.length / 5;
251 Parcelable[] interfaces = new UsbInterface[numInterfaces];
252 try {
253 // repackage interfaceValues as an array of UsbInterface
254 int intf, endp, ival = 0, eval = 0;
Mike Lockwoode7d511e2010-12-30 13:39:37 -0500255 for (intf = 0; intf < numInterfaces; intf++) {
256 int interfaceId = interfaceValues[ival++];
257 int interfaceClass = interfaceValues[ival++];
258 int interfaceSubclass = interfaceValues[ival++];
259 int interfaceProtocol = interfaceValues[ival++];
260 int numEndpoints = interfaceValues[ival++];
261
262 Parcelable[] endpoints = new UsbEndpoint[numEndpoints];
263 for (endp = 0; endp < numEndpoints; endp++) {
264 int address = endpointValues[eval++];
265 int attributes = endpointValues[eval++];
266 int maxPacketSize = endpointValues[eval++];
267 int interval = endpointValues[eval++];
268 endpoints[endp] = new UsbEndpoint(address, attributes,
269 maxPacketSize, interval);
270 }
271
Mike Lockwood8b682ad2011-02-01 15:53:11 -0500272 // don't allow if any interfaces are blacklisted
273 if (isBlackListed(interfaceClass, interfaceSubclass, interfaceProtocol)) {
274 return;
Mike Lockwoode7d511e2010-12-30 13:39:37 -0500275 }
276 interfaces[intf] = new UsbInterface(interfaceId, interfaceClass,
277 interfaceSubclass, interfaceProtocol, endpoints);
278 }
Mike Lockwoode7d511e2010-12-30 13:39:37 -0500279 } catch (Exception e) {
280 // beware of index out of bound exceptions, which might happen if
281 // a device does not set bNumEndpoints correctly
282 Log.e(TAG, "error parsing USB descriptors", e);
283 return;
284 }
285
286 UsbDevice device = new UsbDevice(deviceName, vendorID, productID,
287 deviceClass, deviceSubclass, deviceProtocol, interfaces);
288 mDevices.put(deviceName, device);
289
290 Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
291 intent.putExtra(UsbManager.EXTRA_DEVICE_NAME, deviceName);
292 intent.putExtra(UsbManager.EXTRA_VENDOR_ID, vendorID);
293 intent.putExtra(UsbManager.EXTRA_PRODUCT_ID, productID);
294 intent.putExtra(UsbManager.EXTRA_DEVICE_CLASS, deviceClass);
295 intent.putExtra(UsbManager.EXTRA_DEVICE_SUBCLASS, deviceSubclass);
296 intent.putExtra(UsbManager.EXTRA_DEVICE_PROTOCOL, deviceProtocol);
297 intent.putExtra(UsbManager.EXTRA_DEVICE, device);
298 Log.d(TAG, "usbDeviceAdded, sending " + intent);
299 mContext.sendBroadcast(intent);
300 }
301 }
302
303 // called from JNI in monitorUsbHostBus()
304 private void usbDeviceRemoved(String deviceName) {
305 synchronized (mDevices) {
306 UsbDevice device = mDevices.remove(deviceName);
307 if (device != null) {
308 Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_DETACHED);
309 intent.putExtra(UsbManager.EXTRA_DEVICE_NAME, deviceName);
310 Log.d(TAG, "usbDeviceRemoved, sending " + intent);
311 mContext.sendBroadcast(intent);
312 }
313 }
314 }
315
Mike Lockwoodda39f0e2010-07-27 18:44:30 -0400316 private void initHostSupport() {
Mike Lockwoode7d511e2010-12-30 13:39:37 -0500317 // Create a thread to call into native code to wait for USB host events.
318 // This thread will call us back on usbDeviceAdded and usbDeviceRemoved.
319 Runnable runnable = new Runnable() {
320 public void run() {
321 monitorUsbHostBus();
322 }
323 };
324 new Thread(null, runnable, "UsbService host thread").start();
Mike Lockwoodda39f0e2010-07-27 18:44:30 -0400325 }
326
Mike Lockwood24236072010-06-23 17:36:36 -0400327 void systemReady() {
328 synchronized (this) {
Mike Lockwoodda39f0e2010-07-27 18:44:30 -0400329 if (mContext.getResources().getBoolean(
330 com.android.internal.R.bool.config_hasUsbHostSupport)) {
331 // start monitoring for connected USB devices
332 initHostSupport();
333 }
334
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800335 update(false);
Mike Lockwood24236072010-06-23 17:36:36 -0400336 mSystemReady = true;
337 }
338 }
339
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800340 private final void update(boolean delayed) {
341 mHandler.removeMessages(MSG_UPDATE);
342 mHandler.sendEmptyMessageDelayed(MSG_UPDATE, delayed ? UPDATE_DELAY : 0);
Mike Lockwood24236072010-06-23 17:36:36 -0400343 }
344
Mike Lockwoode7d511e2010-12-30 13:39:37 -0500345 /* Returns a list of all currently attached USB devices */
346 public void getDeviceList(Bundle devices) {
347 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_USB, null);
348 synchronized (mDevices) {
349 for (String name : mDevices.keySet()) {
350 devices.putParcelable(name, mDevices.get(name));
351 }
352 }
353 }
354
355 public ParcelFileDescriptor openDevice(String deviceName) {
Mike Lockwooda8e3a892011-02-01 13:46:50 -0500356 if (isBlackListed(deviceName)) {
357 throw new SecurityException("USB device is on a restricted bus");
358 }
Mike Lockwoode7d511e2010-12-30 13:39:37 -0500359 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_USB, null);
Mike Lockwood8b682ad2011-02-01 15:53:11 -0500360 if (mDevices.get(deviceName) == null) {
361 // if it is not in mDevices, it either does not exist or is blacklisted
362 throw new IllegalArgumentException("device " + deviceName + " does not exist or is restricted");
363 }
Mike Lockwoode7d511e2010-12-30 13:39:37 -0500364 return nativeOpenDevice(deviceName);
365 }
366
Mike Lockwood24236072010-06-23 17:36:36 -0400367 private final Handler mHandler = new Handler() {
Mike Lockwood709981e2010-06-28 09:58:58 -0400368 private void addEnabledFunctions(Intent intent) {
369 // include state of all USB functions in our extras
370 for (int i = 0; i < mEnabledFunctions.size(); i++) {
Mike Lockwood770126a2010-12-09 22:30:37 -0800371 intent.putExtra(mEnabledFunctions.get(i), UsbManager.USB_FUNCTION_ENABLED);
Mike Lockwood709981e2010-06-28 09:58:58 -0400372 }
373 for (int i = 0; i < mDisabledFunctions.size(); i++) {
Mike Lockwood770126a2010-12-09 22:30:37 -0800374 intent.putExtra(mDisabledFunctions.get(i), UsbManager.USB_FUNCTION_DISABLED);
Mike Lockwood709981e2010-06-28 09:58:58 -0400375 }
376 }
377
Mike Lockwood24236072010-06-23 17:36:36 -0400378 @Override
379 public void handleMessage(Message msg) {
380 switch (msg.what) {
381 case MSG_UPDATE:
382 synchronized (this) {
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800383 if (mConnected != mLastConnected || mConfiguration != mLastConfiguration) {
Mike Lockwood24236072010-06-23 17:36:36 -0400384
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800385 final ContentResolver cr = mContext.getContentResolver();
386 if (Settings.Secure.getInt(cr,
387 Settings.Secure.DEVICE_PROVISIONED, 0) == 0) {
388 Slog.i(TAG, "Device not provisioned, skipping USB broadcast");
389 return;
390 }
391
392 mLastConnected = mConnected;
393 mLastConfiguration = mConfiguration;
394
395 // send a sticky broadcast containing current USB state
396 Intent intent = new Intent(UsbManager.ACTION_USB_STATE);
397 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
398 intent.putExtra(UsbManager.USB_CONNECTED, mConnected != 0);
399 intent.putExtra(UsbManager.USB_CONFIGURATION, mConfiguration);
Mike Lockwood709981e2010-06-28 09:58:58 -0400400 addEnabledFunctions(intent);
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800401 mContext.sendStickyBroadcast(intent);
Mike Lockwood24236072010-06-23 17:36:36 -0400402 }
Mike Lockwood24236072010-06-23 17:36:36 -0400403 }
404 break;
405 }
406 }
407 };
Mike Lockwoode7d511e2010-12-30 13:39:37 -0500408
409 private native void monitorUsbHostBus();
410 private native ParcelFileDescriptor nativeOpenDevice(String deviceName);
Mike Lockwood24236072010-06-23 17:36:36 -0400411}