OSDN Git Service

Encapsulate locks in UEventObservers.
authorJeff Brown <jeffbrown@google.com>
Tue, 21 Aug 2012 03:15:34 +0000 (20:15 -0700)
committerJeff Brown <jeffbrown@google.com>
Tue, 21 Aug 2012 03:15:34 +0000 (20:15 -0700)
Synchronized methods make me cry so fixing this first before
I introduce any new functionality that could result in a deadlock.

Bug: 6548391
Change-Id: I9c006dc491ce205bfd86acf828dcebda2a63b2ca

core/java/android/os/UEventObserver.java
services/java/com/android/server/DockObserver.java
services/java/com/android/server/WiredAccessoryObserver.java

index b924e84..d33382b 100644 (file)
@@ -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();
-        }
-    }
 }
index 8bdd7be..ef09b01 100644 (file)
@@ -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;
             }
         }
index 96ac493..56c0fdf 100644 (file)
 
 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();
+        }
+    }
 }