blob: 5c03fb2a76e4bcccf6d4299579069ddad8afc753 [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 Lockwood24236072010-06-23 17:36:36 -040086 private boolean mSystemReady;
87
88 private final Context mContext;
89
Mike Lockwood770126a2010-12-09 22:30:37 -080090 private final UEventObserver mUEventObserver = new UEventObserver() {
91 @Override
92 public void onUEvent(UEventObserver.UEvent event) {
93 if (Log.isLoggable(TAG, Log.VERBOSE)) {
94 Slog.v(TAG, "USB UEVENT: " + event.toString());
95 }
Mike Lockwood24236072010-06-23 17:36:36 -040096
Mike Lockwood770126a2010-12-09 22:30:37 -080097 synchronized (this) {
Mike Lockwoodb92df0f2010-12-10 16:19:32 -080098 String name = event.get("SWITCH_NAME");
99 String state = event.get("SWITCH_STATE");
100 if (name != null && state != null) {
Mike Lockwood770126a2010-12-09 22:30:37 -0800101 try {
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800102 int intState = Integer.parseInt(state);
103 if ("usb_connected".equals(name)) {
104 mConnected = intState;
Mike Lockwood770126a2010-12-09 22:30:37 -0800105 // trigger an Intent broadcast
106 if (mSystemReady) {
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800107 // debounce disconnects
108 update(mConnected == 0);
109 }
110 } else if ("usb_configuration".equals(name)) {
111 mConfiguration = intState;
112 // trigger an Intent broadcast
113 if (mSystemReady) {
114 update(mConnected == 0);
Mike Lockwood770126a2010-12-09 22:30:37 -0800115 }
Mike Lockwood24236072010-06-23 17:36:36 -0400116 }
Mike Lockwood770126a2010-12-09 22:30:37 -0800117 } catch (NumberFormatException e) {
118 Slog.e(TAG, "Could not parse switch state from event " + event);
Mike Lockwood24236072010-06-23 17:36:36 -0400119 }
Mike Lockwood770126a2010-12-09 22:30:37 -0800120 } else {
121 String function = event.get("FUNCTION");
122 String enabledStr = event.get("ENABLED");
123 if (function != null && enabledStr != null) {
124 // Note: we do not broadcast a change when a function is enabled or disabled.
125 // We just record the state change for the next broadcast.
126 boolean enabled = "1".equals(enabledStr);
127 if (enabled) {
128 if (!mEnabledFunctions.contains(function)) {
129 mEnabledFunctions.add(function);
130 }
131 mDisabledFunctions.remove(function);
132 } else {
133 if (!mDisabledFunctions.contains(function)) {
134 mDisabledFunctions.add(function);
135 }
136 mEnabledFunctions.remove(function);
Mike Lockwood24236072010-06-23 17:36:36 -0400137 }
Mike Lockwood24236072010-06-23 17:36:36 -0400138 }
139 }
140 }
141 }
Mike Lockwood770126a2010-12-09 22:30:37 -0800142 };
143
144 public UsbService(Context context) {
145 mContext = context;
146 init(); // set initial status
147
David 'Digit' Turner49db8532011-01-17 00:19:37 +0100148 if (mConfiguration >= 0) {
149 mUEventObserver.startObserving(USB_CONNECTED_MATCH);
150 mUEventObserver.startObserving(USB_CONFIGURATION_MATCH);
151 mUEventObserver.startObserving(USB_FUNCTIONS_MATCH);
152 }
Mike Lockwood24236072010-06-23 17:36:36 -0400153 }
Mike Lockwood770126a2010-12-09 22:30:37 -0800154
Mike Lockwood24236072010-06-23 17:36:36 -0400155 private final void init() {
156 char[] buffer = new char[1024];
157
David 'Digit' Turner49db8532011-01-17 00:19:37 +0100158 mConfiguration = -1;
Mike Lockwood24236072010-06-23 17:36:36 -0400159 try {
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800160 FileReader file = new FileReader(USB_CONNECTED_PATH);
Mike Lockwood24236072010-06-23 17:36:36 -0400161 int len = file.read(buffer, 0, 1024);
Brian Carlstromfd9ddd12010-11-04 11:24:58 -0700162 file.close();
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800163 mConnected = Integer.valueOf((new String(buffer, 0, len)).trim());
164
165 file = new FileReader(USB_CONFIGURATION_PATH);
166 len = file.read(buffer, 0, 1024);
167 file.close();
168 mConfiguration = Integer.valueOf((new String(buffer, 0, len)).trim());
Mike Lockwood24236072010-06-23 17:36:36 -0400169
170 } catch (FileNotFoundException e) {
David 'Digit' Turner49db8532011-01-17 00:19:37 +0100171 Slog.i(TAG, "This kernel does not have USB configuration switch support");
Mike Lockwood24236072010-06-23 17:36:36 -0400172 } catch (Exception e) {
173 Slog.e(TAG, "" , e);
174 }
David 'Digit' Turner49db8532011-01-17 00:19:37 +0100175 if (mConfiguration < 0)
176 return;
Mike Lockwood24236072010-06-23 17:36:36 -0400177
178 try {
179 File[] files = new File(USB_COMPOSITE_CLASS_PATH).listFiles();
180 for (int i = 0; i < files.length; i++) {
181 File file = new File(files[i], "enable");
182 FileReader reader = new FileReader(file);
183 int len = reader.read(buffer, 0, 1024);
Brian Carlstromfd9ddd12010-11-04 11:24:58 -0700184 reader.close();
Mike Lockwood24236072010-06-23 17:36:36 -0400185 int value = Integer.valueOf((new String(buffer, 0, len)).trim());
186 String functionName = files[i].getName();
187 if (value == 1) {
188 mEnabledFunctions.add(functionName);
189 } else {
190 mDisabledFunctions.add(functionName);
191 }
192 }
193 } catch (FileNotFoundException e) {
194 Slog.w(TAG, "This kernel does not have USB composite class support");
195 } catch (Exception e) {
196 Slog.e(TAG, "" , e);
197 }
198 }
199
Mike Lockwoode7d511e2010-12-30 13:39:37 -0500200 // called from JNI in monitorUsbHostBus()
201 private void usbDeviceAdded(String deviceName, int vendorID, int productID,
202 int deviceClass, int deviceSubclass, int deviceProtocol,
203 /* array of quintuples containing id, class, subclass, protocol
204 and number of endpoints for each interface */
205 int[] interfaceValues,
206 /* array of quadruples containing address, attributes, max packet size
207 and interval for each endpoint */
208 int[] endpointValues) {
209
210 // ignore hubs
211 if (deviceClass == UsbConstants.USB_CLASS_HUB) {
212 return;
213 }
214
215 synchronized (mDevices) {
216 if (mDevices.get(deviceName) != null) {
217 Log.w(TAG, "device already on mDevices list: " + deviceName);
218 return;
219 }
220
221 int numInterfaces = interfaceValues.length / 5;
222 Parcelable[] interfaces = new UsbInterface[numInterfaces];
223 try {
224 // repackage interfaceValues as an array of UsbInterface
225 int intf, endp, ival = 0, eval = 0;
226 boolean hasGoodInterface = false;
227 for (intf = 0; intf < numInterfaces; intf++) {
228 int interfaceId = interfaceValues[ival++];
229 int interfaceClass = interfaceValues[ival++];
230 int interfaceSubclass = interfaceValues[ival++];
231 int interfaceProtocol = interfaceValues[ival++];
232 int numEndpoints = interfaceValues[ival++];
233
234 Parcelable[] endpoints = new UsbEndpoint[numEndpoints];
235 for (endp = 0; endp < numEndpoints; endp++) {
236 int address = endpointValues[eval++];
237 int attributes = endpointValues[eval++];
238 int maxPacketSize = endpointValues[eval++];
239 int interval = endpointValues[eval++];
240 endpoints[endp] = new UsbEndpoint(address, attributes,
241 maxPacketSize, interval);
242 }
243
244 if (interfaceClass != UsbConstants.USB_CLASS_HUB) {
245 hasGoodInterface = true;
246 }
247 interfaces[intf] = new UsbInterface(interfaceId, interfaceClass,
248 interfaceSubclass, interfaceProtocol, endpoints);
249 }
250
251 if (!hasGoodInterface) {
252 return;
253 }
254 } catch (Exception e) {
255 // beware of index out of bound exceptions, which might happen if
256 // a device does not set bNumEndpoints correctly
257 Log.e(TAG, "error parsing USB descriptors", e);
258 return;
259 }
260
261 UsbDevice device = new UsbDevice(deviceName, vendorID, productID,
262 deviceClass, deviceSubclass, deviceProtocol, interfaces);
263 mDevices.put(deviceName, device);
264
265 Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
266 intent.putExtra(UsbManager.EXTRA_DEVICE_NAME, deviceName);
267 intent.putExtra(UsbManager.EXTRA_VENDOR_ID, vendorID);
268 intent.putExtra(UsbManager.EXTRA_PRODUCT_ID, productID);
269 intent.putExtra(UsbManager.EXTRA_DEVICE_CLASS, deviceClass);
270 intent.putExtra(UsbManager.EXTRA_DEVICE_SUBCLASS, deviceSubclass);
271 intent.putExtra(UsbManager.EXTRA_DEVICE_PROTOCOL, deviceProtocol);
272 intent.putExtra(UsbManager.EXTRA_DEVICE, device);
273 Log.d(TAG, "usbDeviceAdded, sending " + intent);
274 mContext.sendBroadcast(intent);
275 }
276 }
277
278 // called from JNI in monitorUsbHostBus()
279 private void usbDeviceRemoved(String deviceName) {
280 synchronized (mDevices) {
281 UsbDevice device = mDevices.remove(deviceName);
282 if (device != null) {
283 Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_DETACHED);
284 intent.putExtra(UsbManager.EXTRA_DEVICE_NAME, deviceName);
285 Log.d(TAG, "usbDeviceRemoved, sending " + intent);
286 mContext.sendBroadcast(intent);
287 }
288 }
289 }
290
Mike Lockwoodda39f0e2010-07-27 18:44:30 -0400291 private void initHostSupport() {
Mike Lockwoode7d511e2010-12-30 13:39:37 -0500292 // Create a thread to call into native code to wait for USB host events.
293 // This thread will call us back on usbDeviceAdded and usbDeviceRemoved.
294 Runnable runnable = new Runnable() {
295 public void run() {
296 monitorUsbHostBus();
297 }
298 };
299 new Thread(null, runnable, "UsbService host thread").start();
Mike Lockwoodda39f0e2010-07-27 18:44:30 -0400300 }
301
Mike Lockwood24236072010-06-23 17:36:36 -0400302 void systemReady() {
303 synchronized (this) {
Mike Lockwoodda39f0e2010-07-27 18:44:30 -0400304 if (mContext.getResources().getBoolean(
305 com.android.internal.R.bool.config_hasUsbHostSupport)) {
306 // start monitoring for connected USB devices
307 initHostSupport();
308 }
309
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800310 update(false);
Mike Lockwood24236072010-06-23 17:36:36 -0400311 mSystemReady = true;
312 }
313 }
314
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800315 private final void update(boolean delayed) {
316 mHandler.removeMessages(MSG_UPDATE);
317 mHandler.sendEmptyMessageDelayed(MSG_UPDATE, delayed ? UPDATE_DELAY : 0);
Mike Lockwood24236072010-06-23 17:36:36 -0400318 }
319
Mike Lockwoode7d511e2010-12-30 13:39:37 -0500320 /* Returns a list of all currently attached USB devices */
321 public void getDeviceList(Bundle devices) {
322 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_USB, null);
323 synchronized (mDevices) {
324 for (String name : mDevices.keySet()) {
325 devices.putParcelable(name, mDevices.get(name));
326 }
327 }
328 }
329
330 public ParcelFileDescriptor openDevice(String deviceName) {
331 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_USB, null);
332 return nativeOpenDevice(deviceName);
333 }
334
Mike Lockwood24236072010-06-23 17:36:36 -0400335 private final Handler mHandler = new Handler() {
Mike Lockwood709981e2010-06-28 09:58:58 -0400336 private void addEnabledFunctions(Intent intent) {
337 // include state of all USB functions in our extras
338 for (int i = 0; i < mEnabledFunctions.size(); i++) {
Mike Lockwood770126a2010-12-09 22:30:37 -0800339 intent.putExtra(mEnabledFunctions.get(i), UsbManager.USB_FUNCTION_ENABLED);
Mike Lockwood709981e2010-06-28 09:58:58 -0400340 }
341 for (int i = 0; i < mDisabledFunctions.size(); i++) {
Mike Lockwood770126a2010-12-09 22:30:37 -0800342 intent.putExtra(mDisabledFunctions.get(i), UsbManager.USB_FUNCTION_DISABLED);
Mike Lockwood709981e2010-06-28 09:58:58 -0400343 }
344 }
345
Mike Lockwood24236072010-06-23 17:36:36 -0400346 @Override
347 public void handleMessage(Message msg) {
348 switch (msg.what) {
349 case MSG_UPDATE:
350 synchronized (this) {
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800351 if (mConnected != mLastConnected || mConfiguration != mLastConfiguration) {
Mike Lockwood24236072010-06-23 17:36:36 -0400352
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800353 final ContentResolver cr = mContext.getContentResolver();
354 if (Settings.Secure.getInt(cr,
355 Settings.Secure.DEVICE_PROVISIONED, 0) == 0) {
356 Slog.i(TAG, "Device not provisioned, skipping USB broadcast");
357 return;
358 }
359
360 mLastConnected = mConnected;
361 mLastConfiguration = mConfiguration;
362
363 // send a sticky broadcast containing current USB state
364 Intent intent = new Intent(UsbManager.ACTION_USB_STATE);
365 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
366 intent.putExtra(UsbManager.USB_CONNECTED, mConnected != 0);
367 intent.putExtra(UsbManager.USB_CONFIGURATION, mConfiguration);
Mike Lockwood709981e2010-06-28 09:58:58 -0400368 addEnabledFunctions(intent);
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800369 mContext.sendStickyBroadcast(intent);
Mike Lockwood24236072010-06-23 17:36:36 -0400370 }
Mike Lockwood24236072010-06-23 17:36:36 -0400371 }
372 break;
373 }
374 }
375 };
Mike Lockwoode7d511e2010-12-30 13:39:37 -0500376
377 private native void monitorUsbHostBus();
378 private native ParcelFileDescriptor nativeOpenDevice(String deviceName);
Mike Lockwood24236072010-06-23 17:36:36 -0400379}