blob: e45c3685b1c49b2a66f0f2b6e23b701ea3c1d268 [file] [log] [blame]
Praveen Bharathi21e941b2010-10-06 15:23:14 -05001/*
2 * Copyright (C) 2008 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.app.ActivityManagerNative;
Eric Laurent3fc78a52010-11-18 15:50:22 -080020import android.content.BroadcastReceiver;
Praveen Bharathi21e941b2010-10-06 15:23:14 -050021import android.content.Context;
22import android.content.Intent;
Eric Laurent3fc78a52010-11-18 15:50:22 -080023import android.content.IntentFilter;
Praveen Bharathi21e941b2010-10-06 15:23:14 -050024import android.os.Handler;
25import android.os.Message;
26import android.os.PowerManager;
27import android.os.PowerManager.WakeLock;
28import android.os.UEventObserver;
29import android.util.Slog;
30import android.media.AudioManager;
31import android.util.Log;
32
33import java.io.FileReader;
34import java.io.FileNotFoundException;
35
36/**
37 * <p>WiredAccessoryObserver monitors for a wired headset on the main board or dock.
38 */
39class WiredAccessoryObserver extends UEventObserver {
40 private static final String TAG = WiredAccessoryObserver.class.getSimpleName();
41 private static final boolean LOG = true;
Praveen Bharathi26e37342010-11-02 19:23:30 -070042 private static final int MAX_AUDIO_PORTS = 3; /* h2w, USB Audio & hdmi */
Praveen Bharathi21e941b2010-10-06 15:23:14 -050043 private static final String uEventInfo[][] = { {"DEVPATH=/devices/virtual/switch/h2w",
44 "/sys/class/switch/h2w/state",
45 "/sys/class/switch/h2w/name"},
46 {"DEVPATH=/devices/virtual/switch/usb_audio",
47 "/sys/class/switch/usb_audio/state",
Praveen Bharathi26e37342010-11-02 19:23:30 -070048 "/sys/class/switch/usb_audio/name"},
49 {"DEVPATH=/devices/virtual/switch/hdmi",
50 "/sys/class/switch/hdmi/state",
51 "/sys/class/switch/hdmi/name"} };
Praveen Bharathi21e941b2010-10-06 15:23:14 -050052
53 private static final int BIT_HEADSET = (1 << 0);
54 private static final int BIT_HEADSET_NO_MIC = (1 << 1);
55 private static final int BIT_USB_HEADSET_ANLG = (1 << 2);
56 private static final int BIT_USB_HEADSET_DGTL = (1 << 3);
Praveen Bharathi26e37342010-11-02 19:23:30 -070057 private static final int BIT_HDMI_AUDIO = (1 << 4);
Praveen Bharathi21e941b2010-10-06 15:23:14 -050058 private static final int SUPPORTED_HEADSETS = (BIT_HEADSET|BIT_HEADSET_NO_MIC|
Praveen Bharathi26e37342010-11-02 19:23:30 -070059 BIT_USB_HEADSET_ANLG|BIT_USB_HEADSET_DGTL|
60 BIT_HDMI_AUDIO);
Praveen Bharathi21e941b2010-10-06 15:23:14 -050061 private static final int HEADSETS_WITH_MIC = BIT_HEADSET;
62
63 private int mHeadsetState;
64 private int mPrevHeadsetState;
65 private String mHeadsetName;
66 private int switchState;
67
68 private final Context mContext;
69 private final WakeLock mWakeLock; // held while there is a pending route change
70
71 public WiredAccessoryObserver(Context context) {
72 mContext = context;
73 PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
74 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WiredAccessoryObserver");
75 mWakeLock.setReferenceCounted(false);
76
Eric Laurent3fc78a52010-11-18 15:50:22 -080077 context.registerReceiver(new BootCompletedReceiver(),
78 new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
79 }
80
81 private final class BootCompletedReceiver extends BroadcastReceiver {
82 @Override
83 public void onReceive(Context context, Intent intent) {
84 // At any given time accessories could be inserted
85 // one on the board, one on the dock and one on HDMI:
86 // observe three UEVENTs
87 init(); // set initial status
Elliott Hughesb55dcc22010-11-04 11:55:47 -070088 for (int i = 0; i < MAX_AUDIO_PORTS; i++) {
Praveen Bharathi21e941b2010-10-06 15:23:14 -050089 startObserving(uEventInfo[i][0]);
90 }
Eric Laurent3fc78a52010-11-18 15:50:22 -080091 }
92 }
Praveen Bharathi21e941b2010-10-06 15:23:14 -050093
94 @Override
95 public void onUEvent(UEventObserver.UEvent event) {
96 if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString());
97
98 try {
Praveen Bharathi7526da42010-11-16 02:08:02 -060099 String name = event.get("SWITCH_NAME");
100 int state = Integer.parseInt(event.get("SWITCH_STATE"));
101 updateState(name, state);
Praveen Bharathi21e941b2010-10-06 15:23:14 -0500102 } catch (NumberFormatException e) {
103 Slog.e(TAG, "Could not parse switch state from event " + event);
104 }
105 }
106
Praveen Bharathi7526da42010-11-16 02:08:02 -0600107 private synchronized final void updateState(String name, int state)
108 {
109 if (name.equals("usb_audio")) {
110 if (state == 1) {
111 switchState = ((mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC|
112 BIT_USB_HEADSET_DGTL|BIT_HDMI_AUDIO)) |
113 (state << 2));
114 } else if (state == 2) {
115 switchState = ((mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC|
116 BIT_USB_HEADSET_ANLG|BIT_HDMI_AUDIO)) |
117 (state << 3));
118 } else switchState = (mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC|BIT_HDMI_AUDIO));
119 }
120 else if (name.equals("hdmi")) {
121 switchState = ((mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC|
122 BIT_USB_HEADSET_DGTL|BIT_USB_HEADSET_ANLG)) |
123 (state << 4));
124 }
125 else {
126 switchState = ((mHeadsetState & (BIT_HDMI_AUDIO|BIT_USB_HEADSET_ANLG|
127 BIT_USB_HEADSET_DGTL)) |
128 state);
129 }
130 update(name, switchState);
131 }
132
Praveen Bharathi21e941b2010-10-06 15:23:14 -0500133 private synchronized final void init() {
134 char[] buffer = new char[1024];
135
136 String newName = mHeadsetName;
137 int newState = mHeadsetState;
138 mPrevHeadsetState = mHeadsetState;
139
Eric Laurent3fc78a52010-11-18 15:50:22 -0800140 if (LOG) Slog.v(TAG, "init()");
141
Elliott Hughesb55dcc22010-11-04 11:55:47 -0700142 for (int i = 0; i < MAX_AUDIO_PORTS; i++) {
Praveen Bharathi21e941b2010-10-06 15:23:14 -0500143 try {
144 FileReader file = new FileReader(uEventInfo[i][1]);
145 int len = file.read(buffer, 0, 1024);
Brian Carlstrom237171f2010-11-04 16:23:21 -0700146 file.close();
Praveen Bharathi21e941b2010-10-06 15:23:14 -0500147 newState = Integer.valueOf((new String(buffer, 0, len)).trim());
148
149 file = new FileReader(uEventInfo[i][2]);
150 len = file.read(buffer, 0, 1024);
Brian Carlstrom237171f2010-11-04 16:23:21 -0700151 file.close();
Praveen Bharathi21e941b2010-10-06 15:23:14 -0500152 newName = new String(buffer, 0, len).trim();
153
Praveen Bharathi7526da42010-11-16 02:08:02 -0600154 if (newState > 0) {
155 updateState(newName, newState);
156 }
157
Praveen Bharathi21e941b2010-10-06 15:23:14 -0500158 } catch (FileNotFoundException e) {
159 Slog.w(TAG, "This kernel does not have wired headset support");
160 } catch (Exception e) {
161 Slog.e(TAG, "" , e);
162 }
Praveen Bharathi21e941b2010-10-06 15:23:14 -0500163 }
164 }
165
166 private synchronized final void update(String newName, int newState) {
167 // Retain only relevant bits
168 int headsetState = newState & SUPPORTED_HEADSETS;
169 int newOrOld = headsetState | mHeadsetState;
170 int delay = 0;
171 int usb_headset_anlg = headsetState & BIT_USB_HEADSET_ANLG;
172 int usb_headset_dgtl = headsetState & BIT_USB_HEADSET_DGTL;
173 int h2w_headset = headsetState & (BIT_HEADSET | BIT_HEADSET_NO_MIC);
174 boolean h2wStateChange = true;
175 boolean usbStateChange = true;
176 // reject all suspect transitions: only accept state changes from:
177 // - a: 0 heaset to 1 headset
178 // - b: 1 headset to 0 headset
Eric Laurent3fc78a52010-11-18 15:50:22 -0800179 if (LOG) Slog.v(TAG, "newState = "+newState+", headsetState = "+headsetState+","
180 + "mHeadsetState = "+mHeadsetState);
Praveen Bharathi21e941b2010-10-06 15:23:14 -0500181 if (mHeadsetState == headsetState || ((h2w_headset & (h2w_headset - 1)) != 0)) {
182 Log.e(TAG, "unsetting h2w flag");
183 h2wStateChange = false;
184 }
185 // - c: 0 usb headset to 1 usb headset
186 // - d: 1 usb headset to 0 usb headset
187 if ((usb_headset_anlg >> 2) == 1 && (usb_headset_dgtl >> 3) == 1) {
188 Log.e(TAG, "unsetting usb flag");
189 usbStateChange = false;
190 }
191 if (!h2wStateChange && !usbStateChange) {
192 Log.e(TAG, "invalid transition, returning ...");
193 return;
194 }
195
196 mHeadsetName = newName;
197 mPrevHeadsetState = mHeadsetState;
198 mHeadsetState = headsetState;
199
200 if (headsetState == 0) {
201 Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
202 mContext.sendBroadcast(intent);
203 // It can take hundreds of ms flush the audio pipeline after
204 // apps pause audio playback, but audio route changes are
205 // immediate, so delay the route change by 1000ms.
206 // This could be improved once the audio sub-system provides an
207 // interface to clear the audio pipeline.
208 delay = 1000;
209 } else {
210 // Insert the same delay for headset connection so that the connection event is not
211 // broadcast before the disconnection event in case of fast removal/insertion
212 if (mHandler.hasMessages(0)) {
213 delay = 1000;
214 }
215 }
216 mWakeLock.acquire();
217 mHandler.sendMessageDelayed(mHandler.obtainMessage(0,
218 mHeadsetState,
219 mPrevHeadsetState,
220 mHeadsetName),
221 delay);
222 }
223
224 private synchronized final void sendIntents(int headsetState, int prevHeadsetState, String headsetName) {
225 int allHeadsets = SUPPORTED_HEADSETS;
226 for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) {
227 if ((curHeadset & allHeadsets) != 0) {
228 sendIntent(curHeadset, headsetState, prevHeadsetState, headsetName);
229 allHeadsets &= ~curHeadset;
230 }
231 }
232 }
233
234 private final void sendIntent(int headset, int headsetState, int prevHeadsetState, String headsetName) {
235 if ((headsetState & headset) != (prevHeadsetState & headset)) {
236
237 int state = 0;
238 if ((headsetState & headset) != 0) {
239 state = 1;
240 }
Praveen Bharathi26e37342010-11-02 19:23:30 -0700241 if((headset == BIT_USB_HEADSET_ANLG) || (headset == BIT_USB_HEADSET_DGTL) ||
242 (headset == BIT_HDMI_AUDIO)) {
Praveen Bharathi21e941b2010-10-06 15:23:14 -0500243 Intent intent;
244
245 // Pack up the values and broadcast them to everyone
246 if (headset == BIT_USB_HEADSET_ANLG) {
247 intent = new Intent(Intent.ACTION_USB_ANLG_HEADSET_PLUG);
248 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
249 intent.putExtra("state", state);
250 intent.putExtra("name", headsetName);
251 ActivityManagerNative.broadcastStickyIntent(intent, null);
252 } else if (headset == BIT_USB_HEADSET_DGTL) {
253 intent = new Intent(Intent.ACTION_USB_DGTL_HEADSET_PLUG);
254 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
255 intent.putExtra("state", state);
256 intent.putExtra("name", headsetName);
257 ActivityManagerNative.broadcastStickyIntent(intent, null);
Praveen Bharathi26e37342010-11-02 19:23:30 -0700258 } else if (headset == BIT_HDMI_AUDIO) {
259 intent = new Intent(Intent.ACTION_HDMI_AUDIO_PLUG);
260 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
261 intent.putExtra("state", state);
262 intent.putExtra("name", headsetName);
263 ActivityManagerNative.broadcastStickyIntent(intent, null);
Praveen Bharathi21e941b2010-10-06 15:23:14 -0500264 }
265
266 if (LOG) Slog.v(TAG, "Intent.ACTION_USB_HEADSET_PLUG: state: "+state+" name: "+headsetName);
267 // TODO: Should we require a permission?
268 }
269 if((headset == BIT_HEADSET) || (headset == BIT_HEADSET_NO_MIC)) {
270
271 // Pack up the values and broadcast them to everyone
272 Intent intent = new Intent(Intent.ACTION_HEADSET_PLUG);
273 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
274 //int state = 0;
275 int microphone = 0;
276
277 if ((headset & HEADSETS_WITH_MIC) != 0) {
278 microphone = 1;
279 }
280
281 intent.putExtra("state", state);
282 intent.putExtra("name", headsetName);
283 intent.putExtra("microphone", microphone);
284
285 if (LOG) Slog.v(TAG, "Intent.ACTION_HEADSET_PLUG: state: "+state+" name: "+headsetName+" mic: "+microphone);
286 // TODO: Should we require a permission?
287 ActivityManagerNative.broadcastStickyIntent(intent, null);
288 }
289 }
290 }
291
292 private final Handler mHandler = new Handler() {
293 @Override
294 public void handleMessage(Message msg) {
295 sendIntents(msg.arg1, msg.arg2, (String)msg.obj);
296 mWakeLock.release();
297 }
298 };
299}