Loading core/res/res/values/config.xml +4 −0 Original line number Diff line number Diff line Loading @@ -960,4 +960,8 @@ --> <bool name="config_enableWifiDisplay">false</bool> <!-- When true use the linux /dev/input/event subsystem to detect the switch changes on the headphone/microphone jack. When false use the older uevent framework. --> <bool name="config_useDevInputEventForAudioJack">false</bool> </resources> core/res/res/values/symbols.xml +2 −1 Original line number Diff line number Diff line Loading @@ -271,6 +271,7 @@ <java-symbol type="bool" name="config_enableScreenshotChord" /> <java-symbol type="bool" name="config_bluetooth_default_profiles" /> <java-symbol type="bool" name="config_enableWifiDisplay" /> <java-symbol type="bool" name="config_useDevInputEventForAudioJack" /> <java-symbol type="integer" name="config_cursorWindowSize" /> <java-symbol type="integer" name="config_longPressOnPowerBehavior" /> Loading services/java/com/android/server/SystemServer.java +4 −3 Original line number Diff line number Diff line Loading @@ -631,11 +631,12 @@ class ServerThread extends Thread { } try { Slog.i(TAG, "Wired Accessory Observer"); Slog.i(TAG, "Wired Accessory Manager"); // Listen for wired headset changes new WiredAccessoryObserver(context); inputManager.setWiredAccessoryCallbacks( new WiredAccessoryManager(context, inputManager)); } catch (Throwable e) { reportWtf("starting WiredAccessoryObserver", e); reportWtf("starting WiredAccessoryManager", e); } try { Loading services/java/com/android/server/WiredAccessoryObserver.java→services/java/com/android/server/WiredAccessoryManager.java +432 −0 Original line number Diff line number Diff line Loading @@ -29,6 +29,15 @@ import android.os.UEventObserver; import android.util.Slog; import android.media.AudioManager; import android.util.Log; import android.view.InputDevice; import com.android.internal.R; import com.android.server.input.InputManagerService; import com.android.server.input.InputManagerService.WiredAccessoryCallbacks; import static com.android.server.input.InputManagerService.SW_HEADPHONE_INSERT; import static com.android.server.input.InputManagerService.SW_MICROPHONE_INSERT; import static com.android.server.input.InputManagerService.SW_HEADPHONE_INSERT_BIT; import static com.android.server.input.InputManagerService.SW_MICROPHONE_INSERT_BIT; import java.io.File; import java.io.FileReader; Loading @@ -37,11 +46,14 @@ import java.util.ArrayList; import java.util.List; /** * <p>WiredAccessoryObserver monitors for a wired headset on the main board or dock. * <p>WiredAccessoryManager monitors for a wired headset on the main board or dock using * both the InputManagerService notifyWiredAccessoryChanged interface and the UEventObserver * subsystem. */ final class WiredAccessoryObserver extends UEventObserver { private static final String TAG = WiredAccessoryObserver.class.getSimpleName(); final class WiredAccessoryManager implements WiredAccessoryCallbacks { private static final String TAG = WiredAccessoryManager.class.getSimpleName(); private static final boolean LOG = true; 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); Loading @@ -51,94 +63,108 @@ final class WiredAccessoryObserver extends UEventObserver { BIT_USB_HEADSET_ANLG|BIT_USB_HEADSET_DGTL| BIT_HDMI_AUDIO); private static final String NAME_H2W = "h2w"; private static final String NAME_USB_AUDIO = "usb_audio"; private static final String NAME_HDMI_AUDIO = "hdmi_audio"; private static final String NAME_HDMI = "hdmi"; private static final int MSG_NEW_DEVICE_STATE = 1; private final Object mLock = new Object(); private final Context mContext; private final WakeLock mWakeLock; // held while there is a pending route change private final AudioManager mAudioManager; private final List<UEventInfo> mUEventInfo; private int mHeadsetState; private int mPrevHeadsetState; private String mHeadsetName; public WiredAccessoryObserver(Context context) { mContext = context; private int mSwitchValues; private final WiredAccessoryObserver mObserver; private final InputManagerService mInputManager; private final boolean mUseDevInputEventForAudioJack; public WiredAccessoryManager(Context context, InputManagerService inputManager) { PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WiredAccessoryObserver"); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WiredAccessoryManager"); mWakeLock.setReferenceCounted(false); mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE); mInputManager = inputManager; mUEventInfo = makeObservedUEventList(); mUseDevInputEventForAudioJack = context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack); context.registerReceiver(new BootCompletedReceiver(), new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null); } mObserver = new WiredAccessoryObserver(); context.registerReceiver(new BroadcastReceiver() { @Override public void onUEvent(UEventObserver.UEvent event) { if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString()); public void onReceive(Context ctx, Intent intent) { bootCompleted(); } }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null); } try { String devPath = event.get("DEVPATH"); String name = event.get("SWITCH_NAME"); int state = Integer.parseInt(event.get("SWITCH_STATE")); synchronized (mLock) { updateStateLocked(devPath, name, state); private void bootCompleted() { if (mUseDevInputEventForAudioJack) { int switchValues = 0; if (mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY, SW_HEADPHONE_INSERT) == 1) { switchValues |= SW_HEADPHONE_INSERT_BIT; } } catch (NumberFormatException e) { Slog.e(TAG, "Could not parse switch state from event " + event); if (mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY, SW_MICROPHONE_INSERT) == 1) { switchValues |= SW_MICROPHONE_INSERT_BIT; } notifyWiredAccessoryChanged(0, switchValues, SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT); } private void bootCompleted() { mObserver.init(); } @Override public void notifyWiredAccessoryChanged(long whenNanos, int switchValues, int switchMask) { if (LOG) Slog.v(TAG, "notifyWiredAccessoryChanged: when=" + whenNanos + " bits=" + switchCodeToString(switchValues, switchMask) + " mask=" + Integer.toHexString(switchMask)); synchronized (mLock) { char[] buffer = new char[1024]; mPrevHeadsetState = mHeadsetState; int headset; mSwitchValues = (mSwitchValues & ~switchMask) | switchValues; switch (mSwitchValues & (SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT)) { case 0: headset = 0; break; if (LOG) Slog.v(TAG, "init()"); case SW_HEADPHONE_INSERT_BIT: headset = BIT_HEADSET_NO_MIC; break; for (int i = 0; i < mUEventInfo.size(); ++i) { UEventInfo uei = mUEventInfo.get(i); try { int curState; FileReader file = new FileReader(uei.getSwitchStatePath()); int len = file.read(buffer, 0, 1024); file.close(); curState = Integer.valueOf((new String(buffer, 0, len)).trim()); case SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT: headset = BIT_HEADSET; break; if (curState > 0) { updateStateLocked(uei.getDevPath(), uei.getDevName(), curState); } } catch (FileNotFoundException e) { Slog.w(TAG, uei.getSwitchStatePath() + " not found while attempting to determine initial switch state"); } catch (Exception e) { Slog.e(TAG, "" , e); } } } case SW_MICROPHONE_INSERT_BIT: headset = BIT_HEADSET; break; // At any given time accessories could be inserted // one on the board, one on the dock and one on HDMI: // observe three UEVENTs for (int i = 0; i < mUEventInfo.size(); ++i) { UEventInfo uei = mUEventInfo.get(i); startObserving("DEVPATH="+uei.getDevPath()); } default: headset = 0; break; } private void updateStateLocked(String devPath, String name, int state) { for (int i = 0; i < mUEventInfo.size(); ++i) { UEventInfo uei = mUEventInfo.get(i); if (devPath.equals(uei.getDevPath())) { updateLocked(name, uei.computeNewHeadsetState(mHeadsetState, state)); return; } updateLocked(NAME_H2W, headset); } } /** * Compare the existing headset state with the new state and pass along accordingly. Note * that this only supports a single headset at a time. Inserting both a usb and jacked headset * results in support for the last one plugged in. Similarly, unplugging either is seen as * unplugging all. * * @param newName One of the NAME_xxx variables defined above. * @param newState 0 or one of the BIT_xxx variables defined above. */ private void updateLocked(String newName, int newState) { // Retain only relevant bits int headsetState = newState & SUPPORTED_HEADSETS; Loading @@ -147,19 +173,27 @@ final class WiredAccessoryObserver extends UEventObserver { int h2w_headset = headsetState & (BIT_HEADSET | BIT_HEADSET_NO_MIC); boolean h2wStateChange = true; boolean usbStateChange = true; if (LOG) Slog.v(TAG, "newName=" + newName + " newState=" + newState + " headsetState=" + headsetState + " prev headsetState=" + mHeadsetState); if (mHeadsetState == headsetState) { Log.e(TAG, "No state change."); return; } // reject all suspect transitions: only accept state changes from: // - a: 0 heaset to 1 headset // - a: 0 headset to 1 headset // - b: 1 headset to 0 headset if (LOG) Slog.v(TAG, "newState = "+newState+", headsetState = "+headsetState+"," + "mHeadsetState = "+mHeadsetState); if (mHeadsetState == headsetState || ((h2w_headset & (h2w_headset - 1)) != 0)) { Log.e(TAG, "unsetting h2w flag"); if (h2w_headset == (BIT_HEADSET | BIT_HEADSET_NO_MIC)) { Log.e(TAG, "Invalid combination, unsetting h2w flag"); h2wStateChange = false; } // - c: 0 usb headset to 1 usb headset // - d: 1 usb headset to 0 usb headset if ((usb_headset_anlg >> 2) == 1 && (usb_headset_dgtl >> 3) == 1) { Log.e(TAG, "unsetting usb flag"); if (usb_headset_anlg == BIT_USB_HEADSET_ANLG && usb_headset_dgtl == BIT_USB_HEADSET_DGTL) { Log.e(TAG, "Invalid combination, unsetting usb flag"); usbStateChange = false; } if (!h2wStateChange && !usbStateChange) { Loading @@ -167,16 +201,26 @@ final class WiredAccessoryObserver extends UEventObserver { return; } mHeadsetName = newName; mPrevHeadsetState = mHeadsetState; mHeadsetState = headsetState; mWakeLock.acquire(); Message msg = mHandler.obtainMessage(0, mHeadsetState, mPrevHeadsetState, mHeadsetName); Message msg = mHandler.obtainMessage(MSG_NEW_DEVICE_STATE, headsetState, mHeadsetState, newName); mHandler.sendMessage(msg); mHeadsetState = headsetState; } private final Handler mHandler = new Handler(Looper.myLooper(), null, true) { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_NEW_DEVICE_STATE: setDevicesState(msg.arg1, msg.arg2, (String)msg.obj); mWakeLock.release(); } } }; private void setDevicesState( int headsetState, int prevHeadsetState, String headsetName) { synchronized (mLock) { Loading Loading @@ -224,20 +268,77 @@ final class WiredAccessoryObserver extends UEventObserver { } } private static List<UEventInfo> makeObservedUEventList() { private String switchCodeToString(int switchValues, int switchMask) { StringBuffer sb = new StringBuffer(); if ((switchMask & SW_HEADPHONE_INSERT_BIT) != 0 && (switchValues & SW_HEADPHONE_INSERT_BIT) != 0) { sb.append("SW_HEADPHONE_INSERT "); } if ((switchMask & SW_MICROPHONE_INSERT_BIT) != 0 && (switchValues & SW_MICROPHONE_INSERT_BIT) != 0) { sb.append("SW_MICROPHONE_INSERT"); } return sb.toString(); } class WiredAccessoryObserver extends UEventObserver { private final List<UEventInfo> mUEventInfo; public WiredAccessoryObserver() { mUEventInfo = makeObservedUEventList(); } void init() { synchronized (mLock) { if (LOG) Slog.v(TAG, "init()"); char[] buffer = new char[1024]; for (int i = 0; i < mUEventInfo.size(); ++i) { UEventInfo uei = mUEventInfo.get(i); try { int curState; FileReader file = new FileReader(uei.getSwitchStatePath()); int len = file.read(buffer, 0, 1024); file.close(); curState = Integer.valueOf((new String(buffer, 0, len)).trim()); if (curState > 0) { updateStateLocked(uei.getDevPath(), uei.getDevName(), curState); } } catch (FileNotFoundException e) { Slog.w(TAG, uei.getSwitchStatePath() + " not found while attempting to determine initial switch state"); } catch (Exception e) { Slog.e(TAG, "" , e); } } } // At any given time accessories could be inserted // one on the board, one on the dock and one on HDMI: // observe three UEVENTs for (int i = 0; i < mUEventInfo.size(); ++i) { UEventInfo uei = mUEventInfo.get(i); startObserving("DEVPATH="+uei.getDevPath()); } } private List<UEventInfo> makeObservedUEventList() { List<UEventInfo> retVal = new ArrayList<UEventInfo>(); UEventInfo uei; // Monitor h2w uei = new UEventInfo("h2w", BIT_HEADSET, BIT_HEADSET_NO_MIC); if (!mUseDevInputEventForAudioJack) { uei = new UEventInfo(NAME_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); uei = new UEventInfo(NAME_USB_AUDIO, BIT_USB_HEADSET_ANLG, BIT_USB_HEADSET_DGTL); if (uei.checkSwitchExists()) { retVal.add(uei); } else { Loading @@ -246,17 +347,17 @@ final class WiredAccessoryObserver extends UEventObserver { // 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 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 the kernel does not have an "hdmi_audio" switch, just fall back on the older // "hdmi" switch instead. uei = new UEventInfo(NAME_HDMI_AUDIO, BIT_HDMI_AUDIO, 0); if (uei.checkSwitchExists()) { retVal.add(uei); } else { uei = new UEventInfo("hdmi", BIT_HDMI_AUDIO, 0); uei = new UEventInfo(NAME_HDMI, BIT_HDMI_AUDIO, 0); if (uei.checkSwitchExists()) { retVal.add(uei); } else { Loading @@ -267,15 +368,33 @@ final class WiredAccessoryObserver extends UEventObserver { return retVal; } private final Handler mHandler = new Handler(Looper.myLooper(), null, true) { @Override public void handleMessage(Message msg) { setDevicesState(msg.arg1, msg.arg2, (String)msg.obj); mWakeLock.release(); public void onUEvent(UEventObserver.UEvent event) { if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString()); try { String devPath = event.get("DEVPATH"); String name = event.get("SWITCH_NAME"); int state = Integer.parseInt(event.get("SWITCH_STATE")); synchronized (mLock) { updateStateLocked(devPath, name, state); } } catch (NumberFormatException e) { Slog.e(TAG, "Could not parse switch state from event " + event); } } private void updateStateLocked(String devPath, String name, int state) { for (int i = 0; i < mUEventInfo.size(); ++i) { UEventInfo uei = mUEventInfo.get(i); if (devPath.equals(uei.getDevPath())) { updateLocked(name, uei.computeNewHeadsetState(mHeadsetState, state)); return; } } } }; private static final class UEventInfo { private final class UEventInfo { private final String mDevName; private final int mState1Bits; private final int mState2Bits; Loading @@ -298,7 +417,7 @@ final class WiredAccessoryObserver extends UEventObserver { public boolean checkSwitchExists() { File f = new File(getSwitchStatePath()); return ((null != f) && f.exists()); return f.exists(); } public int computeNewHeadsetState(int headsetState, int switchState) { Loading @@ -309,11 +428,5 @@ final class WiredAccessoryObserver extends UEventObserver { return ((headsetState & preserveMask) | setBits); } } private final class BootCompletedReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { bootCompleted(); } } } services/java/com/android/server/input/InputManagerService.java +50 −8 Original line number Diff line number Diff line Loading @@ -113,6 +113,7 @@ public class InputManagerService extends IInputManager.Stub private final InputManagerHandler mHandler; private WindowManagerCallbacks mWindowManagerCallbacks; private WiredAccessoryCallbacks mWiredAccessoryCallbacks; private boolean mSystemReady; private NotificationManager mNotificationManager; Loading Loading @@ -213,17 +214,41 @@ public class InputManagerService extends IInputManager.Stub /** Scan code: Mouse / trackball button. */ public static final int BTN_MOUSE = 0x110; // Switch code values must match bionic/libc/kernel/common/linux/input.h /** Switch code: Lid switch. When set, lid is shut. */ public static final int SW_LID = 0x00; /** Switch code: Keypad slide. When set, keyboard is exposed. */ public static final int SW_KEYPAD_SLIDE = 0x0a; /** Switch code: Headphone. When set, headphone is inserted. */ public static final int SW_HEADPHONE_INSERT = 0x02; /** Switch code: Microphone. When set, microphone is inserted. */ public static final int SW_MICROPHONE_INSERT = 0x04; /** Switch code: Headphone/Microphone Jack. When set, something is inserted. */ public static final int SW_JACK_PHYSICAL_INSERT = 0x07; public static final int SW_LID_BIT = 1 << SW_LID; public static final int SW_KEYPAD_SLIDE_BIT = 1 << SW_KEYPAD_SLIDE; public static final int SW_HEADPHONE_INSERT_BIT = 1 << SW_HEADPHONE_INSERT; public static final int SW_MICROPHONE_INSERT_BIT = 1 << SW_MICROPHONE_INSERT; public static final int SW_JACK_PHYSICAL_INSERT_BIT = 1 << SW_JACK_PHYSICAL_INSERT; public static final int SW_JACK_BITS = SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT | SW_JACK_PHYSICAL_INSERT_BIT; /** Whether to use the dev/input/event or uevent subsystem for the audio jack. */ final boolean mUseDevInputEventForAudioJack; public InputManagerService(Context context, Handler handler) { this.mContext = context; this.mHandler = new InputManagerHandler(handler.getLooper()); Slog.i(TAG, "Initializing input manager"); mUseDevInputEventForAudioJack = context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack); Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack=" + mUseDevInputEventForAudioJack); mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue()); } Loading @@ -231,6 +256,10 @@ public class InputManagerService extends IInputManager.Stub mWindowManagerCallbacks = callbacks; } public void setWiredAccessoryCallbacks(WiredAccessoryCallbacks callbacks) { mWiredAccessoryCallbacks = callbacks; } public void start() { Slog.i(TAG, "Starting input manager"); nativeStart(mPtr); Loading Loading @@ -513,7 +542,7 @@ public class InputManagerService extends IInputManager.Stub /** * Gets information about the input device with the specified id. * @param id The device id. * @param deviceId The device id. * @return The input device or null if not found. */ @Override // Binder call Loading Loading @@ -976,8 +1005,8 @@ public class InputManagerService extends IInputManager.Stub // Must be called on handler. private void handleSwitchKeyboardLayout(int deviceId, int direction) { final InputDevice device = getInputDevice(deviceId); final String inputDeviceDescriptor = device.getDescriptor(); if (device != null) { final String inputDeviceDescriptor = device.getDescriptor(); final boolean changed; final String keyboardLayoutDescriptor; synchronized (mDataStore) { Loading Loading @@ -1214,6 +1243,7 @@ public class InputManagerService extends IInputManager.Stub } // Called by the heartbeat to ensure locks are not held indefinitely (for deadlock detection). @Override public void monitor() { synchronized (mInputFilterLock) { } nativeMonitor(mPtr); Loading Loading @@ -1244,10 +1274,15 @@ public class InputManagerService extends IInputManager.Stub + ", mask=" + Integer.toHexString(switchMask)); } if ((switchMask & (1 << SW_LID)) != 0) { final boolean lidOpen = ((switchValues & (1 << SW_LID)) == 0); if ((switchMask & SW_LID_BIT) != 0) { final boolean lidOpen = ((switchValues & SW_LID_BIT) == 0); mWindowManagerCallbacks.notifyLidSwitchChanged(whenNanos, lidOpen); } if (mUseDevInputEventForAudioJack && (switchMask & SW_JACK_BITS) != 0) { mWiredAccessoryCallbacks.notifyWiredAccessoryChanged(whenNanos, switchValues, switchMask); } } // Native callback. Loading Loading @@ -1431,7 +1466,6 @@ public class InputManagerService extends IInputManager.Stub return null; } /** * Callback interface implemented by the Window Manager. */ Loading @@ -1458,6 +1492,13 @@ public class InputManagerService extends IInputManager.Stub public int getPointerLayer(); } /** * Callback interface implemented by WiredAccessoryObserver. */ public interface WiredAccessoryCallbacks { public void notifyWiredAccessoryChanged(long whenNanos, int switchValues, int switchMask); } /** * Private handler for the input manager. */ Loading Loading @@ -1498,6 +1539,7 @@ public class InputManagerService extends IInputManager.Stub mDisconnected = true; } @Override public void sendInputEvent(InputEvent event, int policyFlags) { if (event == null) { throw new IllegalArgumentException("event must not be null"); Loading Loading
core/res/res/values/config.xml +4 −0 Original line number Diff line number Diff line Loading @@ -960,4 +960,8 @@ --> <bool name="config_enableWifiDisplay">false</bool> <!-- When true use the linux /dev/input/event subsystem to detect the switch changes on the headphone/microphone jack. When false use the older uevent framework. --> <bool name="config_useDevInputEventForAudioJack">false</bool> </resources>
core/res/res/values/symbols.xml +2 −1 Original line number Diff line number Diff line Loading @@ -271,6 +271,7 @@ <java-symbol type="bool" name="config_enableScreenshotChord" /> <java-symbol type="bool" name="config_bluetooth_default_profiles" /> <java-symbol type="bool" name="config_enableWifiDisplay" /> <java-symbol type="bool" name="config_useDevInputEventForAudioJack" /> <java-symbol type="integer" name="config_cursorWindowSize" /> <java-symbol type="integer" name="config_longPressOnPowerBehavior" /> Loading
services/java/com/android/server/SystemServer.java +4 −3 Original line number Diff line number Diff line Loading @@ -631,11 +631,12 @@ class ServerThread extends Thread { } try { Slog.i(TAG, "Wired Accessory Observer"); Slog.i(TAG, "Wired Accessory Manager"); // Listen for wired headset changes new WiredAccessoryObserver(context); inputManager.setWiredAccessoryCallbacks( new WiredAccessoryManager(context, inputManager)); } catch (Throwable e) { reportWtf("starting WiredAccessoryObserver", e); reportWtf("starting WiredAccessoryManager", e); } try { Loading
services/java/com/android/server/WiredAccessoryObserver.java→services/java/com/android/server/WiredAccessoryManager.java +432 −0 Original line number Diff line number Diff line Loading @@ -29,6 +29,15 @@ import android.os.UEventObserver; import android.util.Slog; import android.media.AudioManager; import android.util.Log; import android.view.InputDevice; import com.android.internal.R; import com.android.server.input.InputManagerService; import com.android.server.input.InputManagerService.WiredAccessoryCallbacks; import static com.android.server.input.InputManagerService.SW_HEADPHONE_INSERT; import static com.android.server.input.InputManagerService.SW_MICROPHONE_INSERT; import static com.android.server.input.InputManagerService.SW_HEADPHONE_INSERT_BIT; import static com.android.server.input.InputManagerService.SW_MICROPHONE_INSERT_BIT; import java.io.File; import java.io.FileReader; Loading @@ -37,11 +46,14 @@ import java.util.ArrayList; import java.util.List; /** * <p>WiredAccessoryObserver monitors for a wired headset on the main board or dock. * <p>WiredAccessoryManager monitors for a wired headset on the main board or dock using * both the InputManagerService notifyWiredAccessoryChanged interface and the UEventObserver * subsystem. */ final class WiredAccessoryObserver extends UEventObserver { private static final String TAG = WiredAccessoryObserver.class.getSimpleName(); final class WiredAccessoryManager implements WiredAccessoryCallbacks { private static final String TAG = WiredAccessoryManager.class.getSimpleName(); private static final boolean LOG = true; 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); Loading @@ -51,94 +63,108 @@ final class WiredAccessoryObserver extends UEventObserver { BIT_USB_HEADSET_ANLG|BIT_USB_HEADSET_DGTL| BIT_HDMI_AUDIO); private static final String NAME_H2W = "h2w"; private static final String NAME_USB_AUDIO = "usb_audio"; private static final String NAME_HDMI_AUDIO = "hdmi_audio"; private static final String NAME_HDMI = "hdmi"; private static final int MSG_NEW_DEVICE_STATE = 1; private final Object mLock = new Object(); private final Context mContext; private final WakeLock mWakeLock; // held while there is a pending route change private final AudioManager mAudioManager; private final List<UEventInfo> mUEventInfo; private int mHeadsetState; private int mPrevHeadsetState; private String mHeadsetName; public WiredAccessoryObserver(Context context) { mContext = context; private int mSwitchValues; private final WiredAccessoryObserver mObserver; private final InputManagerService mInputManager; private final boolean mUseDevInputEventForAudioJack; public WiredAccessoryManager(Context context, InputManagerService inputManager) { PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WiredAccessoryObserver"); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WiredAccessoryManager"); mWakeLock.setReferenceCounted(false); mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE); mInputManager = inputManager; mUEventInfo = makeObservedUEventList(); mUseDevInputEventForAudioJack = context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack); context.registerReceiver(new BootCompletedReceiver(), new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null); } mObserver = new WiredAccessoryObserver(); context.registerReceiver(new BroadcastReceiver() { @Override public void onUEvent(UEventObserver.UEvent event) { if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString()); public void onReceive(Context ctx, Intent intent) { bootCompleted(); } }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null); } try { String devPath = event.get("DEVPATH"); String name = event.get("SWITCH_NAME"); int state = Integer.parseInt(event.get("SWITCH_STATE")); synchronized (mLock) { updateStateLocked(devPath, name, state); private void bootCompleted() { if (mUseDevInputEventForAudioJack) { int switchValues = 0; if (mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY, SW_HEADPHONE_INSERT) == 1) { switchValues |= SW_HEADPHONE_INSERT_BIT; } } catch (NumberFormatException e) { Slog.e(TAG, "Could not parse switch state from event " + event); if (mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY, SW_MICROPHONE_INSERT) == 1) { switchValues |= SW_MICROPHONE_INSERT_BIT; } notifyWiredAccessoryChanged(0, switchValues, SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT); } private void bootCompleted() { mObserver.init(); } @Override public void notifyWiredAccessoryChanged(long whenNanos, int switchValues, int switchMask) { if (LOG) Slog.v(TAG, "notifyWiredAccessoryChanged: when=" + whenNanos + " bits=" + switchCodeToString(switchValues, switchMask) + " mask=" + Integer.toHexString(switchMask)); synchronized (mLock) { char[] buffer = new char[1024]; mPrevHeadsetState = mHeadsetState; int headset; mSwitchValues = (mSwitchValues & ~switchMask) | switchValues; switch (mSwitchValues & (SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT)) { case 0: headset = 0; break; if (LOG) Slog.v(TAG, "init()"); case SW_HEADPHONE_INSERT_BIT: headset = BIT_HEADSET_NO_MIC; break; for (int i = 0; i < mUEventInfo.size(); ++i) { UEventInfo uei = mUEventInfo.get(i); try { int curState; FileReader file = new FileReader(uei.getSwitchStatePath()); int len = file.read(buffer, 0, 1024); file.close(); curState = Integer.valueOf((new String(buffer, 0, len)).trim()); case SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT: headset = BIT_HEADSET; break; if (curState > 0) { updateStateLocked(uei.getDevPath(), uei.getDevName(), curState); } } catch (FileNotFoundException e) { Slog.w(TAG, uei.getSwitchStatePath() + " not found while attempting to determine initial switch state"); } catch (Exception e) { Slog.e(TAG, "" , e); } } } case SW_MICROPHONE_INSERT_BIT: headset = BIT_HEADSET; break; // At any given time accessories could be inserted // one on the board, one on the dock and one on HDMI: // observe three UEVENTs for (int i = 0; i < mUEventInfo.size(); ++i) { UEventInfo uei = mUEventInfo.get(i); startObserving("DEVPATH="+uei.getDevPath()); } default: headset = 0; break; } private void updateStateLocked(String devPath, String name, int state) { for (int i = 0; i < mUEventInfo.size(); ++i) { UEventInfo uei = mUEventInfo.get(i); if (devPath.equals(uei.getDevPath())) { updateLocked(name, uei.computeNewHeadsetState(mHeadsetState, state)); return; } updateLocked(NAME_H2W, headset); } } /** * Compare the existing headset state with the new state and pass along accordingly. Note * that this only supports a single headset at a time. Inserting both a usb and jacked headset * results in support for the last one plugged in. Similarly, unplugging either is seen as * unplugging all. * * @param newName One of the NAME_xxx variables defined above. * @param newState 0 or one of the BIT_xxx variables defined above. */ private void updateLocked(String newName, int newState) { // Retain only relevant bits int headsetState = newState & SUPPORTED_HEADSETS; Loading @@ -147,19 +173,27 @@ final class WiredAccessoryObserver extends UEventObserver { int h2w_headset = headsetState & (BIT_HEADSET | BIT_HEADSET_NO_MIC); boolean h2wStateChange = true; boolean usbStateChange = true; if (LOG) Slog.v(TAG, "newName=" + newName + " newState=" + newState + " headsetState=" + headsetState + " prev headsetState=" + mHeadsetState); if (mHeadsetState == headsetState) { Log.e(TAG, "No state change."); return; } // reject all suspect transitions: only accept state changes from: // - a: 0 heaset to 1 headset // - a: 0 headset to 1 headset // - b: 1 headset to 0 headset if (LOG) Slog.v(TAG, "newState = "+newState+", headsetState = "+headsetState+"," + "mHeadsetState = "+mHeadsetState); if (mHeadsetState == headsetState || ((h2w_headset & (h2w_headset - 1)) != 0)) { Log.e(TAG, "unsetting h2w flag"); if (h2w_headset == (BIT_HEADSET | BIT_HEADSET_NO_MIC)) { Log.e(TAG, "Invalid combination, unsetting h2w flag"); h2wStateChange = false; } // - c: 0 usb headset to 1 usb headset // - d: 1 usb headset to 0 usb headset if ((usb_headset_anlg >> 2) == 1 && (usb_headset_dgtl >> 3) == 1) { Log.e(TAG, "unsetting usb flag"); if (usb_headset_anlg == BIT_USB_HEADSET_ANLG && usb_headset_dgtl == BIT_USB_HEADSET_DGTL) { Log.e(TAG, "Invalid combination, unsetting usb flag"); usbStateChange = false; } if (!h2wStateChange && !usbStateChange) { Loading @@ -167,16 +201,26 @@ final class WiredAccessoryObserver extends UEventObserver { return; } mHeadsetName = newName; mPrevHeadsetState = mHeadsetState; mHeadsetState = headsetState; mWakeLock.acquire(); Message msg = mHandler.obtainMessage(0, mHeadsetState, mPrevHeadsetState, mHeadsetName); Message msg = mHandler.obtainMessage(MSG_NEW_DEVICE_STATE, headsetState, mHeadsetState, newName); mHandler.sendMessage(msg); mHeadsetState = headsetState; } private final Handler mHandler = new Handler(Looper.myLooper(), null, true) { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_NEW_DEVICE_STATE: setDevicesState(msg.arg1, msg.arg2, (String)msg.obj); mWakeLock.release(); } } }; private void setDevicesState( int headsetState, int prevHeadsetState, String headsetName) { synchronized (mLock) { Loading Loading @@ -224,20 +268,77 @@ final class WiredAccessoryObserver extends UEventObserver { } } private static List<UEventInfo> makeObservedUEventList() { private String switchCodeToString(int switchValues, int switchMask) { StringBuffer sb = new StringBuffer(); if ((switchMask & SW_HEADPHONE_INSERT_BIT) != 0 && (switchValues & SW_HEADPHONE_INSERT_BIT) != 0) { sb.append("SW_HEADPHONE_INSERT "); } if ((switchMask & SW_MICROPHONE_INSERT_BIT) != 0 && (switchValues & SW_MICROPHONE_INSERT_BIT) != 0) { sb.append("SW_MICROPHONE_INSERT"); } return sb.toString(); } class WiredAccessoryObserver extends UEventObserver { private final List<UEventInfo> mUEventInfo; public WiredAccessoryObserver() { mUEventInfo = makeObservedUEventList(); } void init() { synchronized (mLock) { if (LOG) Slog.v(TAG, "init()"); char[] buffer = new char[1024]; for (int i = 0; i < mUEventInfo.size(); ++i) { UEventInfo uei = mUEventInfo.get(i); try { int curState; FileReader file = new FileReader(uei.getSwitchStatePath()); int len = file.read(buffer, 0, 1024); file.close(); curState = Integer.valueOf((new String(buffer, 0, len)).trim()); if (curState > 0) { updateStateLocked(uei.getDevPath(), uei.getDevName(), curState); } } catch (FileNotFoundException e) { Slog.w(TAG, uei.getSwitchStatePath() + " not found while attempting to determine initial switch state"); } catch (Exception e) { Slog.e(TAG, "" , e); } } } // At any given time accessories could be inserted // one on the board, one on the dock and one on HDMI: // observe three UEVENTs for (int i = 0; i < mUEventInfo.size(); ++i) { UEventInfo uei = mUEventInfo.get(i); startObserving("DEVPATH="+uei.getDevPath()); } } private List<UEventInfo> makeObservedUEventList() { List<UEventInfo> retVal = new ArrayList<UEventInfo>(); UEventInfo uei; // Monitor h2w uei = new UEventInfo("h2w", BIT_HEADSET, BIT_HEADSET_NO_MIC); if (!mUseDevInputEventForAudioJack) { uei = new UEventInfo(NAME_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); uei = new UEventInfo(NAME_USB_AUDIO, BIT_USB_HEADSET_ANLG, BIT_USB_HEADSET_DGTL); if (uei.checkSwitchExists()) { retVal.add(uei); } else { Loading @@ -246,17 +347,17 @@ final class WiredAccessoryObserver extends UEventObserver { // 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 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 the kernel does not have an "hdmi_audio" switch, just fall back on the older // "hdmi" switch instead. uei = new UEventInfo(NAME_HDMI_AUDIO, BIT_HDMI_AUDIO, 0); if (uei.checkSwitchExists()) { retVal.add(uei); } else { uei = new UEventInfo("hdmi", BIT_HDMI_AUDIO, 0); uei = new UEventInfo(NAME_HDMI, BIT_HDMI_AUDIO, 0); if (uei.checkSwitchExists()) { retVal.add(uei); } else { Loading @@ -267,15 +368,33 @@ final class WiredAccessoryObserver extends UEventObserver { return retVal; } private final Handler mHandler = new Handler(Looper.myLooper(), null, true) { @Override public void handleMessage(Message msg) { setDevicesState(msg.arg1, msg.arg2, (String)msg.obj); mWakeLock.release(); public void onUEvent(UEventObserver.UEvent event) { if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString()); try { String devPath = event.get("DEVPATH"); String name = event.get("SWITCH_NAME"); int state = Integer.parseInt(event.get("SWITCH_STATE")); synchronized (mLock) { updateStateLocked(devPath, name, state); } } catch (NumberFormatException e) { Slog.e(TAG, "Could not parse switch state from event " + event); } } private void updateStateLocked(String devPath, String name, int state) { for (int i = 0; i < mUEventInfo.size(); ++i) { UEventInfo uei = mUEventInfo.get(i); if (devPath.equals(uei.getDevPath())) { updateLocked(name, uei.computeNewHeadsetState(mHeadsetState, state)); return; } } } }; private static final class UEventInfo { private final class UEventInfo { private final String mDevName; private final int mState1Bits; private final int mState2Bits; Loading @@ -298,7 +417,7 @@ final class WiredAccessoryObserver extends UEventObserver { public boolean checkSwitchExists() { File f = new File(getSwitchStatePath()); return ((null != f) && f.exists()); return f.exists(); } public int computeNewHeadsetState(int headsetState, int switchState) { Loading @@ -309,11 +428,5 @@ final class WiredAccessoryObserver extends UEventObserver { return ((headsetState & preserveMask) | setBits); } } private final class BootCompletedReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { bootCompleted(); } } }
services/java/com/android/server/input/InputManagerService.java +50 −8 Original line number Diff line number Diff line Loading @@ -113,6 +113,7 @@ public class InputManagerService extends IInputManager.Stub private final InputManagerHandler mHandler; private WindowManagerCallbacks mWindowManagerCallbacks; private WiredAccessoryCallbacks mWiredAccessoryCallbacks; private boolean mSystemReady; private NotificationManager mNotificationManager; Loading Loading @@ -213,17 +214,41 @@ public class InputManagerService extends IInputManager.Stub /** Scan code: Mouse / trackball button. */ public static final int BTN_MOUSE = 0x110; // Switch code values must match bionic/libc/kernel/common/linux/input.h /** Switch code: Lid switch. When set, lid is shut. */ public static final int SW_LID = 0x00; /** Switch code: Keypad slide. When set, keyboard is exposed. */ public static final int SW_KEYPAD_SLIDE = 0x0a; /** Switch code: Headphone. When set, headphone is inserted. */ public static final int SW_HEADPHONE_INSERT = 0x02; /** Switch code: Microphone. When set, microphone is inserted. */ public static final int SW_MICROPHONE_INSERT = 0x04; /** Switch code: Headphone/Microphone Jack. When set, something is inserted. */ public static final int SW_JACK_PHYSICAL_INSERT = 0x07; public static final int SW_LID_BIT = 1 << SW_LID; public static final int SW_KEYPAD_SLIDE_BIT = 1 << SW_KEYPAD_SLIDE; public static final int SW_HEADPHONE_INSERT_BIT = 1 << SW_HEADPHONE_INSERT; public static final int SW_MICROPHONE_INSERT_BIT = 1 << SW_MICROPHONE_INSERT; public static final int SW_JACK_PHYSICAL_INSERT_BIT = 1 << SW_JACK_PHYSICAL_INSERT; public static final int SW_JACK_BITS = SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT | SW_JACK_PHYSICAL_INSERT_BIT; /** Whether to use the dev/input/event or uevent subsystem for the audio jack. */ final boolean mUseDevInputEventForAudioJack; public InputManagerService(Context context, Handler handler) { this.mContext = context; this.mHandler = new InputManagerHandler(handler.getLooper()); Slog.i(TAG, "Initializing input manager"); mUseDevInputEventForAudioJack = context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack); Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack=" + mUseDevInputEventForAudioJack); mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue()); } Loading @@ -231,6 +256,10 @@ public class InputManagerService extends IInputManager.Stub mWindowManagerCallbacks = callbacks; } public void setWiredAccessoryCallbacks(WiredAccessoryCallbacks callbacks) { mWiredAccessoryCallbacks = callbacks; } public void start() { Slog.i(TAG, "Starting input manager"); nativeStart(mPtr); Loading Loading @@ -513,7 +542,7 @@ public class InputManagerService extends IInputManager.Stub /** * Gets information about the input device with the specified id. * @param id The device id. * @param deviceId The device id. * @return The input device or null if not found. */ @Override // Binder call Loading Loading @@ -976,8 +1005,8 @@ public class InputManagerService extends IInputManager.Stub // Must be called on handler. private void handleSwitchKeyboardLayout(int deviceId, int direction) { final InputDevice device = getInputDevice(deviceId); final String inputDeviceDescriptor = device.getDescriptor(); if (device != null) { final String inputDeviceDescriptor = device.getDescriptor(); final boolean changed; final String keyboardLayoutDescriptor; synchronized (mDataStore) { Loading Loading @@ -1214,6 +1243,7 @@ public class InputManagerService extends IInputManager.Stub } // Called by the heartbeat to ensure locks are not held indefinitely (for deadlock detection). @Override public void monitor() { synchronized (mInputFilterLock) { } nativeMonitor(mPtr); Loading Loading @@ -1244,10 +1274,15 @@ public class InputManagerService extends IInputManager.Stub + ", mask=" + Integer.toHexString(switchMask)); } if ((switchMask & (1 << SW_LID)) != 0) { final boolean lidOpen = ((switchValues & (1 << SW_LID)) == 0); if ((switchMask & SW_LID_BIT) != 0) { final boolean lidOpen = ((switchValues & SW_LID_BIT) == 0); mWindowManagerCallbacks.notifyLidSwitchChanged(whenNanos, lidOpen); } if (mUseDevInputEventForAudioJack && (switchMask & SW_JACK_BITS) != 0) { mWiredAccessoryCallbacks.notifyWiredAccessoryChanged(whenNanos, switchValues, switchMask); } } // Native callback. Loading Loading @@ -1431,7 +1466,6 @@ public class InputManagerService extends IInputManager.Stub return null; } /** * Callback interface implemented by the Window Manager. */ Loading @@ -1458,6 +1492,13 @@ public class InputManagerService extends IInputManager.Stub public int getPointerLayer(); } /** * Callback interface implemented by WiredAccessoryObserver. */ public interface WiredAccessoryCallbacks { public void notifyWiredAccessoryChanged(long whenNanos, int switchValues, int switchMask); } /** * Private handler for the input manager. */ Loading Loading @@ -1498,6 +1539,7 @@ public class InputManagerService extends IInputManager.Stub mDisconnected = true; } @Override public void sendInputEvent(InputEvent event, int policyFlags) { if (event == null) { throw new IllegalArgumentException("event must not be null"); Loading