Added support for dock headset observer

Change-Id: I06b2e65e3bfa10735e6c7fd3349afa9ae7d45292
Signed-off-by: Praveen Bharathi <pbharathi@motorola.com>
diff --git a/api/current.xml b/api/current.xml
index b0cb834..54aae7f 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -50131,6 +50131,17 @@
  visibility="public"
 >
 </field>
+<field name="CATEGORY_HE_DESK_DOCK"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.intent.category.HE_DESK_DOCK&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="CATEGORY_HOME"
  type="java.lang.String"
  transient="false"
@@ -50164,6 +50175,17 @@
  visibility="public"
 >
 </field>
+<field name="CATEGORY_LE_DESK_DOCK"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.intent.category.LE_DESK_DOCK&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="CATEGORY_MONKEY"
  type="java.lang.String"
  transient="false"
@@ -50383,6 +50405,28 @@
  visibility="public"
 >
 </field>
+<field name="EXTRA_DOCK_STATE_HE_DESK"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="EXTRA_DOCK_STATE_LE_DESK"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="EXTRA_DOCK_STATE_UNDOCKED"
  type="int"
  transient="false"
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 8c36aa6..e14282c 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -533,6 +533,8 @@
  *     <li> {@link #CATEGORY_TEST}
  *     <li> {@link #CATEGORY_CAR_DOCK}
  *     <li> {@link #CATEGORY_DESK_DOCK}
+ *     <li> {@link #CATEGORY_LE_DESK_DOCK}
+ *     <li> {@link #CATEGORY_HE_DESK_DOCK}
  *     <li> {@link #CATEGORY_CAR_MODE}
  *     <li> {@link #CATEGORY_APP_MARKET}
  * </ul>
@@ -549,6 +551,8 @@
  *     <li> {@link #EXTRA_CHANGED_COMPONENT_NAME}
  *     <li> {@link #EXTRA_DATA_REMOVED}
  *     <li> {@link #EXTRA_DOCK_STATE}
+ *     <li> {@link #EXTRA_DOCK_STATE_HE_DESK}
+ *     <li> {@link #EXTRA_DOCK_STATE_LE_DESK}
  *     <li> {@link #EXTRA_DOCK_STATE_CAR}
  *     <li> {@link #EXTRA_DOCK_STATE_DESK}
  *     <li> {@link #EXTRA_DOCK_STATE_UNDOCKED}
@@ -1822,6 +1826,36 @@
             "android.intent.action.HEADSET_PLUG";
 
     /**
+     * Broadcast Action: An analog audio speaker/headset plugged in or unplugged.
+     *
+     * <p>The intent will have the following extra values:
+     * <ul>
+     *   <li><em>state</em> - 0 for unplugged, 1 for plugged. </li>
+     *   <li><em>name</em> - Headset type, human readable string </li>
+     * </ul>
+     * </ul>
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_USB_ANLG_HEADSET_PLUG =
+            "android.intent.action.DOCK_HEADSET_PLUG";
+
+    /**
+     * Broadcast Action: An analog audio speaker/headset plugged in or unplugged.
+     *
+     * <p>The intent will have the following extra values:
+     * <ul>
+     *   <li><em>state</em> - 0 for unplugged, 1 for plugged. </li>
+     *   <li><em>name</em> - Headset type, human readable string </li>
+     * </ul>
+     * </ul>
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_USB_DGTL_HEADSET_PLUG =
+            "android.intent.action.DOCK_HEADSET_PLUG";
+
+    /**
      * Broadcast Action: An outgoing call is about to be placed.
      *
      * <p>The Intent will have the following extra value:
@@ -2060,6 +2094,21 @@
      */
     @SdkConstant(SdkConstantType.INTENT_CATEGORY)
     public static final String CATEGORY_DESK_DOCK = "android.intent.category.DESK_DOCK";
+    /**
+     * An activity to run when device is inserted into a analog (low end) dock.
+     * Used with {@link #ACTION_MAIN} to launch an activity.  For more
+     * information, see {@link android.app.UiModeManager}.
+     */
+    @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+    public static final String CATEGORY_LE_DESK_DOCK = "android.intent.category.LE_DESK_DOCK";
+
+    /**
+     * An activity to run when device is inserted into a digital (high end) dock.
+     * Used with {@link #ACTION_MAIN} to launch an activity.  For more
+     * information, see {@link android.app.UiModeManager}.
+     */
+    @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+    public static final String CATEGORY_HE_DESK_DOCK = "android.intent.category.HE_DESK_DOCK";
 
     /**
      * Used to indicate that the activity can be used in a car environment.
@@ -2208,7 +2257,9 @@
      * intents to request the dock state.  Possible values are
      * {@link android.content.Intent#EXTRA_DOCK_STATE_UNDOCKED},
      * {@link android.content.Intent#EXTRA_DOCK_STATE_DESK}, or
-     * {@link android.content.Intent#EXTRA_DOCK_STATE_CAR}.
+     * {@link android.content.Intent#EXTRA_DOCK_STATE_CAR}, or
+     * {@link android.content.Intent#EXTRA_DOCK_STATE_LE_DESK}, or
+     * {@link android.content.Intent#EXTRA_DOCK_STATE_HE_DESK}.
      */
     public static final String EXTRA_DOCK_STATE = "android.intent.extra.DOCK_STATE";
 
@@ -2231,6 +2282,18 @@
     public static final int EXTRA_DOCK_STATE_CAR = 2;
 
     /**
+     * Used as an int value for {@link android.content.Intent#EXTRA_DOCK_STATE}
+     * to represent that the phone is in a analog (low end) dock.
+     */
+    public static final int EXTRA_DOCK_STATE_LE_DESK = 3;
+
+    /**
+     * Used as an int value for {@link android.content.Intent#EXTRA_DOCK_STATE}
+     * to represent that the phone is in a digital (high end) dock.
+     */
+    public static final int EXTRA_DOCK_STATE_HE_DESK = 4;
+
+    /**
      * Boolean that can be supplied as meta-data with a dock activity, to
      * indicate that the dock should take over the home key when it is active.
      */
diff --git a/include/media/AudioSystem.h b/include/media/AudioSystem.h
index 9fd905f..1081c35 100644
--- a/include/media/AudioSystem.h
+++ b/include/media/AudioSystem.h
@@ -262,11 +262,15 @@
         DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES = 0x100,
         DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER = 0x200,
         DEVICE_OUT_AUX_DIGITAL = 0x400,
+        DEVICE_OUT_ANLG_DOCK_HEADSET = 0x800,
+        DEVICE_OUT_DGTL_DOCK_HEADSET = 0x1000,
         DEVICE_OUT_DEFAULT = 0x8000,
         DEVICE_OUT_ALL = (DEVICE_OUT_EARPIECE | DEVICE_OUT_SPEAKER | DEVICE_OUT_WIRED_HEADSET |
                 DEVICE_OUT_WIRED_HEADPHONE | DEVICE_OUT_BLUETOOTH_SCO | DEVICE_OUT_BLUETOOTH_SCO_HEADSET |
                 DEVICE_OUT_BLUETOOTH_SCO_CARKIT | DEVICE_OUT_BLUETOOTH_A2DP | DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
-                DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER | DEVICE_OUT_AUX_DIGITAL | DEVICE_OUT_DEFAULT),
+                DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER | DEVICE_OUT_AUX_DIGITAL |
+                DEVICE_OUT_ANLG_DOCK_HEADSET | DEVICE_OUT_DGTL_DOCK_HEADSET |
+                DEVICE_OUT_DEFAULT),
         DEVICE_OUT_ALL_A2DP = (DEVICE_OUT_BLUETOOTH_A2DP | DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
                 DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER),
 
@@ -309,6 +313,8 @@
         FORCE_WIRED_ACCESSORY,
         FORCE_BT_CAR_DOCK,
         FORCE_BT_DESK_DOCK,
+        FORCE_ANALOG_DOCK,
+        FORCE_DIGITAL_DOCK,
         NUM_FORCE_CONFIG,
         FORCE_DEFAULT = FORCE_NONE
     };
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 23f34d2..a49bb37 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -307,10 +307,13 @@
         // Register for device connection intent broadcasts.
         IntentFilter intentFilter =
                 new IntentFilter(Intent.ACTION_HEADSET_PLUG);
+
         intentFilter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
         intentFilter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
         intentFilter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
         intentFilter.addAction(Intent.ACTION_DOCK_EVENT);
+        intentFilter.addAction(Intent.ACTION_USB_ANLG_HEADSET_PLUG);
+        intentFilter.addAction(Intent.ACTION_USB_DGTL_HEADSET_PLUG);
         context.registerReceiver(mReceiver, intentFilter);
 
         // Register for media button intent broadcasts.
@@ -1816,6 +1819,12 @@
                     case Intent.EXTRA_DOCK_STATE_CAR:
                         config = AudioSystem.FORCE_BT_CAR_DOCK;
                         break;
+                    case Intent.EXTRA_DOCK_STATE_LE_DESK:
+                        config = AudioSystem.FORCE_ANALOG_DOCK;
+                        break;
+                    case Intent.EXTRA_DOCK_STATE_HE_DESK:
+                        config = AudioSystem.FORCE_DIGITAL_DOCK;
+                        break;
                     case Intent.EXTRA_DOCK_STATE_UNDOCKED:
                     default:
                         config = AudioSystem.FORCE_NONE;
@@ -1927,6 +1936,32 @@
                         mConnectedDevices.put( new Integer(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE), "");
                     }
                 }
+            } else if (action.equals(Intent.ACTION_USB_ANLG_HEADSET_PLUG)) {
+                int state = intent.getIntExtra("state", 0);
+                Log.v(TAG, "Broadcast Receiver: Got ACTION_USB_ANLG_HEADSET_PLUG, state = "+state);
+                boolean isConnected = mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET);
+                if (state == 0 && isConnected) {
+                    AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET,
+                                                         AudioSystem.DEVICE_STATE_UNAVAILABLE, "");
+                    mConnectedDevices.remove(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET);
+                } else if (state == 1 && !isConnected)  {
+                    AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET,
+                                                         AudioSystem.DEVICE_STATE_AVAILABLE, "");
+                    mConnectedDevices.put( new Integer(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET), "");
+                }
+            } else if (action.equals(Intent.ACTION_USB_DGTL_HEADSET_PLUG)) {
+                int state = intent.getIntExtra("state", 0);
+                Log.v(TAG, "Broadcast Receiver: Got ACTION_USB_DGTL_HEADSET_PLUG, state = "+state);
+                boolean isConnected = mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET);
+                if (state == 0 && isConnected) {
+                    AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET,
+                                                         AudioSystem.DEVICE_STATE_UNAVAILABLE, "");
+                    mConnectedDevices.remove(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET);
+                } else if (state == 1 && !isConnected)  {
+                    AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET,
+                                                         AudioSystem.DEVICE_STATE_AVAILABLE, "");
+                    mConnectedDevices.put( new Integer(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET), "");
+                }
             } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
                 int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
                 synchronized (mScoClients) {
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index a4818ff..5442791 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -243,6 +243,8 @@
     public static final int DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES = 0x100;
     public static final int DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER = 0x200;
     public static final int DEVICE_OUT_AUX_DIGITAL = 0x400;
+    public static final int DEVICE_OUT_ANLG_DOCK_HEADSET = 0x800;
+    public static final int DEVICE_OUT_DGTL_DOCK_HEADSET = 0x1000;
     public static final int DEVICE_OUT_DEFAULT = 0x8000;
     // input devices
     public static final int DEVICE_IN_COMMUNICATION = 0x10000;
@@ -273,6 +275,8 @@
     public static final int FORCE_WIRED_ACCESSORY = 5;
     public static final int FORCE_BT_CAR_DOCK = 6;
     public static final int FORCE_BT_DESK_DOCK = 7;
+    public static final int FORCE_ANALOG_DOCK = 8;
+    public static final int FORCE_DIGITAL_DOCK = 9;
     public static final int FORCE_DEFAULT = FORCE_NONE;
 
     // usage for serForceUse
diff --git a/services/audioflinger/AudioPolicyManagerBase.cpp b/services/audioflinger/AudioPolicyManagerBase.cpp
index 65d9ef7..86d4c9f 100644
--- a/services/audioflinger/AudioPolicyManagerBase.cpp
+++ b/services/audioflinger/AudioPolicyManagerBase.cpp
@@ -356,7 +356,9 @@
         break;
     case AudioSystem::FOR_MEDIA:
         if (config != AudioSystem::FORCE_HEADPHONES && config != AudioSystem::FORCE_BT_A2DP &&
-            config != AudioSystem::FORCE_WIRED_ACCESSORY && config != AudioSystem::FORCE_NONE) {
+            config != AudioSystem::FORCE_WIRED_ACCESSORY &&
+            config != AudioSystem::FORCE_ANALOG_DOCK &&
+            config != AudioSystem::FORCE_DIGITAL_DOCK && config != AudioSystem::FORCE_NONE) {
             LOGW("setForceUse() invalid config %d for FOR_MEDIA", config);
             return;
         }
@@ -372,7 +374,10 @@
         break;
     case AudioSystem::FOR_DOCK:
         if (config != AudioSystem::FORCE_NONE && config != AudioSystem::FORCE_BT_CAR_DOCK &&
-            config != AudioSystem::FORCE_BT_DESK_DOCK && config != AudioSystem::FORCE_WIRED_ACCESSORY) {
+            config != AudioSystem::FORCE_BT_DESK_DOCK &&
+            config != AudioSystem::FORCE_WIRED_ACCESSORY &&
+            config != AudioSystem::FORCE_ANALOG_DOCK &&
+            config != AudioSystem::FORCE_DIGITAL_DOCK) {
             LOGW("setForceUse() invalid config %d for FOR_DOCK", config);
         }
         forceVolumeReeval = true;
@@ -1366,6 +1371,7 @@
 
 void AudioPolicyManagerBase::closeA2dpOutputs()
 {
+
     LOGV("setDeviceConnectionState() closing A2DP and duplicated output!");
 
     if (mDuplicatedOutput != 0) {
@@ -1558,6 +1564,8 @@
             if (device) break;
             device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADSET;
             if (device) break;
+            device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_ANLG_DOCK_HEADSET;
+            if (device) break;
 #ifdef WITH_A2DP
             // when not in a phone call, phone strategy should route STREAM_VOICE_CALL to A2DP
             if (mPhoneState != AudioSystem::MODE_IN_CALL) {
@@ -1617,6 +1625,12 @@
         if (device2 == 0) {
             device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADSET;
         }
+        if (device2 == 0) {
+            device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_ANLG_DOCK_HEADSET;
+        }
+        if (device2 == 0) {
+            device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_DGTL_DOCK_HEADSET;
+        }
 #ifdef WITH_A2DP
         if (mA2dpOutput != 0) {
             if (strategy == STRATEGY_SONIFICATION && !a2dpUsedForSonification()) {
@@ -1797,7 +1811,9 @@
         (AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP |
         AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
         AudioSystem::DEVICE_OUT_WIRED_HEADSET |
-        AudioSystem::DEVICE_OUT_WIRED_HEADPHONE)) &&
+        AudioSystem::DEVICE_OUT_WIRED_HEADPHONE |
+        AudioSystem::DEVICE_OUT_ANLG_DOCK_HEADSET |
+        AudioSystem::DEVICE_OUT_DGTL_DOCK_HEADSET)) &&
         (getStrategy((AudioSystem::stream_type)stream) == STRATEGY_SONIFICATION) &&
         streamDesc.mCanBeMuted) {
         volume *= SONIFICATION_HEADSET_VOLUME_FACTOR;
diff --git a/services/java/com/android/server/DockObserver.java b/services/java/com/android/server/DockObserver.java
index bee8872..f993093 100644
--- a/services/java/com/android/server/DockObserver.java
+++ b/services/java/com/android/server/DockObserver.java
@@ -103,7 +103,6 @@
             FileReader file = new FileReader(DOCK_STATE_PATH);
             int len = file.read(buffer, 0, 1024);
             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) {
@@ -158,13 +157,17 @@
                         {
                             String whichSound = null;
                             if (mDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
-                                if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_DESK) {
+                                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) {
+                                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;
diff --git a/services/java/com/android/server/HeadsetObserver.java b/services/java/com/android/server/HeadsetObserver.java
deleted file mode 100644
index 6f0a91d..0000000
--- a/services/java/com/android/server/HeadsetObserver.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import android.app.ActivityManagerNative;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Handler;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.os.UEventObserver;
-import android.util.Slog;
-import android.media.AudioManager;
-
-import java.io.FileReader;
-import java.io.FileNotFoundException;
-
-/**
- * <p>HeadsetObserver monitors for a wired headset.
- */
-class HeadsetObserver extends UEventObserver {
-    private static final String TAG = HeadsetObserver.class.getSimpleName();
-    private static final boolean LOG = true;
-
-    private static final String HEADSET_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/h2w";
-    private static final String HEADSET_STATE_PATH = "/sys/class/switch/h2w/state";
-    private static final String HEADSET_NAME_PATH = "/sys/class/switch/h2w/name";
-
-    private static final int BIT_HEADSET = (1 << 0);
-    private static final int BIT_HEADSET_NO_MIC = (1 << 1);
-    private static final int SUPPORTED_HEADSETS = (BIT_HEADSET|BIT_HEADSET_NO_MIC);
-    private static final int HEADSETS_WITH_MIC = BIT_HEADSET;
-
-    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
-
-    public HeadsetObserver(Context context) {
-        mContext = context;
-        PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
-        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "HeadsetObserver");
-        mWakeLock.setReferenceCounted(false);
-
-        startObserving(HEADSET_UEVENT_MATCH);
-
-        init();  // set initial status
-    }
-
-    @Override
-    public void onUEvent(UEventObserver.UEvent event) {
-        if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString());
-
-        try {
-            update(event.get("SWITCH_NAME"), Integer.parseInt(event.get("SWITCH_STATE")));
-        } catch (NumberFormatException e) {
-            Slog.e(TAG, "Could not parse switch state from event " + event);
-        }
-    }
-
-    private synchronized final void init() {
-        char[] buffer = new char[1024];
-
-        String newName = mHeadsetName;
-        int newState = mHeadsetState;
-        mPrevHeadsetState = mHeadsetState;
-        try {
-            FileReader file = new FileReader(HEADSET_STATE_PATH);
-            int len = file.read(buffer, 0, 1024);
-            newState = Integer.valueOf((new String(buffer, 0, len)).trim());
-
-            file = new FileReader(HEADSET_NAME_PATH);
-            len = file.read(buffer, 0, 1024);
-            newName = new String(buffer, 0, len).trim();
-
-        } catch (FileNotFoundException e) {
-            Slog.w(TAG, "This kernel does not have wired headset support");
-        } catch (Exception e) {
-            Slog.e(TAG, "" , e);
-        }
-
-        update(newName, newState);
-    }
-
-    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;
-        // reject all suspect transitions: only accept state changes from:
-        // - a: 0 heaset to 1 headset
-        // - b: 1 headset to 0 headset
-        if (mHeadsetState == headsetState || ((newOrOld & (newOrOld - 1)) != 0)) {
-            return;
-        }
-
-        mHeadsetName = newName;
-        mPrevHeadsetState = mHeadsetState;
-        mHeadsetState = headsetState;
-
-        if (headsetState == 0) {
-            Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
-            mContext.sendBroadcast(intent);
-            // It can take hundreds of ms flush the audio pipeline after
-            // apps pause audio playback, but audio route changes are
-            // immediate, so delay the route change by 1000ms.
-            // This could be improved once the audio sub-system provides an
-            // interface to clear the audio pipeline.
-            delay = 1000;
-        } else {
-            // Insert the same delay for headset connection so that the connection event is not
-            // broadcast before the disconnection event in case of fast removal/insertion
-            if (mHandler.hasMessages(0)) {
-                delay = 1000;
-            }
-        }
-        mWakeLock.acquire();
-        mHandler.sendMessageDelayed(mHandler.obtainMessage(0,
-                                                           mHeadsetState,
-                                                           mPrevHeadsetState,
-                                                           mHeadsetName),
-                                    delay);
-    }
-
-    private synchronized final void sendIntents(int headsetState, int prevHeadsetState, String headsetName) {
-        int allHeadsets = SUPPORTED_HEADSETS;
-        for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) {
-            if ((curHeadset & allHeadsets) != 0) {
-                sendIntent(curHeadset, headsetState, prevHeadsetState, headsetName);
-                allHeadsets &= ~curHeadset;
-            }
-        }
-    }
-
-    private final void sendIntent(int headset, int headsetState, int prevHeadsetState, String headsetName) {
-        if ((headsetState & headset) != (prevHeadsetState & headset)) {
-            //  Pack up the values and broadcast them to everyone
-            Intent intent = new Intent(Intent.ACTION_HEADSET_PLUG);
-            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-            int state = 0;
-            int microphone = 0;
-
-            if ((headset & HEADSETS_WITH_MIC) != 0) {
-                microphone = 1;
-            }
-            if ((headsetState & headset) != 0) {
-                state = 1;
-            }
-            intent.putExtra("state", state);
-            intent.putExtra("name", headsetName);
-            intent.putExtra("microphone", microphone);
-
-            if (LOG) Slog.v(TAG, "Intent.ACTION_HEADSET_PLUG: state: "+state+" name: "+headsetName+" mic: "+microphone);
-            // TODO: Should we require a permission?
-            ActivityManagerNative.broadcastStickyIntent(intent, null);
-        }
-    }
-
-    private final Handler mHandler = new Handler() {
-        @Override
-        public void handleMessage(Message msg) {
-            sendIntents(msg.arg1, msg.arg2, (String)msg.obj);
-            mWakeLock.release();
-        }
-    };
-}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 237ab80..54f7441 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -121,7 +121,7 @@
         WindowManagerService wm = null;
         BluetoothService bluetooth = null;
         BluetoothA2dpService bluetoothA2dp = null;
-        HeadsetObserver headset = null;
+        WiredAccessoryObserver wiredAccessory = null;
         DockObserver dock = null;
         UsbObserver usb = null;
         UiModeManagerService uiMode = null;
@@ -388,14 +388,6 @@
             }
 
             try {
-                Slog.i(TAG, "Headset Observer");
-                // Listen for wired headset changes
-                headset = new HeadsetObserver(context);
-            } catch (Throwable e) {
-                Slog.e(TAG, "Failure starting HeadsetObserver", e);
-            }
-
-            try {
                 Slog.i(TAG, "Dock Observer");
                 // Listen for dock station changes
                 dock = new DockObserver(context, power);
@@ -404,6 +396,14 @@
             }
 
             try {
+                Slog.i(TAG, "Wired Accessory Observer");
+                // Listen for wired headset changes
+                wiredAccessory = new WiredAccessoryObserver(context);
+            } catch (Throwable e) {
+                Slog.e(TAG, "Failure starting WiredAccessoryObserver", e);
+            }
+
+            try {
                 Slog.i(TAG, "USB Observer");
                 // Listen for USB changes
                 usb = new UsbObserver(context);
diff --git a/services/java/com/android/server/WiredAccessoryObserver.java b/services/java/com/android/server/WiredAccessoryObserver.java
new file mode 100644
index 0000000..ab92fdf
--- /dev/null
+++ b/services/java/com/android/server/WiredAccessoryObserver.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.app.ActivityManagerNative;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.UEventObserver;
+import android.util.Slog;
+import android.media.AudioManager;
+import android.util.Log;
+
+import java.io.FileReader;
+import java.io.FileNotFoundException;
+
+/**
+ * <p>WiredAccessoryObserver monitors for a wired headset on the main board or dock.
+ */
+class WiredAccessoryObserver extends UEventObserver {
+    private static final String TAG = WiredAccessoryObserver.class.getSimpleName();
+    private static final boolean LOG = true;
+    private static final int MAX_AUDIO_PORTS = 2; /* h2w & USB Audio */
+    private static final String uEventInfo[][] = { {"DEVPATH=/devices/virtual/switch/h2w",
+                                                    "/sys/class/switch/h2w/state",
+                                                    "/sys/class/switch/h2w/name"},
+                                                   {"DEVPATH=/devices/virtual/switch/usb_audio",
+                                                    "/sys/class/switch/usb_audio/state",
+                                                    "/sys/class/switch/usb_audio/name"} };
+
+    private static final int BIT_HEADSET = (1 << 0);
+    private static final int BIT_HEADSET_NO_MIC = (1 << 1);
+    private static final int BIT_USB_HEADSET_ANLG = (1 << 2);
+    private static final int BIT_USB_HEADSET_DGTL = (1 << 3);
+    private static final int SUPPORTED_HEADSETS = (BIT_HEADSET|BIT_HEADSET_NO_MIC|
+                                                   BIT_USB_HEADSET_ANLG|BIT_USB_HEADSET_DGTL);
+    private static final int HEADSETS_WITH_MIC = BIT_HEADSET;
+
+    private int mHeadsetState;
+    private int mPrevHeadsetState;
+    private String mHeadsetName;
+    private int switchState;
+
+    private final Context mContext;
+    private final WakeLock mWakeLock;  // held while there is a pending route change
+
+    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);
+
+        // At any given time both headsets could be inserted
+        // one on the board and one on the dock
+        // observe two UEVENTs
+        for (int i = 0; i <= MAX_AUDIO_PORTS; i++) {
+            startObserving(uEventInfo[i][0]);
+        }
+        init();  // set initial status
+    }
+
+    @Override
+    public void onUEvent(UEventObserver.UEvent event) {
+        if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString());
+
+        try {
+            if ((event.get("SWITCH_NAME")).equals("usb_audio")) {
+                if (Integer.parseInt(event.get("SWITCH_STATE")) == 1) {
+                    switchState = ((mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC|
+                                                     BIT_USB_HEADSET_DGTL)) |
+                                   (Integer.parseInt(event.get("SWITCH_STATE")) << 2));
+                } else if (Integer.parseInt(event.get("SWITCH_STATE")) == 2) {
+                    switchState = ((mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC|
+                                                     BIT_USB_HEADSET_ANLG)) |
+                                   (Integer.parseInt(event.get("SWITCH_STATE")) << 3));
+                }
+                else switchState = (mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC));
+            }
+            else {
+                switchState = ((mHeadsetState & (BIT_USB_HEADSET_ANLG|BIT_USB_HEADSET_DGTL)) |
+                              (Integer.parseInt(event.get("SWITCH_STATE"))));
+            }
+            update(event.get("SWITCH_NAME"), switchState);
+        } catch (NumberFormatException e) {
+            Slog.e(TAG, "Could not parse switch state from event " + event);
+        }
+    }
+
+    private synchronized final void init() {
+        char[] buffer = new char[1024];
+
+        String newName = mHeadsetName;
+        int newState = mHeadsetState;
+        mPrevHeadsetState = mHeadsetState;
+
+        for (int i = 0; i <= MAX_AUDIO_PORTS; i++) {
+            try {
+                FileReader file = new FileReader(uEventInfo[i][1]);
+                int len = file.read(buffer, 0, 1024);
+                newState = Integer.valueOf((new String(buffer, 0, len)).trim());
+
+                file = new FileReader(uEventInfo[i][2]);
+                len = file.read(buffer, 0, 1024);
+                newName = new String(buffer, 0, len).trim();
+
+            } catch (FileNotFoundException e) {
+                Slog.w(TAG, "This kernel does not have wired headset support");
+            } catch (Exception e) {
+                Slog.e(TAG, "" , e);
+            }
+
+            update(newName, newState);
+        }
+    }
+
+    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
+        Log.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;
+
+        if (headsetState == 0) {
+            Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
+            mContext.sendBroadcast(intent);
+            // It can take hundreds of ms flush the audio pipeline after
+            // apps pause audio playback, but audio route changes are
+            // immediate, so delay the route change by 1000ms.
+            // This could be improved once the audio sub-system provides an
+            // interface to clear the audio pipeline.
+            delay = 1000;
+        } else {
+            // Insert the same delay for headset connection so that the connection event is not
+            // broadcast before the disconnection event in case of fast removal/insertion
+            if (mHandler.hasMessages(0)) {
+                delay = 1000;
+            }
+        }
+        mWakeLock.acquire();
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(0,
+                                                           mHeadsetState,
+                                                           mPrevHeadsetState,
+                                                           mHeadsetName),
+                                    delay);
+    }
+
+    private synchronized final void sendIntents(int headsetState, int prevHeadsetState, String headsetName) {
+        int allHeadsets = SUPPORTED_HEADSETS;
+        for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) {
+            if ((curHeadset & allHeadsets) != 0) {
+                sendIntent(curHeadset, headsetState, prevHeadsetState, headsetName);
+                allHeadsets &= ~curHeadset;
+            }
+        }
+    }
+
+    private final void sendIntent(int headset, int headsetState, int prevHeadsetState, String headsetName) {
+        if ((headsetState & headset) != (prevHeadsetState & headset)) {
+
+            int state = 0;
+            if ((headsetState & headset) != 0) {
+                state = 1;
+            }
+            if((headset == BIT_USB_HEADSET_ANLG) || (headset == BIT_USB_HEADSET_DGTL)) {
+                Intent intent;
+
+                //  Pack up the values and broadcast them to everyone
+                if (headset == BIT_USB_HEADSET_ANLG) {
+                    intent = new Intent(Intent.ACTION_USB_ANLG_HEADSET_PLUG);
+                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                    intent.putExtra("state", state);
+                    intent.putExtra("name", headsetName);
+                    ActivityManagerNative.broadcastStickyIntent(intent, null);
+                } else if (headset == BIT_USB_HEADSET_DGTL) {
+                    intent = new Intent(Intent.ACTION_USB_DGTL_HEADSET_PLUG);
+                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                    intent.putExtra("state", state);
+                    intent.putExtra("name", headsetName);
+                    ActivityManagerNative.broadcastStickyIntent(intent, null);
+                }
+
+                if (LOG) Slog.v(TAG, "Intent.ACTION_USB_HEADSET_PLUG: state: "+state+" name: "+headsetName);
+                // TODO: Should we require a permission?
+            }
+            if((headset == BIT_HEADSET) || (headset == BIT_HEADSET_NO_MIC)) {
+
+                //  Pack up the values and broadcast them to everyone
+                Intent intent = new Intent(Intent.ACTION_HEADSET_PLUG);
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                //int state = 0;
+                int microphone = 0;
+
+                if ((headset & HEADSETS_WITH_MIC) != 0) {
+                    microphone = 1;
+                }
+
+                intent.putExtra("state", state);
+                intent.putExtra("name", headsetName);
+                intent.putExtra("microphone", microphone);
+
+                if (LOG) Slog.v(TAG, "Intent.ACTION_HEADSET_PLUG: state: "+state+" name: "+headsetName+" mic: "+microphone);
+                // TODO: Should we require a permission?
+                ActivityManagerNative.broadcastStickyIntent(intent, null);
+            }
+        }
+    }
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            sendIntents(msg.arg1, msg.arg2, (String)msg.obj);
+            mWakeLock.release();
+        }
+    };
+}