blob: a2bf75db99353e92a84c7c8743ec44835b44cfac [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 Lockwood770126a2010-12-09 22:30:37 -080022import android.hardware.UsbManager;
Mike Lockwood24236072010-06-23 17:36:36 -040023import android.net.Uri;
24import android.os.Handler;
25import android.os.Message;
26import android.os.UEventObserver;
Mike Lockwooda3156052010-11-20 12:28:27 -050027import android.provider.Ptp;
Mike Lockwood24236072010-06-23 17:36:36 -040028import android.provider.Settings;
29import android.util.Log;
30import android.util.Slog;
31
32import java.io.File;
33import java.io.FileNotFoundException;
34import java.io.FileReader;
35import java.util.ArrayList;
36
37/**
Mike Lockwood770126a2010-12-09 22:30:37 -080038 * <p>UsbService monitors for changes to USB state.
Mike Lockwood24236072010-06-23 17:36:36 -040039 */
Mike Lockwood770126a2010-12-09 22:30:37 -080040class UsbService {
41 private static final String TAG = UsbService.class.getSimpleName();
Mike Lockwood24236072010-06-23 17:36:36 -040042 private static final boolean LOG = false;
43
Mike Lockwoodb92df0f2010-12-10 16:19:32 -080044 private static final String USB_CONNECTED_MATCH =
45 "DEVPATH=/devices/virtual/switch/usb_connected";
46 private static final String USB_CONFIGURATION_MATCH =
47 "DEVPATH=/devices/virtual/switch/usb_configuration";
48 private static final String USB_FUNCTIONS_MATCH =
49 "DEVPATH=/devices/virtual/usb_composite/";
50 private static final String USB_CONNECTED_PATH =
51 "/sys/class/switch/usb_connected/state";
52 private static final String USB_CONFIGURATION_PATH =
53 "/sys/class/switch/usb_configuration/state";
54 private static final String USB_COMPOSITE_CLASS_PATH =
55 "/sys/class/usb_composite";
Mike Lockwood24236072010-06-23 17:36:36 -040056
57 private static final int MSG_UPDATE = 0;
58
Mike Lockwoodb92df0f2010-12-10 16:19:32 -080059 // Delay for debouncing USB disconnects.
60 // We often get rapid connect/disconnect events when enabling USB functions,
61 // which need debouncing.
62 private static final int UPDATE_DELAY = 1000;
63
64 // current connected and configuration state
65 private int mConnected;
66 private int mConfiguration;
67
68 // last broadcasted connected and configuration state
69 private int mLastConnected = -1;
70 private int mLastConfiguration = -1;
Mike Lockwood24236072010-06-23 17:36:36 -040071
72 // lists of enabled and disabled USB functions
73 private final ArrayList<String> mEnabledFunctions = new ArrayList<String>();
74 private final ArrayList<String> mDisabledFunctions = new ArrayList<String>();
75
76 private boolean mSystemReady;
77
78 private final Context mContext;
79
Mike Lockwood770126a2010-12-09 22:30:37 -080080 private final UEventObserver mUEventObserver = new UEventObserver() {
81 @Override
82 public void onUEvent(UEventObserver.UEvent event) {
83 if (Log.isLoggable(TAG, Log.VERBOSE)) {
84 Slog.v(TAG, "USB UEVENT: " + event.toString());
85 }
Mike Lockwood24236072010-06-23 17:36:36 -040086
Mike Lockwood770126a2010-12-09 22:30:37 -080087 synchronized (this) {
Mike Lockwoodb92df0f2010-12-10 16:19:32 -080088 String name = event.get("SWITCH_NAME");
89 String state = event.get("SWITCH_STATE");
90 if (name != null && state != null) {
Mike Lockwood770126a2010-12-09 22:30:37 -080091 try {
Mike Lockwoodb92df0f2010-12-10 16:19:32 -080092 int intState = Integer.parseInt(state);
93 if ("usb_connected".equals(name)) {
94 mConnected = intState;
Mike Lockwood770126a2010-12-09 22:30:37 -080095 // trigger an Intent broadcast
96 if (mSystemReady) {
Mike Lockwoodb92df0f2010-12-10 16:19:32 -080097 // debounce disconnects
98 update(mConnected == 0);
99 }
100 } else if ("usb_configuration".equals(name)) {
101 mConfiguration = intState;
102 // trigger an Intent broadcast
103 if (mSystemReady) {
104 update(mConnected == 0);
Mike Lockwood770126a2010-12-09 22:30:37 -0800105 }
Mike Lockwood24236072010-06-23 17:36:36 -0400106 }
Mike Lockwood770126a2010-12-09 22:30:37 -0800107 } catch (NumberFormatException e) {
108 Slog.e(TAG, "Could not parse switch state from event " + event);
Mike Lockwood24236072010-06-23 17:36:36 -0400109 }
Mike Lockwood770126a2010-12-09 22:30:37 -0800110 } else {
111 String function = event.get("FUNCTION");
112 String enabledStr = event.get("ENABLED");
113 if (function != null && enabledStr != null) {
114 // Note: we do not broadcast a change when a function is enabled or disabled.
115 // We just record the state change for the next broadcast.
116 boolean enabled = "1".equals(enabledStr);
117 if (enabled) {
118 if (!mEnabledFunctions.contains(function)) {
119 mEnabledFunctions.add(function);
120 }
121 mDisabledFunctions.remove(function);
122 } else {
123 if (!mDisabledFunctions.contains(function)) {
124 mDisabledFunctions.add(function);
125 }
126 mEnabledFunctions.remove(function);
Mike Lockwood24236072010-06-23 17:36:36 -0400127 }
Mike Lockwood24236072010-06-23 17:36:36 -0400128 }
129 }
130 }
131 }
Mike Lockwood770126a2010-12-09 22:30:37 -0800132 };
133
134 public UsbService(Context context) {
135 mContext = context;
136 init(); // set initial status
137
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800138 mUEventObserver.startObserving(USB_CONNECTED_MATCH);
Mike Lockwood770126a2010-12-09 22:30:37 -0800139 mUEventObserver.startObserving(USB_CONFIGURATION_MATCH);
140 mUEventObserver.startObserving(USB_FUNCTIONS_MATCH);
Mike Lockwood24236072010-06-23 17:36:36 -0400141 }
Mike Lockwood770126a2010-12-09 22:30:37 -0800142
Mike Lockwood24236072010-06-23 17:36:36 -0400143 private final void init() {
144 char[] buffer = new char[1024];
145
146 try {
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800147 FileReader file = new FileReader(USB_CONNECTED_PATH);
Mike Lockwood24236072010-06-23 17:36:36 -0400148 int len = file.read(buffer, 0, 1024);
Brian Carlstromfd9ddd12010-11-04 11:24:58 -0700149 file.close();
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800150 mConnected = Integer.valueOf((new String(buffer, 0, len)).trim());
151
152 file = new FileReader(USB_CONFIGURATION_PATH);
153 len = file.read(buffer, 0, 1024);
154 file.close();
155 mConfiguration = Integer.valueOf((new String(buffer, 0, len)).trim());
Mike Lockwood24236072010-06-23 17:36:36 -0400156
157 } catch (FileNotFoundException e) {
158 Slog.w(TAG, "This kernel does not have USB configuration switch support");
159 } catch (Exception e) {
160 Slog.e(TAG, "" , e);
161 }
162
163 try {
164 File[] files = new File(USB_COMPOSITE_CLASS_PATH).listFiles();
165 for (int i = 0; i < files.length; i++) {
166 File file = new File(files[i], "enable");
167 FileReader reader = new FileReader(file);
168 int len = reader.read(buffer, 0, 1024);
Brian Carlstromfd9ddd12010-11-04 11:24:58 -0700169 reader.close();
Mike Lockwood24236072010-06-23 17:36:36 -0400170 int value = Integer.valueOf((new String(buffer, 0, len)).trim());
171 String functionName = files[i].getName();
172 if (value == 1) {
173 mEnabledFunctions.add(functionName);
174 } else {
175 mDisabledFunctions.add(functionName);
176 }
177 }
178 } catch (FileNotFoundException e) {
179 Slog.w(TAG, "This kernel does not have USB composite class support");
180 } catch (Exception e) {
181 Slog.e(TAG, "" , e);
182 }
183 }
184
Mike Lockwoodda39f0e2010-07-27 18:44:30 -0400185 private native void monitorUsbHostBus();
186
187 // called from JNI in monitorUsbHostBus()
188 private void usbCameraAdded(int deviceID) {
Mike Lockwood770126a2010-12-09 22:30:37 -0800189 Intent intent = new Intent(UsbManager.ACTION_USB_CAMERA_ATTACHED,
Mike Lockwooda3156052010-11-20 12:28:27 -0500190 Ptp.Device.getContentUri(deviceID));
Mike Lockwoodda39f0e2010-07-27 18:44:30 -0400191 Log.d(TAG, "usbCameraAdded, sending " + intent);
192 mContext.sendBroadcast(intent);
193 }
194
195 // called from JNI in monitorUsbHostBus()
196 private void usbCameraRemoved(int deviceID) {
Mike Lockwood770126a2010-12-09 22:30:37 -0800197 Intent intent = new Intent(UsbManager.ACTION_USB_CAMERA_DETACHED,
Mike Lockwooda3156052010-11-20 12:28:27 -0500198 Ptp.Device.getContentUri(deviceID));
Mike Lockwoodda39f0e2010-07-27 18:44:30 -0400199 Log.d(TAG, "usbCameraRemoved, sending " + intent);
200 mContext.sendBroadcast(intent);
201 }
202
203 private void initHostSupport() {
204 // Create a thread to call into native code to wait for USB host events.
205 // This thread will call us back on usbCameraAdded and usbCameraRemoved.
206 Runnable runnable = new Runnable() {
207 public void run() {
208 monitorUsbHostBus();
209 }
210 };
Mike Lockwood770126a2010-12-09 22:30:37 -0800211 new Thread(null, runnable, "UsbService host thread").start();
Mike Lockwoodda39f0e2010-07-27 18:44:30 -0400212 }
213
Mike Lockwood24236072010-06-23 17:36:36 -0400214 void systemReady() {
215 synchronized (this) {
Mike Lockwoodda39f0e2010-07-27 18:44:30 -0400216 if (mContext.getResources().getBoolean(
217 com.android.internal.R.bool.config_hasUsbHostSupport)) {
218 // start monitoring for connected USB devices
219 initHostSupport();
220 }
221
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800222 update(false);
Mike Lockwood24236072010-06-23 17:36:36 -0400223 mSystemReady = true;
224 }
225 }
226
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800227 private final void update(boolean delayed) {
228 mHandler.removeMessages(MSG_UPDATE);
229 mHandler.sendEmptyMessageDelayed(MSG_UPDATE, delayed ? UPDATE_DELAY : 0);
Mike Lockwood24236072010-06-23 17:36:36 -0400230 }
231
232 private final Handler mHandler = new Handler() {
Mike Lockwood709981e2010-06-28 09:58:58 -0400233 private void addEnabledFunctions(Intent intent) {
234 // include state of all USB functions in our extras
235 for (int i = 0; i < mEnabledFunctions.size(); i++) {
Mike Lockwood770126a2010-12-09 22:30:37 -0800236 intent.putExtra(mEnabledFunctions.get(i), UsbManager.USB_FUNCTION_ENABLED);
Mike Lockwood709981e2010-06-28 09:58:58 -0400237 }
238 for (int i = 0; i < mDisabledFunctions.size(); i++) {
Mike Lockwood770126a2010-12-09 22:30:37 -0800239 intent.putExtra(mDisabledFunctions.get(i), UsbManager.USB_FUNCTION_DISABLED);
Mike Lockwood709981e2010-06-28 09:58:58 -0400240 }
241 }
242
Mike Lockwood24236072010-06-23 17:36:36 -0400243 @Override
244 public void handleMessage(Message msg) {
245 switch (msg.what) {
246 case MSG_UPDATE:
247 synchronized (this) {
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800248 if (mConnected != mLastConnected || mConfiguration != mLastConfiguration) {
Mike Lockwood24236072010-06-23 17:36:36 -0400249
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800250 final ContentResolver cr = mContext.getContentResolver();
251 if (Settings.Secure.getInt(cr,
252 Settings.Secure.DEVICE_PROVISIONED, 0) == 0) {
253 Slog.i(TAG, "Device not provisioned, skipping USB broadcast");
254 return;
255 }
256
257 mLastConnected = mConnected;
258 mLastConfiguration = mConfiguration;
259
260 // send a sticky broadcast containing current USB state
261 Intent intent = new Intent(UsbManager.ACTION_USB_STATE);
262 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
263 intent.putExtra(UsbManager.USB_CONNECTED, mConnected != 0);
264 intent.putExtra(UsbManager.USB_CONFIGURATION, mConfiguration);
Mike Lockwood709981e2010-06-28 09:58:58 -0400265 addEnabledFunctions(intent);
Mike Lockwoodb92df0f2010-12-10 16:19:32 -0800266 mContext.sendStickyBroadcast(intent);
Mike Lockwood24236072010-06-23 17:36:36 -0400267 }
Mike Lockwood24236072010-06-23 17:36:36 -0400268 }
269 break;
270 }
271 }
272 };
273}