diff options
-rw-r--r-- | core/java/android/os/UEventObserver.java | 196 | ||||
-rw-r--r-- | services/java/com/android/server/DockObserver.java | 233 | ||||
-rw-r--r-- | services/java/com/android/server/WiredAccessoryObserver.java | 305 |
3 files changed, 386 insertions, 348 deletions
diff --git a/core/java/android/os/UEventObserver.java b/core/java/android/os/UEventObserver.java index b924e84..d33382b 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.<p> + * This method will cause the UEvent thread to start if this is the first + * invocation of startObserving in this process.<p> + * Once called, the UEvent thread will call onUEvent() when an incoming + * UEvent matches the specified string.<p> + * 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.<p> + * 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<String,String> mMap = new HashMap<String,String>(); + private final HashMap<String,String> mMap = new HashMap<String,String>(); 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<Object> mObservers = new ArrayList<Object>(); - - UEventThread() { + private final ArrayList<Object> mKeysAndObservers = new ArrayList<Object>(); + + private final ArrayList<UEventObserver> mTempObserversToSignal = + new ArrayList<UEventObserver>(); + + 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)); + } + } + } + + 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(mObservers) { - mObservers.add(match); - mObservers.add(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.<p> - * This method will cause the UEvent thread to start if this is the first - * invocation of startObserving in this process.<p> - * Once called, the UEvent thread will call onUEvent() when an incoming - * UEvent matches the specified string.<p> - * 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.<p> - * 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 8bdd7be..ef09b01 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; /** * <p>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 static boolean isScreenSaverActivatedOnDock(Context context) { - return 0 != Settings.Secure.getInt( - context.getContentResolver(), SCREENSAVER_ACTIVATE_ON_DOCK, DEFAULT_DOCK); - } + private void handleDockStateChange() { + synchronized (mLock) { + Slog.i(TAG, "Dock state changed: " + mDockState); - private final Handler mHandler = new Handler() { - @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(); - 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; + } - 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; - } - } + // 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(); - } - } - } + 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); + 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 Settings.Secure.getInt(context.getContentResolver(), + SCREENSAVER_ACTIVATE_ON_DOCK, DEFAULT_DOCK) != 0; + } + + private final Handler mHandler = new Handler(Looper.myLooper(), null, true) { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + 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 96ac493..56c0fdf 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; /** * <p>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,122 +50,32 @@ 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; - - 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 static List<UEventInfo> makeObservedUEventList() { - List<UEventInfo> retVal = new ArrayList<UEventInfo>(); - 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"); - } + private final Object mLock = new Object(); - // 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> uEventInfo = makeObservedUEventList(); + 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; - 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); + mUEventInfo = makeObservedUEventList(); + 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()); @@ -174,56 +84,64 @@ class WiredAccessoryObserver extends UEventObserver { String devPath = event.get("DEVPATH"); String name = event.get("SWITCH_NAME"); int state = Integer.parseInt(event.get("SWITCH_STATE")); - updateState(devPath, name, state); + synchronized (mLock) { + updateStateLocked(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 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); + } } } - } - - 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); - } + // 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()); + } + } - } 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 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 synchronized final void update(String newName, int newState) { + private void updateLocked(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); @@ -254,28 +172,26 @@ class WiredAccessoryObserver extends UEventObserver { mHeadsetState = headsetState; mWakeLock.acquire(); - mHandler.sendMessage(mHandler.obtainMessage(0, - mHeadsetState, - mPrevHeadsetState, - mHeadsetName)); + + Message msg = mHandler.obtainMessage(0, mHeadsetState, mPrevHeadsetState, mHeadsetName); + mHandler.sendMessage(msg); } - 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 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; + } } } } - private final void setDeviceState(int headset, - int headsetState, - int prevHeadsetState, - String headsetName) { + private void setDeviceStateLocked(int headset, + int headsetState, int prevHeadsetState, String headsetName) { if ((headsetState & headset) != (prevHeadsetState & headset)) { int device; int state; @@ -308,11 +224,96 @@ class WiredAccessoryObserver extends UEventObserver { } } - private final Handler mHandler = new Handler() { + private static List<UEventInfo> makeObservedUEventList() { + List<UEventInfo> retVal = new ArrayList<UEventInfo>(); + 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 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(); + } + } } |