2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com
.android
.server
;
19 import android
.content
.Context
;
20 import android
.os
.Handler
;
21 import android
.os
.Looper
;
22 import android
.os
.Message
;
23 import android
.os
.PowerManager
;
24 import android
.os
.PowerManager
.WakeLock
;
25 import android
.os
.UEventObserver
;
26 import android
.util
.Slog
;
27 import android
.media
.AudioManager
;
28 import android
.util
.Log
;
29 import android
.view
.InputDevice
;
31 import com
.android
.internal
.R
;
32 import com
.android
.server
.input
.InputManagerService
;
33 import com
.android
.server
.input
.InputManagerService
.WiredAccessoryCallbacks
;
34 import static com
.android
.server
.input
.InputManagerService
.SW_HEADPHONE_INSERT
;
35 import static com
.android
.server
.input
.InputManagerService
.SW_MICROPHONE_INSERT
;
36 import static com
.android
.server
.input
.InputManagerService
.SW_LINEOUT_INSERT
;
37 import static com
.android
.server
.input
.InputManagerService
.SW_HEADPHONE_INSERT_BIT
;
38 import static com
.android
.server
.input
.InputManagerService
.SW_MICROPHONE_INSERT_BIT
;
39 import static com
.android
.server
.input
.InputManagerService
.SW_LINEOUT_INSERT_BIT
;
42 import java
.io
.FileReader
;
43 import java
.io
.FileNotFoundException
;
44 import java
.util
.ArrayList
;
45 import java
.util
.List
;
46 import java
.util
.Locale
;
49 * <p>WiredAccessoryManager monitors for a wired headset on the main board or dock using
50 * both the InputManagerService notifyWiredAccessoryChanged interface and the UEventObserver
53 final class WiredAccessoryManager
implements WiredAccessoryCallbacks
{
54 private static final String TAG
= WiredAccessoryManager
.class.getSimpleName();
55 private static final boolean LOG
= true;
57 private static final int BIT_HEADSET
= (1 << 0);
58 private static final int BIT_HEADSET_NO_MIC
= (1 << 1);
59 private static final int BIT_USB_HEADSET_ANLG
= (1 << 2);
60 private static final int BIT_USB_HEADSET_DGTL
= (1 << 3);
61 private static final int BIT_HDMI_AUDIO
= (1 << 4);
62 private static final int BIT_LINEOUT
= (1 << 5);
63 private static final int SUPPORTED_HEADSETS
= (BIT_HEADSET
|BIT_HEADSET_NO_MIC
|
64 BIT_USB_HEADSET_ANLG
|BIT_USB_HEADSET_DGTL
|
65 BIT_HDMI_AUDIO
|BIT_LINEOUT
);
67 private static final String NAME_H2W
= "h2w";
68 private static final String NAME_USB_AUDIO
= "usb_audio";
69 private static final String NAME_HDMI_AUDIO
= "hdmi_audio";
70 private static final String NAME_HDMI
= "hdmi";
72 private static final int MSG_NEW_DEVICE_STATE
= 1;
73 private static final int MSG_SYSTEM_READY
= 2;
75 private final Object mLock
= new Object();
77 private final WakeLock mWakeLock
; // held while there is a pending route change
78 private final AudioManager mAudioManager
;
80 private int mHeadsetState
;
82 private int mSwitchValues
;
84 private final WiredAccessoryObserver mObserver
;
85 private final InputManagerService mInputManager
;
87 private final boolean mUseDevInputEventForAudioJack
;
89 public WiredAccessoryManager(Context context
, InputManagerService inputManager
) {
90 PowerManager pm
= (PowerManager
)context
.getSystemService(Context
.POWER_SERVICE
);
91 mWakeLock
= pm
.newWakeLock(PowerManager
.PARTIAL_WAKE_LOCK
, "WiredAccessoryManager");
92 mWakeLock
.setReferenceCounted(false);
93 mAudioManager
= (AudioManager
)context
.getSystemService(Context
.AUDIO_SERVICE
);
94 mInputManager
= inputManager
;
96 mUseDevInputEventForAudioJack
=
97 context
.getResources().getBoolean(R
.bool
.config_useDevInputEventForAudioJack
);
99 mObserver
= new WiredAccessoryObserver();
102 private void onSystemReady() {
103 if (mUseDevInputEventForAudioJack
) {
104 int switchValues
= 0;
105 if (mInputManager
.getSwitchState(-1, InputDevice
.SOURCE_ANY
, SW_HEADPHONE_INSERT
) == 1) {
106 switchValues
|= SW_HEADPHONE_INSERT_BIT
;
108 if (mInputManager
.getSwitchState(-1, InputDevice
.SOURCE_ANY
, SW_MICROPHONE_INSERT
) == 1) {
109 switchValues
|= SW_MICROPHONE_INSERT_BIT
;
111 if (mInputManager
.getSwitchState(-1, InputDevice
.SOURCE_ANY
, SW_LINEOUT_INSERT
) == 1) {
112 switchValues
|= SW_LINEOUT_INSERT_BIT
;
114 notifyWiredAccessoryChanged(0, switchValues
,
115 SW_HEADPHONE_INSERT_BIT
| SW_MICROPHONE_INSERT_BIT
| SW_LINEOUT_INSERT_BIT
);
122 public void notifyWiredAccessoryChanged(long whenNanos
, int switchValues
, int switchMask
) {
123 if (LOG
) Slog
.v(TAG
, "notifyWiredAccessoryChanged: when=" + whenNanos
124 + " bits=" + switchCodeToString(switchValues
, switchMask
)
125 + " mask=" + Integer
.toHexString(switchMask
));
127 synchronized (mLock
) {
129 mSwitchValues
= (mSwitchValues
& ~switchMask
) | switchValues
;
130 switch (mSwitchValues
&
131 (SW_HEADPHONE_INSERT_BIT
| SW_MICROPHONE_INSERT_BIT
| SW_LINEOUT_INSERT_BIT
)) {
136 case SW_HEADPHONE_INSERT_BIT
:
137 headset
= BIT_HEADSET_NO_MIC
;
140 case SW_LINEOUT_INSERT_BIT
:
141 headset
= BIT_LINEOUT
;
144 case SW_HEADPHONE_INSERT_BIT
| SW_MICROPHONE_INSERT_BIT
:
145 headset
= BIT_HEADSET
;
148 case SW_MICROPHONE_INSERT_BIT
:
149 headset
= BIT_HEADSET
;
157 updateLocked(NAME_H2W
,
158 (mHeadsetState
& ~
(BIT_HEADSET
| BIT_HEADSET_NO_MIC
| BIT_LINEOUT
)) | headset
);
163 public void systemReady() {
164 synchronized (mLock
) {
167 Message msg
= mHandler
.obtainMessage(MSG_SYSTEM_READY
, 0, 0, null);
168 mHandler
.sendMessage(msg
);
173 * Compare the existing headset state with the new state and pass along accordingly. Note
174 * that this only supports a single headset at a time. Inserting both a usb and jacked headset
175 * results in support for the last one plugged in. Similarly, unplugging either is seen as
178 * @param newName One of the NAME_xxx variables defined above.
179 * @param newState 0 or one of the BIT_xxx variables defined above.
181 private void updateLocked(String newName
, int newState
) {
182 // Retain only relevant bits
183 int headsetState
= newState
& SUPPORTED_HEADSETS
;
184 int usb_headset_anlg
= headsetState
& BIT_USB_HEADSET_ANLG
;
185 int usb_headset_dgtl
= headsetState
& BIT_USB_HEADSET_DGTL
;
186 int h2w_headset
= headsetState
& (BIT_HEADSET
| BIT_HEADSET_NO_MIC
| BIT_LINEOUT
);
187 boolean h2wStateChange
= true;
188 boolean usbStateChange
= true;
189 if (LOG
) Slog
.v(TAG
, "newName=" + newName
190 + " newState=" + newState
191 + " headsetState=" + headsetState
192 + " prev headsetState=" + mHeadsetState
);
194 if (mHeadsetState
== headsetState
) {
195 Log
.e(TAG
, "No state change.");
199 // reject all suspect transitions: only accept state changes from:
200 // - a: 0 headset to 1 headset
201 // - b: 1 headset to 0 headset
202 if (h2w_headset
== (BIT_HEADSET
| BIT_HEADSET_NO_MIC
| BIT_LINEOUT
)) {
203 Log
.e(TAG
, "Invalid combination, unsetting h2w flag");
204 h2wStateChange
= false;
206 // - c: 0 usb headset to 1 usb headset
207 // - d: 1 usb headset to 0 usb headset
208 if (usb_headset_anlg
== BIT_USB_HEADSET_ANLG
&& usb_headset_dgtl
== BIT_USB_HEADSET_DGTL
) {
209 Log
.e(TAG
, "Invalid combination, unsetting usb flag");
210 usbStateChange
= false;
212 if (!h2wStateChange
&& !usbStateChange
) {
213 Log
.e(TAG
, "invalid transition, returning ...");
219 Log
.i(TAG
, "MSG_NEW_DEVICE_STATE");
220 Message msg
= mHandler
.obtainMessage(MSG_NEW_DEVICE_STATE
, headsetState
,
222 mHandler
.sendMessage(msg
);
224 mHeadsetState
= headsetState
;
227 private final Handler mHandler
= new Handler(Looper
.myLooper(), null, true) {
229 public void handleMessage(Message msg
) {
231 case MSG_NEW_DEVICE_STATE
:
232 setDevicesState(msg
.arg1
, msg
.arg2
, (String
)msg
.obj
);
235 case MSG_SYSTEM_READY
:
243 private void setDevicesState(
244 int headsetState
, int prevHeadsetState
, String headsetName
) {
245 synchronized (mLock
) {
246 int allHeadsets
= SUPPORTED_HEADSETS
;
247 for (int curHeadset
= 1; allHeadsets
!= 0; curHeadset
<<= 1) {
248 if ((curHeadset
& allHeadsets
) != 0) {
249 setDeviceStateLocked(curHeadset
, headsetState
, prevHeadsetState
, headsetName
);
250 allHeadsets
&= ~curHeadset
;
256 private void setDeviceStateLocked(int headset
,
257 int headsetState
, int prevHeadsetState
, String headsetName
) {
258 if ((headsetState
& headset
) != (prevHeadsetState
& headset
)) {
263 if ((headsetState
& headset
) != 0) {
269 if (headset
== BIT_HEADSET
) {
270 outDevice
= AudioManager
.DEVICE_OUT_WIRED_HEADSET
;
271 inDevice
= AudioManager
.DEVICE_IN_WIRED_HEADSET
;
272 } else if (headset
== BIT_HEADSET_NO_MIC
){
273 outDevice
= AudioManager
.DEVICE_OUT_WIRED_HEADPHONE
;
274 } else if (headset
== BIT_LINEOUT
){
275 outDevice
= AudioManager
.DEVICE_OUT_LINE
;
276 } else if (headset
== BIT_USB_HEADSET_ANLG
) {
277 outDevice
= AudioManager
.DEVICE_OUT_ANLG_DOCK_HEADSET
;
278 } else if (headset
== BIT_USB_HEADSET_DGTL
) {
279 outDevice
= AudioManager
.DEVICE_OUT_DGTL_DOCK_HEADSET
;
280 } else if (headset
== BIT_HDMI_AUDIO
) {
281 outDevice
= AudioManager
.DEVICE_OUT_HDMI
;
283 Slog
.e(TAG
, "setDeviceState() invalid headset type: "+headset
);
288 Slog
.v(TAG
, "headsetName: " + headsetName
+
289 (state
== 1 ?
" connected" : " disconnected"));
292 if (outDevice
!= 0) {
293 mAudioManager
.setWiredDeviceConnectionState(outDevice
, state
, "", headsetName
);
296 mAudioManager
.setWiredDeviceConnectionState(inDevice
, state
, "", headsetName
);
301 private String
switchCodeToString(int switchValues
, int switchMask
) {
302 StringBuffer sb
= new StringBuffer();
303 if ((switchMask
& SW_HEADPHONE_INSERT_BIT
) != 0 &&
304 (switchValues
& SW_HEADPHONE_INSERT_BIT
) != 0) {
305 sb
.append("SW_HEADPHONE_INSERT ");
307 if ((switchMask
& SW_MICROPHONE_INSERT_BIT
) != 0 &&
308 (switchValues
& SW_MICROPHONE_INSERT_BIT
) != 0) {
309 sb
.append("SW_MICROPHONE_INSERT");
311 return sb
.toString();
314 class WiredAccessoryObserver
extends UEventObserver
{
315 private final List
<UEventInfo
> mUEventInfo
;
317 public WiredAccessoryObserver() {
318 mUEventInfo
= makeObservedUEventList();
322 synchronized (mLock
) {
323 if (LOG
) Slog
.v(TAG
, "init()");
324 char[] buffer
= new char[1024];
326 for (int i
= 0; i
< mUEventInfo
.size(); ++i
) {
327 UEventInfo uei
= mUEventInfo
.get(i
);
330 FileReader file
= new FileReader(uei
.getSwitchStatePath());
331 int len
= file
.read(buffer
, 0, 1024);
333 curState
= Integer
.valueOf((new String(buffer
, 0, len
)).trim());
336 updateStateLocked(uei
.getDevPath(), uei
.getDevName(), curState
);
338 } catch (FileNotFoundException e
) {
339 Slog
.w(TAG
, uei
.getSwitchStatePath() +
340 " not found while attempting to determine initial switch state");
341 } catch (Exception e
) {
347 // At any given time accessories could be inserted
348 // one on the board, one on the dock and one on HDMI:
349 // observe three UEVENTs
350 for (int i
= 0; i
< mUEventInfo
.size(); ++i
) {
351 UEventInfo uei
= mUEventInfo
.get(i
);
352 startObserving("DEVPATH="+uei
.getDevPath());
356 private List
<UEventInfo
> makeObservedUEventList() {
357 List
<UEventInfo
> retVal
= new ArrayList
<UEventInfo
>();
361 if (!mUseDevInputEventForAudioJack
) {
362 uei
= new UEventInfo(NAME_H2W
, BIT_HEADSET
, BIT_HEADSET_NO_MIC
, BIT_LINEOUT
);
363 if (uei
.checkSwitchExists()) {
366 Slog
.w(TAG
, "This kernel does not have wired headset support");
371 uei
= new UEventInfo(NAME_USB_AUDIO
, BIT_USB_HEADSET_ANLG
, BIT_USB_HEADSET_DGTL
, 0);
372 if (uei
.checkSwitchExists()) {
375 Slog
.w(TAG
, "This kernel does not have usb audio support");
380 // If the kernel has support for the "hdmi_audio" switch, use that. It will be
381 // signalled only when the HDMI driver has a video mode configured, and the downstream
382 // sink indicates support for audio in its EDID.
384 // If the kernel does not have an "hdmi_audio" switch, just fall back on the older
385 // "hdmi" switch instead.
386 uei
= new UEventInfo(NAME_HDMI_AUDIO
, BIT_HDMI_AUDIO
, 0, 0);
387 if (uei
.checkSwitchExists()) {
390 uei
= new UEventInfo(NAME_HDMI
, BIT_HDMI_AUDIO
, 0, 0);
391 if (uei
.checkSwitchExists()) {
394 Slog
.w(TAG
, "This kernel does not have HDMI audio support");
402 public void onUEvent(UEventObserver
.UEvent event
) {
403 if (LOG
) Slog
.v(TAG
, "Headset UEVENT: " + event
.toString());
406 String devPath
= event
.get("DEVPATH");
407 String name
= event
.get("SWITCH_NAME");
408 int state
= Integer
.parseInt(event
.get("SWITCH_STATE"));
409 synchronized (mLock
) {
410 updateStateLocked(devPath
, name
, state
);
412 } catch (NumberFormatException e
) {
413 Slog
.e(TAG
, "Could not parse switch state from event " + event
);
417 private void updateStateLocked(String devPath
, String name
, int state
) {
418 for (int i
= 0; i
< mUEventInfo
.size(); ++i
) {
419 UEventInfo uei
= mUEventInfo
.get(i
);
420 if (devPath
.equals(uei
.getDevPath())) {
421 updateLocked(name
, uei
.computeNewHeadsetState(mHeadsetState
, state
));
427 private final class UEventInfo
{
428 private final String mDevName
;
429 private final int mState1Bits
;
430 private final int mState2Bits
;
431 private final int mStateNbits
;
433 public UEventInfo(String devName
, int state1Bits
, int state2Bits
, int stateNbits
) {
435 mState1Bits
= state1Bits
;
436 mState2Bits
= state2Bits
;
437 mStateNbits
= stateNbits
;
440 public String
getDevName() { return mDevName
; }
442 public String
getDevPath() {
443 return String
.format(Locale
.US
, "/devices/virtual/switch/%s", mDevName
);
446 public String
getSwitchStatePath() {
447 return String
.format(Locale
.US
, "/sys/class/switch/%s/state", mDevName
);
450 public boolean checkSwitchExists() {
451 File f
= new File(getSwitchStatePath());
455 public int computeNewHeadsetState(int headsetState
, int switchState
) {
456 int preserveMask
= ~
(mState1Bits
| mState2Bits
| mStateNbits
);
457 int setBits
= ((switchState
== 1) ? mState1Bits
:
458 ((switchState
== 2) ? mState2Bits
:
459 ((switchState
== mStateNbits
) ? mStateNbits
: 0)));
461 return ((headsetState
& preserveMask
) | setBits
);