diff --git a/core/java/android/os/UEventObserver.java b/core/java/android/os/UEventObserver.java index b924e84958544..d33382b23c845 100644 --- a/core/java/android/os/UEventObserver.java +++ b/core/java/android/os/UEventObserver.java @@ -37,14 +37,79 @@ import java.util.HashMap; * @hide */ public abstract class UEventObserver { - private static final String TAG = UEventObserver.class.getSimpleName(); + private static UEventThread sThread; + + private static native void native_setup(); + private static native int next_event(byte[] buffer); + + public UEventObserver() { + } + + protected void finalize() throws Throwable { + try { + stopObserving(); + } finally { + super.finalize(); + } + } + + private static UEventThread getThread() { + synchronized (UEventObserver.class) { + if (sThread == null) { + sThread = new UEventThread(); + sThread.start(); + } + return sThread; + } + } + + private static UEventThread peekThread() { + synchronized (UEventObserver.class) { + return sThread; + } + } + + /** + * Begin observation of UEvent's.

+ * This method will cause the UEvent thread to start if this is the first + * invocation of startObserving in this process.

+ * Once called, the UEvent thread will call onUEvent() when an incoming + * UEvent matches the specified string.

+ * This method can be called multiple times to register multiple matches. + * Only one call to stopObserving is required even with multiple registered + * matches. + * @param match A substring of the UEvent to match. Use "" to match all + * UEvent's + */ + public final void startObserving(String match) { + final UEventThread t = getThread(); + t.addObserver(match, this); + } + + /** + * End observation of UEvent's.

+ * This process's UEvent thread will never call onUEvent() on this + * UEventObserver after this call. Repeated calls have no effect. + */ + public final void stopObserving() { + final UEventThread t = getThread(); + if (t != null) { + t.removeObserver(this); + } + } + + /** + * Subclasses of UEventObserver should override this method to handle + * UEvents. + */ + public abstract void onUEvent(UEvent event); /** * Representation of a UEvent. */ - static public class UEvent { + public static final class UEvent { // collection of key=value pairs parsed from the uevent message - public HashMap mMap = new HashMap(); + private final HashMap mMap = new HashMap(); public UEvent(String message) { int offset = 0; @@ -79,20 +144,20 @@ public abstract class UEventObserver { } } - private static UEventThread sThread; - private static boolean sThreadStarted = false; - - private static class UEventThread extends Thread { + private static final class UEventThread extends Thread { /** Many to many mapping of string match to observer. * Multimap would be better, but not available in android, so use * an ArrayList where even elements are the String match and odd * elements the corresponding UEventObserver observer */ - private ArrayList mObservers = new ArrayList(); - - UEventThread() { + private final ArrayList mKeysAndObservers = new ArrayList(); + + private final ArrayList mTempObserversToSignal = + new ArrayList(); + + public UEventThread() { super("UEventObserver"); } - + public void run() { native_setup(); @@ -101,91 +166,54 @@ public abstract class UEventObserver { while (true) { len = next_event(buffer); if (len > 0) { - String bufferStr = new String(buffer, 0, len); // easier to search a String - synchronized (mObservers) { - for (int i = 0; i < mObservers.size(); i += 2) { - if (bufferStr.indexOf((String)mObservers.get(i)) != -1) { - ((UEventObserver)mObservers.get(i+1)) - .onUEvent(new UEvent(bufferStr)); - } - } - } + sendEvent(new String(buffer, 0, len)); } } } - public void addObserver(String match, UEventObserver observer) { - synchronized(mObservers) { - mObservers.add(match); - mObservers.add(observer); + + private void sendEvent(String message) { + synchronized (mKeysAndObservers) { + final int N = mKeysAndObservers.size(); + for (int i = 0; i < N; i += 2) { + final String key = (String)mKeysAndObservers.get(i); + if (message.indexOf(key) != -1) { + final UEventObserver observer = + (UEventObserver)mKeysAndObservers.get(i + 1); + mTempObserversToSignal.add(observer); + } + } + } + + if (!mTempObserversToSignal.isEmpty()) { + final UEvent event = new UEvent(message); + final int N = mTempObserversToSignal.size(); + for (int i = 0; i < N; i++) { + final UEventObserver observer = mTempObserversToSignal.get(i); + observer.onUEvent(event); + } + mTempObserversToSignal.clear(); } } + + public void addObserver(String match, UEventObserver observer) { + synchronized (mKeysAndObservers) { + mKeysAndObservers.add(match); + mKeysAndObservers.add(observer); + } + } + /** Removes every key/value pair where value=observer from mObservers */ public void removeObserver(UEventObserver observer) { - synchronized(mObservers) { - boolean found = true; - while (found) { - found = false; - for (int i = 0; i < mObservers.size(); i += 2) { - if (mObservers.get(i+1) == observer) { - mObservers.remove(i+1); - mObservers.remove(i); - found = true; - break; - } + synchronized (mKeysAndObservers) { + for (int i = 0; i < mKeysAndObservers.size(); ) { + if (mKeysAndObservers.get(i + 1) == observer) { + mKeysAndObservers.remove(i + 1); + mKeysAndObservers.remove(i); + } else { + i += 2; } } } } } - - private static native void native_setup(); - private static native int next_event(byte[] buffer); - - private static final synchronized void ensureThreadStarted() { - if (sThreadStarted == false) { - sThread = new UEventThread(); - sThread.start(); - sThreadStarted = true; - } - } - - /** - * Begin observation of UEvent's.

- * This method will cause the UEvent thread to start if this is the first - * invocation of startObserving in this process.

- * Once called, the UEvent thread will call onUEvent() when an incoming - * UEvent matches the specified string.

- * This method can be called multiple times to register multiple matches. - * Only one call to stopObserving is required even with multiple registered - * matches. - * @param match A substring of the UEvent to match. Use "" to match all - * UEvent's - */ - public final synchronized void startObserving(String match) { - ensureThreadStarted(); - sThread.addObserver(match, this); - } - - /** - * End observation of UEvent's.

- * This process's UEvent thread will never call onUEvent() on this - * UEventObserver after this call. Repeated calls have no effect. - */ - public final synchronized void stopObserving() { - sThread.removeObserver(this); - } - - /** - * Subclasses of UEventObserver should override this method to handle - * UEvents. - */ - public abstract void onUEvent(UEvent event); - - protected void finalize() throws Throwable { - try { - stopObserving(); - } finally { - super.finalize(); - } - } } diff --git a/services/java/com/android/server/DockObserver.java b/services/java/com/android/server/DockObserver.java index 8bdd7be8e196a..ef09b010320e9 100644 --- a/services/java/com/android/server/DockObserver.java +++ b/services/java/com/android/server/DockObserver.java @@ -18,10 +18,6 @@ package com.android.server; import static android.provider.Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK; -import com.android.server.power.PowerManagerService; - -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -30,6 +26,7 @@ import android.media.Ringtone; import android.media.RingtoneManager; import android.net.Uri; import android.os.Handler; +import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; @@ -47,7 +44,7 @@ import java.io.FileReader; /** *

DockObserver monitors for a docking station. */ -class DockObserver extends UEventObserver { +final class DockObserver extends UEventObserver { private static final String TAG = DockObserver.class.getSimpleName(); private static final boolean LOG = false; @@ -56,7 +53,9 @@ class DockObserver extends UEventObserver { private static final int DEFAULT_DOCK = 1; - private static final int MSG_DOCK_STATE = 0; + private static final int MSG_DOCK_STATE_CHANGED = 0; + + private final Object mLock = new Object(); private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED; private int mPreviousDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED; @@ -78,7 +77,7 @@ class DockObserver extends UEventObserver { Slog.v(TAG, "Dock UEVENT: " + event.toString()); } - synchronized (this) { + synchronized (mLock) { try { int newState = Integer.parseInt(event.get("SWITCH_STATE")); if (newState != mDockState) { @@ -96,7 +95,7 @@ class DockObserver extends UEventObserver { (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); pm.wakeUp(SystemClock.uptimeMillis()); } - update(); + updateLocked(); } } } catch (NumberFormatException e) { @@ -105,132 +104,142 @@ class DockObserver extends UEventObserver { } } - private final void init() { - char[] buffer = new char[1024]; - - try { - FileReader file = new FileReader(DOCK_STATE_PATH); - int len = file.read(buffer, 0, 1024); - file.close(); - mPreviousDockState = mDockState = Integer.valueOf((new String(buffer, 0, len)).trim()); - } catch (FileNotFoundException e) { - Slog.w(TAG, "This kernel does not have dock station support"); - } catch (Exception e) { - Slog.e(TAG, "" , e); + private void init() { + synchronized (mLock) { + try { + char[] buffer = new char[1024]; + FileReader file = new FileReader(DOCK_STATE_PATH); + try { + int len = file.read(buffer, 0, 1024); + mDockState = Integer.valueOf((new String(buffer, 0, len)).trim()); + mPreviousDockState = mDockState; + } finally { + file.close(); + } + } catch (FileNotFoundException e) { + Slog.w(TAG, "This kernel does not have dock station support"); + } catch (Exception e) { + Slog.e(TAG, "" , e); + } } } void systemReady() { - synchronized (this) { + synchronized (mLock) { // don't bother broadcasting undocked here if (mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) { - update(); + updateLocked(); } mSystemReady = true; } } - private final void update() { - mHandler.sendEmptyMessage(MSG_DOCK_STATE); + private void updateLocked() { + mHandler.sendEmptyMessage(MSG_DOCK_STATE_CHANGED); + } + + private void handleDockStateChange() { + synchronized (mLock) { + Slog.i(TAG, "Dock state changed: " + mDockState); + + final ContentResolver cr = mContext.getContentResolver(); + + if (Settings.Secure.getInt(cr, + Settings.Secure.DEVICE_PROVISIONED, 0) == 0) { + Slog.i(TAG, "Device not provisioned, skipping dock broadcast"); + return; + } + + // Pack up the values and broadcast them to everyone + Intent intent = new Intent(Intent.ACTION_DOCK_EVENT); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + intent.putExtra(Intent.EXTRA_DOCK_STATE, mDockState); + + // Check if this is Bluetooth Dock + // TODO(BT): Get Dock address. + // String address = null; + // if (address != null) { + // intent.putExtra(BluetoothDevice.EXTRA_DEVICE, + // BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address)); + // } + + // User feedback to confirm dock connection. Particularly + // useful for flaky contact pins... + if (Settings.System.getInt(cr, + Settings.System.DOCK_SOUNDS_ENABLED, 1) == 1) { + String whichSound = null; + if (mDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) { + if ((mPreviousDockState == Intent.EXTRA_DOCK_STATE_DESK) || + (mPreviousDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) || + (mPreviousDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) { + whichSound = Settings.System.DESK_UNDOCK_SOUND; + } else if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_CAR) { + whichSound = Settings.System.CAR_UNDOCK_SOUND; + } + } else { + if ((mDockState == Intent.EXTRA_DOCK_STATE_DESK) || + (mDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) || + (mDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) { + whichSound = Settings.System.DESK_DOCK_SOUND; + } else if (mDockState == Intent.EXTRA_DOCK_STATE_CAR) { + whichSound = Settings.System.CAR_DOCK_SOUND; + } + } + + if (whichSound != null) { + final String soundPath = Settings.System.getString(cr, whichSound); + if (soundPath != null) { + final Uri soundUri = Uri.parse("file://" + soundPath); + if (soundUri != null) { + final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri); + if (sfx != null) { + sfx.setStreamType(AudioManager.STREAM_SYSTEM); + sfx.play(); + } + } + } + } + } + + IDreamManager mgr = IDreamManager.Stub.asInterface(ServiceManager.getService("dreams")); + if (mgr != null) { + // dreams feature enabled + boolean undocked = mDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED; + if (undocked) { + try { + if (mgr.isDreaming()) { + mgr.awaken(); + } + } catch (RemoteException e) { + Slog.w(TAG, "Unable to awaken!", e); + } + } else { + if (isScreenSaverActivatedOnDock(mContext)) { + try { + mgr.dream(); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to dream!", e); + } + } + } + } else { + // dreams feature not enabled, send legacy intent + mContext.sendStickyBroadcast(intent); + } + } } private static boolean isScreenSaverActivatedOnDock(Context context) { - return 0 != Settings.Secure.getInt( - context.getContentResolver(), SCREENSAVER_ACTIVATE_ON_DOCK, DEFAULT_DOCK); + return Settings.Secure.getInt(context.getContentResolver(), + SCREENSAVER_ACTIVATE_ON_DOCK, DEFAULT_DOCK) != 0; } - private final Handler mHandler = new Handler() { + private final Handler mHandler = new Handler(Looper.myLooper(), null, true) { @Override public void handleMessage(Message msg) { switch (msg.what) { - case MSG_DOCK_STATE: - synchronized (this) { - Slog.i(TAG, "Dock state changed: " + mDockState); - - final ContentResolver cr = mContext.getContentResolver(); - - if (Settings.Secure.getInt(cr, - Settings.Secure.DEVICE_PROVISIONED, 0) == 0) { - Slog.i(TAG, "Device not provisioned, skipping dock broadcast"); - return; - } - // Pack up the values and broadcast them to everyone - Intent intent = new Intent(Intent.ACTION_DOCK_EVENT); - intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - intent.putExtra(Intent.EXTRA_DOCK_STATE, mDockState); - - // Check if this is Bluetooth Dock - // TODO(BT): Get Dock address. - String address = null; - if (address != null) - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, - BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address)); - - // User feedback to confirm dock connection. Particularly - // useful for flaky contact pins... - if (Settings.System.getInt(cr, - Settings.System.DOCK_SOUNDS_ENABLED, 1) == 1) - { - String whichSound = null; - if (mDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) { - if ((mPreviousDockState == Intent.EXTRA_DOCK_STATE_DESK) || - (mPreviousDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) || - (mPreviousDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) { - whichSound = Settings.System.DESK_UNDOCK_SOUND; - } else if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_CAR) { - whichSound = Settings.System.CAR_UNDOCK_SOUND; - } - } else { - if ((mDockState == Intent.EXTRA_DOCK_STATE_DESK) || - (mDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) || - (mDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) { - whichSound = Settings.System.DESK_DOCK_SOUND; - } else if (mDockState == Intent.EXTRA_DOCK_STATE_CAR) { - whichSound = Settings.System.CAR_DOCK_SOUND; - } - } - - if (whichSound != null) { - final String soundPath = Settings.System.getString(cr, whichSound); - if (soundPath != null) { - final Uri soundUri = Uri.parse("file://" + soundPath); - if (soundUri != null) { - final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri); - if (sfx != null) { - sfx.setStreamType(AudioManager.STREAM_SYSTEM); - sfx.play(); - } - } - } - } - } - - IDreamManager mgr = IDreamManager.Stub.asInterface(ServiceManager.getService("dreams")); - if (mgr != null) { - // dreams feature enabled - boolean undocked = mDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED; - if (undocked) { - try { - if (mgr.isDreaming()) { - mgr.awaken(); - } - } catch (RemoteException e) { - Slog.w(TAG, "Unable to awaken!", e); - } - } else { - if (isScreenSaverActivatedOnDock(mContext)) { - try { - mgr.dream(); - } catch (RemoteException e) { - Slog.w(TAG, "Unable to dream!", e); - } - } - } - } else { - // dreams feature not enabled, send legacy intent - mContext.sendStickyBroadcast(intent); - } - } + case MSG_DOCK_STATE_CHANGED: + handleDockStateChange(); break; } } diff --git a/services/java/com/android/server/WiredAccessoryObserver.java b/services/java/com/android/server/WiredAccessoryObserver.java index 96ac493f680d5..56c0fdff8e708 100644 --- a/services/java/com/android/server/WiredAccessoryObserver.java +++ b/services/java/com/android/server/WiredAccessoryObserver.java @@ -16,12 +16,12 @@ package com.android.server; -import android.app.ActivityManagerNative; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Handler; +import android.os.Looper; import android.os.Message; import android.os.PowerManager; import android.os.PowerManager.WakeLock; @@ -39,7 +39,7 @@ import java.util.List; /** *

WiredAccessoryObserver monitors for a wired headset on the main board or dock. */ -class WiredAccessoryObserver extends UEventObserver { +final class WiredAccessoryObserver extends UEventObserver { private static final String TAG = WiredAccessoryObserver.class.getSimpleName(); private static final boolean LOG = true; private static final int BIT_HEADSET = (1 << 0); @@ -50,40 +50,177 @@ class WiredAccessoryObserver extends UEventObserver { private static final int SUPPORTED_HEADSETS = (BIT_HEADSET|BIT_HEADSET_NO_MIC| BIT_USB_HEADSET_ANLG|BIT_USB_HEADSET_DGTL| 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; + private final Object mLock = new Object(); - public UEventInfo(String devName, int state1Bits, int state2Bits) { - mDevName = devName; - mState1Bits = state1Bits; - mState2Bits = state2Bits; + private final Context mContext; + private final WakeLock mWakeLock; // held while there is a pending route change + private final AudioManager mAudioManager; + private final List mUEventInfo; + + private int mHeadsetState; + private int mPrevHeadsetState; + private String mHeadsetName; + + public WiredAccessoryObserver(Context context) { + mContext = context; + + PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); + mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WiredAccessoryObserver"); + mWakeLock.setReferenceCounted(false); + mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE); + + mUEventInfo = makeObservedUEventList(); + + context.registerReceiver(new BootCompletedReceiver(), + new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null); + } + + @Override + 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 bootCompleted() { + synchronized (mLock) { + char[] buffer = new char[1024]; + mPrevHeadsetState = mHeadsetState; + + if (LOG) Slog.v(TAG, "init()"); + + 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); + } + } } - public String getDevName() { return mDevName; } + // 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()); + } + } - public String getDevPath() { - return String.format("/devices/virtual/switch/%s", mDevName); + 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 void updateLocked(String newName, int newState) { + // Retain only relevant bits + int headsetState = newState & SUPPORTED_HEADSETS; + int usb_headset_anlg = headsetState & BIT_USB_HEADSET_ANLG; + int usb_headset_dgtl = headsetState & BIT_USB_HEADSET_DGTL; + int h2w_headset = headsetState & (BIT_HEADSET | BIT_HEADSET_NO_MIC); + boolean h2wStateChange = true; + boolean usbStateChange = true; + // reject all suspect transitions: only accept state changes from: + // - a: 0 heaset 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"); + 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"); + usbStateChange = false; + } + if (!h2wStateChange && !usbStateChange) { + Log.e(TAG, "invalid transition, returning ..."); + return; } - public String getSwitchStatePath() { - return String.format("/sys/class/switch/%s/state", mDevName); + mHeadsetName = newName; + mPrevHeadsetState = mHeadsetState; + mHeadsetState = headsetState; + + mWakeLock.acquire(); + + Message msg = mHandler.obtainMessage(0, mHeadsetState, mPrevHeadsetState, mHeadsetName); + mHandler.sendMessage(msg); + } + + private void setDevicesState( + int headsetState, int prevHeadsetState, String headsetName) { + synchronized (mLock) { + int allHeadsets = SUPPORTED_HEADSETS; + for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) { + if ((curHeadset & allHeadsets) != 0) { + setDeviceStateLocked(curHeadset, headsetState, prevHeadsetState, headsetName); + allHeadsets &= ~curHeadset; + } + } } + } - public boolean checkSwitchExists() { - File f = new File(getSwitchStatePath()); - return ((null != f) && f.exists()); - } + private void setDeviceStateLocked(int headset, + int headsetState, int prevHeadsetState, String headsetName) { + if ((headsetState & headset) != (prevHeadsetState & headset)) { + int device; + int state; - public int computeNewHeadsetState(int headsetState, int switchState) { - int preserveMask = ~(mState1Bits | mState2Bits); - int setBits = ((switchState == 1) ? mState1Bits : - ((switchState == 2) ? mState2Bits : 0)); + if ((headsetState & headset) != 0) { + state = 1; + } else { + state = 0; + } - return ((headsetState & preserveMask) | setBits); + if (headset == BIT_HEADSET) { + device = AudioManager.DEVICE_OUT_WIRED_HEADSET; + } else if (headset == BIT_HEADSET_NO_MIC){ + device = AudioManager.DEVICE_OUT_WIRED_HEADPHONE; + } else if (headset == BIT_USB_HEADSET_ANLG) { + device = AudioManager.DEVICE_OUT_ANLG_DOCK_HEADSET; + } else if (headset == BIT_USB_HEADSET_DGTL) { + device = AudioManager.DEVICE_OUT_DGTL_DOCK_HEADSET; + } else if (headset == BIT_HDMI_AUDIO) { + device = AudioManager.DEVICE_OUT_AUX_DIGITAL; + } else { + Slog.e(TAG, "setDeviceState() invalid headset type: "+headset); + return; + } + + if (LOG) + Slog.v(TAG, "device "+headsetName+((state == 1) ? " connected" : " disconnected")); + + mAudioManager.setWiredDeviceConnectionState(device, state, headsetName); } } @@ -130,189 +267,53 @@ class WiredAccessoryObserver extends UEventObserver { return retVal; } - private static List uEventInfo = makeObservedUEventList(); - - private int mHeadsetState; - private int mPrevHeadsetState; - private String mHeadsetName; - - private final Context mContext; - private final WakeLock mWakeLock; // held while there is a pending route change - - private final AudioManager mAudioManager; - - public WiredAccessoryObserver(Context context) { - mContext = context; - PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); - mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WiredAccessoryObserver"); - mWakeLock.setReferenceCounted(false); - mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE); - - context.registerReceiver(new BootCompletedReceiver(), - new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null); - } - - private final class BootCompletedReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - // At any given time accessories could be inserted - // one on the board, one on the dock and one on HDMI: - // observe three UEVENTs - init(); // set initial status - for (int i = 0; i < uEventInfo.size(); ++i) { - UEventInfo uei = uEventInfo.get(i); - startObserving("DEVPATH="+uei.getDevPath()); - } - } - } - - @Override - 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")); - updateState(devPath, name, state); - } catch (NumberFormatException e) { - Slog.e(TAG, "Could not parse switch state from event " + event); - } - } - - private synchronized final void updateState(String devPath, String name, int state) - { - for (int i = 0; i < uEventInfo.size(); ++i) { - UEventInfo uei = uEventInfo.get(i); - if (devPath.equals(uei.getDevPath())) { - update(name, uei.computeNewHeadsetState(mHeadsetState, state)); - return; - } - } - } - - private synchronized final void init() { - char[] buffer = new char[1024]; - mPrevHeadsetState = mHeadsetState; - - if (LOG) Slog.v(TAG, "init()"); - - for (int i = 0; i < uEventInfo.size(); ++i) { - UEventInfo uei = uEventInfo.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) { - updateState(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); - } - } - } - - private synchronized final void update(String newName, int newState) { - // Retain only relevant bits - int headsetState = newState & SUPPORTED_HEADSETS; - int newOrOld = headsetState | mHeadsetState; - int delay = 0; - int usb_headset_anlg = headsetState & BIT_USB_HEADSET_ANLG; - int usb_headset_dgtl = headsetState & BIT_USB_HEADSET_DGTL; - int h2w_headset = headsetState & (BIT_HEADSET | BIT_HEADSET_NO_MIC); - boolean h2wStateChange = true; - boolean usbStateChange = true; - // reject all suspect transitions: only accept state changes from: - // - a: 0 heaset 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"); - 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"); - usbStateChange = false; - } - if (!h2wStateChange && !usbStateChange) { - Log.e(TAG, "invalid transition, returning ..."); - return; - } - - mHeadsetName = newName; - mPrevHeadsetState = mHeadsetState; - mHeadsetState = headsetState; - - mWakeLock.acquire(); - mHandler.sendMessage(mHandler.obtainMessage(0, - mHeadsetState, - mPrevHeadsetState, - mHeadsetName)); - } - - private synchronized final void setDevicesState(int headsetState, - int prevHeadsetState, - String headsetName) { - int allHeadsets = SUPPORTED_HEADSETS; - for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) { - if ((curHeadset & allHeadsets) != 0) { - setDeviceState(curHeadset, headsetState, prevHeadsetState, headsetName); - allHeadsets &= ~curHeadset; - } - } - } - - private final void setDeviceState(int headset, - int headsetState, - int prevHeadsetState, - String headsetName) { - if ((headsetState & headset) != (prevHeadsetState & headset)) { - int device; - int state; - - if ((headsetState & headset) != 0) { - state = 1; - } else { - state = 0; - } - - if (headset == BIT_HEADSET) { - device = AudioManager.DEVICE_OUT_WIRED_HEADSET; - } else if (headset == BIT_HEADSET_NO_MIC){ - device = AudioManager.DEVICE_OUT_WIRED_HEADPHONE; - } else if (headset == BIT_USB_HEADSET_ANLG) { - device = AudioManager.DEVICE_OUT_ANLG_DOCK_HEADSET; - } else if (headset == BIT_USB_HEADSET_DGTL) { - device = AudioManager.DEVICE_OUT_DGTL_DOCK_HEADSET; - } else if (headset == BIT_HDMI_AUDIO) { - device = AudioManager.DEVICE_OUT_AUX_DIGITAL; - } else { - Slog.e(TAG, "setDeviceState() invalid headset type: "+headset); - return; - } - - if (LOG) - Slog.v(TAG, "device "+headsetName+((state == 1) ? " connected" : " disconnected")); - - mAudioManager.setWiredDeviceConnectionState(device, state, headsetName); - } - } - - private final Handler mHandler = new Handler() { + 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(); } }; + + private static final 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("/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 final class BootCompletedReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + bootCompleted(); + } + } }