diff --git a/services/java/com/android/server/WiredAccessoryObserver.java b/services/java/com/android/server/WiredAccessoryObserver.java index 326b94033776a..a7a46ddd5aa70 100644 --- a/services/java/com/android/server/WiredAccessoryObserver.java +++ b/services/java/com/android/server/WiredAccessoryObserver.java @@ -30,8 +30,11 @@ import android.util.Slog; import android.media.AudioManager; import android.util.Log; +import java.io.File; import java.io.FileReader; import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.List; /** *

WiredAccessoryObserver monitors for a wired headset on the main board or dock. @@ -39,17 +42,6 @@ import java.io.FileNotFoundException; class WiredAccessoryObserver extends UEventObserver { private static final String TAG = WiredAccessoryObserver.class.getSimpleName(); private static final boolean LOG = true; - private static final int MAX_AUDIO_PORTS = 3; /* h2w, USB Audio & hdmi */ - private static final String uEventInfo[][] = { {"DEVPATH=/devices/virtual/switch/h2w", - "/sys/class/switch/h2w/state", - "/sys/class/switch/h2w/name"}, - {"DEVPATH=/devices/virtual/switch/usb_audio", - "/sys/class/switch/usb_audio/state", - "/sys/class/switch/usb_audio/name"}, - {"DEVPATH=/devices/virtual/switch/hdmi", - "/sys/class/switch/hdmi/state", - "/sys/class/switch/hdmi/name"} }; - private static final int BIT_HEADSET = (1 << 0); private static final int BIT_HEADSET_NO_MIC = (1 << 1); private static final int BIT_USB_HEADSET_ANLG = (1 << 2); @@ -60,10 +52,89 @@ class WiredAccessoryObserver extends UEventObserver { BIT_HDMI_AUDIO); private static final int HEADSETS_WITH_MIC = BIT_HEADSET; + private static class UEventInfo { + private final String mDevName; + private final int mState1Bits; + private final int mState2Bits; + + public UEventInfo(String devName, int state1Bits, int state2Bits) { + mDevName = devName; + mState1Bits = state1Bits; + mState2Bits = state2Bits; + } + + public String getDevName() { return mDevName; } + + public String getDevPath() { + return String.format("DEVPATH=/devices/virtual/switch/%s", mDevName); + } + + public String getSwitchStatePath() { + return String.format("/sys/class/switch/%s/state", mDevName); + } + + public boolean checkSwitchExists() { + File f = new File(getSwitchStatePath()); + return ((null != f) && f.exists()); + } + + public int computeNewHeadsetState(int headsetState, int switchState) { + int preserveMask = ~(mState1Bits | mState2Bits); + int setBits = ((switchState == 1) ? mState1Bits : + ((switchState == 2) ? mState2Bits : 0)); + + return ((headsetState & preserveMask) | setBits); + } + } + + private static List makeObservedUEventList() { + List retVal = new ArrayList(); + UEventInfo uei; + + // Monitor h2w + uei = new UEventInfo("h2w", BIT_HEADSET, BIT_HEADSET_NO_MIC); + if (uei.checkSwitchExists()) { + retVal.add(uei); + } else { + Slog.w(TAG, "This kernel does not have wired headset support"); + } + + // Monitor USB + uei = new UEventInfo("usb_audio", BIT_USB_HEADSET_ANLG, BIT_USB_HEADSET_DGTL); + if (uei.checkSwitchExists()) { + retVal.add(uei); + } else { + Slog.w(TAG, "This kernel does not have usb audio support"); + } + + // Monitor HDMI + // + // If the kernel has support for the "hdmi_audio" switch, use that. It will be signalled + // only when the HDMI driver has a video mode configured, and the downstream sink indicates + // support for audio in its EDID. + // + // If the kernel does not have an "hdmi_audio" switch, just fall back on the older "hdmi" + // switch instead. + uei = new UEventInfo("hdmi_audio", BIT_HDMI_AUDIO, 0); + if (uei.checkSwitchExists()) { + retVal.add(uei); + } else { + uei = new UEventInfo("hdmi", BIT_HDMI_AUDIO, 0); + if (uei.checkSwitchExists()) { + retVal.add(uei); + } else { + Slog.w(TAG, "This kernel does not have HDMI audio support"); + } + } + + return retVal; + } + + private static List uEventInfo = makeObservedUEventList(); + private int mHeadsetState; private int mPrevHeadsetState; private String mHeadsetName; - private int switchState; private final Context mContext; private final WakeLock mWakeLock; // held while there is a pending route change @@ -85,11 +156,12 @@ class WiredAccessoryObserver extends UEventObserver { // one on the board, one on the dock and one on HDMI: // observe three UEVENTs init(); // set initial status - for (int i = 0; i < MAX_AUDIO_PORTS; i++) { - startObserving(uEventInfo[i][0]); + for (int i = 0; i < uEventInfo.size(); ++i) { + UEventInfo uei = uEventInfo.get(i); + startObserving(uei.getDevPath()); } } - } + } @Override public void onUEvent(UEventObserver.UEvent event) { @@ -106,50 +178,47 @@ class WiredAccessoryObserver extends UEventObserver { private synchronized final void updateState(String name, int state) { - if (name.equals("usb_audio")) { - switchState = ((mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC|BIT_HDMI_AUDIO)) | - ((state == 1) ? BIT_USB_HEADSET_ANLG : - ((state == 2) ? BIT_USB_HEADSET_DGTL : 0))); - } else if (name.equals("hdmi")) { - switchState = ((mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC| - BIT_USB_HEADSET_DGTL|BIT_USB_HEADSET_ANLG)) | - ((state == 1) ? BIT_HDMI_AUDIO : 0)); - } else { - switchState = ((mHeadsetState & (BIT_HDMI_AUDIO|BIT_USB_HEADSET_ANLG| - BIT_USB_HEADSET_DGTL)) | - ((state == 1) ? BIT_HEADSET : - ((state == 2) ? BIT_HEADSET_NO_MIC : 0))); + // FIXME: When ueventd informs of a change in state for a switch, it does not have to be + // the case that the name reported by /sys/class/switch//name is the same as + // . For normal users of the linux switch class driver, it will be. But it is + // technically possible to hook the print_name method in the class driver and return a + // different name each and every time the name sysfs entry is queried. + // + // Right now this is not the case for any of the switch implementations used here. I'm not + // certain anyone would ever choose to implement such a dynamic name, or what it would mean + // for the implementation at this level, but if it ever happens, we will need to revisit + // this code. + for (int i = 0; i < uEventInfo.size(); ++i) { + UEventInfo uei = uEventInfo.get(i); + if (name.equals(uei.getDevName())) { + update(name, uei.computeNewHeadsetState(mHeadsetState, state)); + return; + } } - update(name, switchState); } private synchronized final void init() { char[] buffer = new char[1024]; - - String newName = mHeadsetName; - int newState = mHeadsetState; mPrevHeadsetState = mHeadsetState; if (LOG) Slog.v(TAG, "init()"); - for (int i = 0; i < MAX_AUDIO_PORTS; i++) { + for (int i = 0; i < uEventInfo.size(); ++i) { + UEventInfo uei = uEventInfo.get(i); try { - FileReader file = new FileReader(uEventInfo[i][1]); + int curState; + FileReader file = new FileReader(uei.getSwitchStatePath()); int len = file.read(buffer, 0, 1024); file.close(); - newState = Integer.valueOf((new String(buffer, 0, len)).trim()); + curState = Integer.valueOf((new String(buffer, 0, len)).trim()); - file = new FileReader(uEventInfo[i][2]); - len = file.read(buffer, 0, 1024); - file.close(); - newName = new String(buffer, 0, len).trim(); - - if (newState > 0) { - updateState(newName, newState); + if (curState > 0) { + updateState(uei.getDevName(), curState); } } catch (FileNotFoundException e) { - Slog.w(TAG, "This kernel does not have wired headset support"); + Slog.w(TAG, uei.getSwitchStatePath() + + " not found while attempting to determine initial switch state"); } catch (Exception e) { Slog.e(TAG, "" , e); }