blob: 45b0fcfe682199e230ff42e30ef4096c17508adf [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 Lockwoode7d511e2010-12-30 13:39:37 -0500216 // called from JNI in monitorUsbHostBus()
217 private void usbDeviceAdded(String deviceName, int vendorID, int productID,
218 int deviceClass, int deviceSubclass, int deviceProtocol,
219 /* array of quintuples containing id, class, subclass, protocol
220 and number of endpoints for each interface */
221 int[] interfaceValues,
222 /* array of quadruples containing address, attributes, max packet size
223 and interval for each endpoint */
224 int[] endpointValues) {
225
226 // ignore hubs
227 if (deviceClass == UsbConstants.USB_CLASS_HUB) {
228 return;
229 }
230
Mike Lockwooda8e3a892011-02-01 13:46:50 -0500231 if (isBlackListed(deviceName)) {
232 return;
233 }
234
Mike Lockwoode7d511e2010-12-30 13:39:37 -0500235 synchronized (mDevices) {
236 if (mDevices.get(deviceName) != null) {
237 Log.w(TAG, "device already on mDevices list: " + deviceName);
238 return;
239 }
240
241 int numInterfaces = interfaceValues.length / 5;
242 Parcelable[] interfaces = new UsbInterface[numInterfaces];
243 try {
244 // repackage interfaceValues as an array of UsbInterface
245 int intf, endp, ival = 0, eval = 0;
246 boolean hasGoodInterface = false;
247 for (intf = 0; intf < numInterfaces; intf++) {
248 int interfaceId = interfaceValues[ival++];
249 int interfaceClass = interfaceValues[ival++];
250 int interfaceSubclass = interfaceValues[ival++];
251 int interfaceProtocol = interfaceValues[ival++];
252 int numEndpoints = interfaceValues[ival++];
253
254 Parcelable[] endpoints = new UsbEndpoint[numEndpoints];
255 for (endp = 0; endp < numEndpoints; endp++) {
256 int address = endpointValues[eval++];
257 int attributes = endpointValues[eval++];
258 int maxPacketSize = endpointValues[eval++];
259 int interval = endpointValues[eval++];
260 endpoints[endp] = new UsbEndpoint(address, attributes,
261 maxPacketSize, interval);
262 }
263
264 if (interfaceClass != UsbConstants.USB_CLASS_HUB) {
265 hasGoodInterface = true;
266 }
267 interfaces[intf] = new UsbInterface(interfaceId, interfaceClass,
268 interfaceSubclass, interfaceProtocol, endpoints);
269 }
270
271 if (!hasGoodInterface) {
272 return;
273 }
274 } catch (Exception e) {
275 // beware of index out of bound exceptions, which might happen if
276 // a device does not set bNumEndpoints correctly
277 Log.e(TAG, "error parsing USB descriptors", e);
278 return;
279 }
280
281 UsbDevice device = new UsbDevice(deviceName, vendorID, productID,
282 deviceClass, deviceSubclass, deviceProtocol, interfaces);
283 mDevices.put(deviceName, device);
284
285 Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
286 intent.putExtra(UsbManager.EXTRA_DEVICE_NAME, deviceName);
287 intent.putExtra(UsbManager.EXTRA_VENDOR_ID, vendorID);
288 intent.putExtra(UsbManager.EXTRA_PRODUCT_ID, productID);
289 intent.putExtra(UsbManager.EXTRA_DEVICE_CLASS, deviceClass);
290 intent.putExtra(UsbManager.EXTRA_DEVICE_SUBCLASS, deviceSubclass);
291 intent.putExtra(UsbManager.EXTRA_DEVICE_PROTOCOL, deviceProtocol);
292 intent.putExtra(UsbManager.EXTRA_DEVICE, device);
293 Log.d(TAG, "usbDeviceAdded, sending " + intent);
294 mContext.sendBroadcast(intent);
295 }
296 }
297
298 // called from JNI in monitorUsbHostBus()
299 private void usbDeviceRemoved(String deviceName) {
300 synchronized (mDevices) {
301 UsbDevice device = mDevices.remove(deviceName);
302 if (device != null) {
303 Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_DETACHED);
304 intent.putExtra(UsbManager.EXTRA_DEVICE_NAME, deviceName);
305 Log.d(TAG, "usbDeviceRemoved, sending " + intent);
306 mContext.sendBroadcast(intent);
307 }
308 }
309 }
310
Mike Lockwoodda39f0e2010-07-27 18:44:30 -0400311 private void initHostSupport() {
Mike Lockwoode7d511e2010-12-30 13:39:37 -0500312 // Create a thread to call into native code to wait for USB host events.
313 // This thread will call us back on usbDeviceAdded and usbDeviceRemoved.
314 Runnable runnable = new Runnable() {
315 public void run() {
316 monitorUsbHostBus();
317 }
318 };
319 new Thread(null, runnable, "UsbService host thread").start();
Mike Lockwoodda39f0e2010-07-27 18:44:30 -0400320 }
321
Mike Lockwood24236072010-06-23 17:36:36 -0400322 void systemReady() {
323 synchronized (this) {
Mike Lockwoodda39f0e2010-07-27 18:44:30 -0400324 if (mContext.getResources().getBoolean(
325 com.android.internal.R.bool.config_hasUsbHostSupport)) {
326 // start monitoring for connected USB devices
327 initHostSupport();
328 }
329
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800330 update(false);
Mike Lockwood24236072010-06-23 17:36:36 -0400331 mSystemReady = true;
332 }
333 }
334
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800335 private final void update(boolean delayed) {
336 mHandler.removeMessages(MSG_UPDATE);
337 mHandler.sendEmptyMessageDelayed(MSG_UPDATE, delayed ? UPDATE_DELAY : 0);
Mike Lockwood24236072010-06-23 17:36:36 -0400338 }
339
Mike Lockwoode7d511e2010-12-30 13:39:37 -0500340 /* Returns a list of all currently attached USB devices */
341 public void getDeviceList(Bundle devices) {
342 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_USB, null);
343 synchronized (mDevices) {
344 for (String name : mDevices.keySet()) {
345 devices.putParcelable(name, mDevices.get(name));
346 }
347 }
348 }
349
350 public ParcelFileDescriptor openDevice(String deviceName) {
Mike Lockwooda8e3a892011-02-01 13:46:50 -0500351 if (isBlackListed(deviceName)) {
352 throw new SecurityException("USB device is on a restricted bus");
353 }
Mike Lockwoode7d511e2010-12-30 13:39:37 -0500354 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_USB, null);
355 return nativeOpenDevice(deviceName);
356 }
357
Mike Lockwood24236072010-06-23 17:36:36 -0400358 private final Handler mHandler = new Handler() {
Mike Lockwood709981e2010-06-28 09:58:58 -0400359 private void addEnabledFunctions(Intent intent) {
360 // include state of all USB functions in our extras
361 for (int i = 0; i < mEnabledFunctions.size(); i++) {
Mike Lockwood770126a2010-12-09 22:30:37 -0800362 intent.putExtra(mEnabledFunctions.get(i), UsbManager.USB_FUNCTION_ENABLED);
Mike Lockwood709981e2010-06-28 09:58:58 -0400363 }
364 for (int i = 0; i < mDisabledFunctions.size(); i++) {
Mike Lockwood770126a2010-12-09 22:30:37 -0800365 intent.putExtra(mDisabledFunctions.get(i), UsbManager.USB_FUNCTION_DISABLED);
Mike Lockwood709981e2010-06-28 09:58:58 -0400366 }
367 }
368
Mike Lockwood24236072010-06-23 17:36:36 -0400369 @Override
370 public void handleMessage(Message msg) {
371 switch (msg.what) {
372 case MSG_UPDATE:
373 synchronized (this) {
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800374 if (mConnected != mLastConnected || mConfiguration != mLastConfiguration) {
Mike Lockwood24236072010-06-23 17:36:36 -0400375
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800376 final ContentResolver cr = mContext.getContentResolver();
377 if (Settings.Secure.getInt(cr,
378 Settings.Secure.DEVICE_PROVISIONED, 0) == 0) {
379 Slog.i(TAG, "Device not provisioned, skipping USB broadcast");
380 return;
381 }
382
383 mLastConnected = mConnected;
384 mLastConfiguration = mConfiguration;
385
386 // send a sticky broadcast containing current USB state
387 Intent intent = new Intent(UsbManager.ACTION_USB_STATE);
388 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
389 intent.putExtra(UsbManager.USB_CONNECTED, mConnected != 0);
390 intent.putExtra(UsbManager.USB_CONFIGURATION, mConfiguration);
Mike Lockwood709981e2010-06-28 09:58:58 -0400391 addEnabledFunctions(intent);
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800392 mContext.sendStickyBroadcast(intent);
Mike Lockwood24236072010-06-23 17:36:36 -0400393 }
Mike Lockwood24236072010-06-23 17:36:36 -0400394 }
395 break;
396 }
397 }
398 };
Mike Lockwoode7d511e2010-12-30 13:39:37 -0500399
400 private native void monitorUsbHostBus();
401 private native ParcelFileDescriptor nativeOpenDevice(String deviceName);
Mike Lockwood24236072010-06-23 17:36:36 -0400402}