fix issue 1713090: After a Bluetooth call, MusicPlayer starts playing on speaker rather than wired external audio.

Temporary fix until audio routing is refactored in Eclair release:
- centralized and synchronized all audio routing control in AudioService.setRouting()
- deprecated AudioManager.setRouting() and AudioManager.getRouting() methods
diff --git a/api/current.xml b/api/current.xml
index 2bab739..2f93971 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -63668,7 +63668,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="mode" type="int">
@@ -63879,7 +63879,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="mode" type="int">
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index f509fb5..e43c9c4 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -642,7 +642,9 @@
      *           <var>false</var> to turn it off
      */
     public void setSpeakerphoneOn(boolean on){
-        setRouting(MODE_IN_CALL, on ? ROUTE_SPEAKER : ROUTE_EARPIECE, ROUTE_ALL);
+        // Temporary fix for issue #1713090 until audio routing is refactored in eclair release.
+        // MODE_INVALID indicates to AudioService that setRouting() was initiated by AudioManager
+        setRoutingP(MODE_INVALID, on ? ROUTE_SPEAKER: 0, ROUTE_SPEAKER);
     }
 
     /**
@@ -651,7 +653,7 @@
      * @return true if speakerphone is on, false if it's off
      */
     public boolean isSpeakerphoneOn() {
-        return (getRouting(MODE_IN_CALL) & ROUTE_SPEAKER) == 0 ? false : true;
+        return (getRoutingP(MODE_IN_CALL) & ROUTE_SPEAKER) == 0 ? false : true;
      }
 
     /**
@@ -661,14 +663,9 @@
      *           headset; <var>false</var> to route audio to/from phone earpiece
      */
     public void setBluetoothScoOn(boolean on){
-        // Don't disable A2DP when turning off SCO.
-        // A2DP does not affect in-call routing.
-        setRouting(MODE_RINGTONE,
-               on ? ROUTE_BLUETOOTH_SCO: ROUTE_SPEAKER, ROUTE_ALL & ~ROUTE_BLUETOOTH_A2DP);
-        setRouting(MODE_NORMAL,
-                on ? ROUTE_BLUETOOTH_SCO: ROUTE_SPEAKER, ROUTE_ALL & ~ROUTE_BLUETOOTH_A2DP);
-        setRouting(MODE_IN_CALL,
-                on ? ROUTE_BLUETOOTH_SCO: ROUTE_EARPIECE, ROUTE_ALL);
+        // Temporary fix for issue #1713090 until audio routing is refactored in eclair release.
+        // MODE_INVALID indicates to AudioService that setRouting() was initiated by AudioManager
+        setRoutingP(MODE_INVALID, on ? ROUTE_BLUETOOTH_SCO: 0, ROUTE_BLUETOOTH_SCO);
     }
 
     /**
@@ -678,7 +675,7 @@
      *         false if otherwise
      */
     public boolean isBluetoothScoOn() {
-        return (getRouting(MODE_IN_CALL) & ROUTE_BLUETOOTH_SCO) == 0 ? false : true;
+        return (getRoutingP(MODE_IN_CALL) & ROUTE_BLUETOOTH_SCO) == 0 ? false : true;
     }
 
     /**
@@ -688,12 +685,9 @@
      *           headset; <var>false</var> disable A2DP audio
      */
     public void setBluetoothA2dpOn(boolean on){
-        // the audio flinger chooses A2DP as a higher priority,
-        // so there is no need to disable other routes.
-        setRouting(MODE_RINGTONE,
-               on ? ROUTE_BLUETOOTH_A2DP: 0, ROUTE_BLUETOOTH_A2DP);
-        setRouting(MODE_NORMAL,
-                on ? ROUTE_BLUETOOTH_A2DP: 0, ROUTE_BLUETOOTH_A2DP);
+        // Temporary fix for issue #1713090 until audio routing is refactored in eclair release.
+        // MODE_INVALID indicates to AudioService that setRouting() was initiated by AudioManager
+        setRoutingP(MODE_INVALID, on ? ROUTE_BLUETOOTH_A2DP: 0, ROUTE_BLUETOOTH_A2DP);
     }
 
     /**
@@ -703,7 +697,7 @@
      *         false if otherwise
      */
     public boolean isBluetoothA2dpOn() {
-        return (getRouting(MODE_NORMAL) & ROUTE_BLUETOOTH_A2DP) == 0 ? false : true;
+        return (getRoutingP(MODE_NORMAL) & ROUTE_BLUETOOTH_A2DP) == 0 ? false : true;
     }
 
     /**
@@ -714,14 +708,9 @@
      * @hide
      */
     public void setWiredHeadsetOn(boolean on){
-        // A2DP has higher priority than wired headset, so headset connect/disconnect events
-        // should not affect A2DP routing
-        setRouting(MODE_NORMAL,
-                on ? ROUTE_HEADSET : ROUTE_SPEAKER, ROUTE_ALL & ~ROUTE_BLUETOOTH_A2DP);
-        setRouting(MODE_RINGTONE,
-                on ? ROUTE_HEADSET | ROUTE_SPEAKER : ROUTE_SPEAKER, ROUTE_ALL & ~ROUTE_BLUETOOTH_A2DP);
-        setRouting(MODE_IN_CALL,
-                on ? ROUTE_HEADSET : ROUTE_EARPIECE, ROUTE_ALL);
+        // Temporary fix for issue #1713090 until audio routing is refactored in eclair release.
+        // MODE_INVALID indicates to AudioService that setRouting() was initiated by AudioManager
+        setRoutingP(MODE_INVALID, on ? ROUTE_HEADSET: 0, ROUTE_HEADSET);
     }
 
     /**
@@ -732,7 +721,7 @@
      * @hide
      */
     public boolean isWiredHeadsetOn() {
-        return (getRouting(MODE_NORMAL) & ROUTE_HEADSET) == 0 ? false : true;
+        return (getRoutingP(MODE_NORMAL) & ROUTE_HEADSET) == 0 ? false : true;
     }
 
     /**
@@ -860,7 +849,11 @@
      *               more of ROUTE_xxx types. Set bits indicate that route should be on
      * @param mask   bit vector of routes to change, created from one or more of
      * ROUTE_xxx types. Unset bits indicate the route should be left unchanged
+     *
+     * @deprecated   Do not set audio routing directly, use setSpeakerphoneOn(),
+     * setBluetoothScoOn(), setBluetoothA2dpOn() and setWiredHeadsetOn() methods instead.
      */
+
     public void setRouting(int mode, int routes, int mask) {
         IAudioService service = getService();
         try {
@@ -876,7 +869,10 @@
      * @param mode audio mode to get route (e.g., MODE_RINGTONE)
      * @return an audio route bit vector that can be compared with ROUTE_xxx
      * bits
+     * @deprecated   Do not query audio routing directly, use isSpeakerphoneOn(),
+     * isBluetoothScoOn(), isBluetoothA2dpOn() and isWiredHeadsetOn() methods instead.
      */
+    @Deprecated
     public int getRouting(int mode) {
         IAudioService service = getService();
         try {
@@ -1076,4 +1072,31 @@
       * {@hide}
       */
      private IBinder mICallBack = new Binder();
+
+     /**
+      * {@hide}
+      */
+     private void setRoutingP(int mode, int routes, int mask) {
+         IAudioService service = getService();
+         try {
+             service.setRouting(mode, routes, mask);
+         } catch (RemoteException e) {
+             Log.e(TAG, "Dead object in setRouting", e);
+         }
+     }
+
+
+     /**
+      * {@hide}
+      */
+     private int getRoutingP(int mode) {
+         IAudioService service = getService();
+         try {
+             return service.getRouting(mode);
+         } catch (RemoteException e) {
+             Log.e(TAG, "Dead object in getRouting", e);
+             return -1;
+         }
+     }
+
 }
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 2e3e460..881de4d 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -100,6 +100,10 @@
     private int[] mRoutes = new int[AudioSystem.NUM_MODES];
     private Object mSettingsLock = new Object();
     private boolean mMediaServerOk;
+    private boolean mSpeakerIsOn;
+    private boolean mBluetoothScoIsConnected;
+    private boolean mHeadsetIsConnected;
+    private boolean mBluetoothA2dpIsConnected;
 
     private SoundPool mSoundPool;
     private Object mSoundEffectsLock = new Object();
@@ -189,6 +193,10 @@
         mMediaServerOk = true;
         AudioSystem.setErrorCallback(mAudioSystemCallback);
         loadSoundEffects();
+        mSpeakerIsOn = false;
+        mBluetoothScoIsConnected = false;
+        mHeadsetIsConnected = false;
+        mBluetoothA2dpIsConnected = false;
     }
 
     private void createAudioSystemThread() {
@@ -606,8 +614,9 @@
         }
         synchronized (mSettingsLock) {
             if (mode != mMode) {
-                AudioSystem.setMode(mode);
-                mMode = mode;
+                if (AudioSystem.setMode(mode) == AudioSystem.AUDIO_STATUS_OK) {
+                    mMode = mode;
+                }
             }
             int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
             int index = mStreamStates[streamType].mIndex;
@@ -623,18 +632,167 @@
 
     /** @see AudioManager#setRouting(int, int, int) */
     public void setRouting(int mode, int routes, int mask) {
+        int incallMask = 0;
+        int ringtoneMask = 0;
+        int normalMask = 0;
+
         if (!checkAudioSettingsPermission("setRouting()")) {
             return;
         }
         synchronized (mSettingsLock) {
-            if ((mRoutes[mode] & mask) != (routes & mask)) {
-                AudioSystem.setRouting(mode, routes, mask);
-                mRoutes[mode] = (mRoutes[mode] & ~mask) | (routes & mask);
+            // Temporary fix for issue #1713090 until audio routing is refactored in eclair release.
+            // mode AudioSystem.MODE_INVALID is used only by the following AudioManager methods:
+            // setWiredHeadsetOn(), setBluetoothA2dpOn(), setBluetoothScoOn() and setSpeakerphoneOn().
+            // If applications are using AudioManager.setRouting() that is now deprecated, the routing
+            // command will be ignored.
+            if (mode == AudioSystem.MODE_INVALID) {
+                switch (mask) {
+                case AudioSystem.ROUTE_SPEAKER:
+                    // handle setSpeakerphoneOn()
+                    if (routes != 0 && !mSpeakerIsOn) {
+                        mSpeakerIsOn = true;
+                        mRoutes[AudioSystem.MODE_IN_CALL] = AudioSystem.ROUTE_SPEAKER;
+                        incallMask = AudioSystem.ROUTE_ALL;
+                    } else if (mSpeakerIsOn) {
+                        mSpeakerIsOn = false;
+                        if (mBluetoothScoIsConnected) {
+                            mRoutes[AudioSystem.MODE_IN_CALL] = AudioSystem.ROUTE_BLUETOOTH_SCO;
+                        } else if (mHeadsetIsConnected) {
+                            mRoutes[AudioSystem.MODE_IN_CALL] = AudioSystem.ROUTE_HEADSET;
+                        } else {
+                            mRoutes[AudioSystem.MODE_IN_CALL] = AudioSystem.ROUTE_EARPIECE;
+                        }
+                        incallMask = AudioSystem.ROUTE_ALL;
+                    }
+                    break;
+
+                case AudioSystem.ROUTE_BLUETOOTH_SCO:
+                    // handle setBluetoothScoOn()
+                    if (routes != 0 && !mBluetoothScoIsConnected) {
+                        mBluetoothScoIsConnected = true;
+                        mRoutes[AudioSystem.MODE_IN_CALL] = AudioSystem.ROUTE_BLUETOOTH_SCO;
+                        mRoutes[AudioSystem.MODE_RINGTONE] = (mRoutes[AudioSystem.MODE_RINGTONE] & AudioSystem.ROUTE_BLUETOOTH_A2DP) |
+                                                              AudioSystem.ROUTE_BLUETOOTH_SCO;
+                        mRoutes[AudioSystem.MODE_NORMAL] = (mRoutes[AudioSystem.MODE_NORMAL] & AudioSystem.ROUTE_BLUETOOTH_A2DP) |
+                                                            AudioSystem.ROUTE_BLUETOOTH_SCO;
+                        incallMask = AudioSystem.ROUTE_ALL;
+                        // A2DP has higher priority than SCO headset, so headset connect/disconnect events
+                        // should not affect A2DP routing
+                        ringtoneMask = AudioSystem.ROUTE_ALL & ~AudioSystem.ROUTE_BLUETOOTH_A2DP;
+                        normalMask = AudioSystem.ROUTE_ALL & ~AudioSystem.ROUTE_BLUETOOTH_A2DP;
+                    } else if (mBluetoothScoIsConnected) {
+                        mBluetoothScoIsConnected = false;
+                        if (mHeadsetIsConnected) {
+                            mRoutes[AudioSystem.MODE_IN_CALL] = AudioSystem.ROUTE_HEADSET;
+                            mRoutes[AudioSystem.MODE_RINGTONE] = (mRoutes[AudioSystem.MODE_RINGTONE] & AudioSystem.ROUTE_BLUETOOTH_A2DP) |
+                                                                 (AudioSystem.ROUTE_HEADSET|AudioSystem.ROUTE_SPEAKER);
+                            mRoutes[AudioSystem.MODE_NORMAL] = (mRoutes[AudioSystem.MODE_NORMAL] & AudioSystem.ROUTE_BLUETOOTH_A2DP) |
+                                                               AudioSystem.ROUTE_HEADSET;
+                        } else {
+                            if (mSpeakerIsOn) {
+                                mRoutes[AudioSystem.MODE_IN_CALL] = AudioSystem.ROUTE_SPEAKER;
+                            } else {
+                                mRoutes[AudioSystem.MODE_IN_CALL] = AudioSystem.ROUTE_EARPIECE;
+                            }
+                            mRoutes[AudioSystem.MODE_RINGTONE] = (mRoutes[AudioSystem.MODE_RINGTONE] & AudioSystem.ROUTE_BLUETOOTH_A2DP) |
+                                                                 AudioSystem.ROUTE_SPEAKER;
+                            mRoutes[AudioSystem.MODE_NORMAL] = (mRoutes[AudioSystem.MODE_NORMAL] & AudioSystem.ROUTE_BLUETOOTH_A2DP) |
+                                                               AudioSystem.ROUTE_SPEAKER;
+                        }
+                        incallMask = AudioSystem.ROUTE_ALL;
+                        // A2DP has higher priority than SCO headset, so headset connect/disconnect events
+                        // should not affect A2DP routing
+                        ringtoneMask = AudioSystem.ROUTE_ALL & ~AudioSystem.ROUTE_BLUETOOTH_A2DP;
+                        normalMask = AudioSystem.ROUTE_ALL & ~AudioSystem.ROUTE_BLUETOOTH_A2DP;
+                    }
+                    break;
+
+                case AudioSystem.ROUTE_HEADSET:
+                    // handle setWiredHeadsetOn()
+                    if (routes != 0 && !mHeadsetIsConnected) {
+                        mHeadsetIsConnected = true;
+                        // do not act upon headset connection if bluetooth SCO is connected to match phone app behavior
+                        if (!mBluetoothScoIsConnected) {
+                            mRoutes[AudioSystem.MODE_IN_CALL] = AudioSystem.ROUTE_HEADSET;
+                            mRoutes[AudioSystem.MODE_RINGTONE] = (mRoutes[AudioSystem.MODE_RINGTONE] & AudioSystem.ROUTE_BLUETOOTH_A2DP) |
+                                                                 (AudioSystem.ROUTE_HEADSET|AudioSystem.ROUTE_SPEAKER);
+                            mRoutes[AudioSystem.MODE_NORMAL] = (mRoutes[AudioSystem.MODE_NORMAL] & AudioSystem.ROUTE_BLUETOOTH_A2DP) |
+                                                               AudioSystem.ROUTE_HEADSET;
+                            incallMask = AudioSystem.ROUTE_ALL;
+                            // A2DP has higher priority than wired headset, so headset connect/disconnect events
+                            // should not affect A2DP routing
+                            ringtoneMask = AudioSystem.ROUTE_ALL & ~AudioSystem.ROUTE_BLUETOOTH_A2DP;
+                            normalMask = AudioSystem.ROUTE_ALL & ~AudioSystem.ROUTE_BLUETOOTH_A2DP;
+                        }
+                    } else if (mHeadsetIsConnected) {
+                        mHeadsetIsConnected = false;
+                        // do not act upon headset disconnection if bluetooth SCO is connected to match phone app behavior
+                        if (!mBluetoothScoIsConnected) {
+                            if (mSpeakerIsOn) {
+                                mRoutes[AudioSystem.MODE_IN_CALL] = AudioSystem.ROUTE_SPEAKER;
+                            } else {
+                                mRoutes[AudioSystem.MODE_IN_CALL] = AudioSystem.ROUTE_EARPIECE;
+                            }
+                            mRoutes[AudioSystem.MODE_RINGTONE] = (mRoutes[AudioSystem.MODE_RINGTONE] & AudioSystem.ROUTE_BLUETOOTH_A2DP) |
+                                                                 AudioSystem.ROUTE_SPEAKER;
+                            mRoutes[AudioSystem.MODE_NORMAL] = (mRoutes[AudioSystem.MODE_NORMAL] & AudioSystem.ROUTE_BLUETOOTH_A2DP) |
+                                                               AudioSystem.ROUTE_SPEAKER;
+
+                            incallMask = AudioSystem.ROUTE_ALL;
+                            // A2DP has higher priority than wired headset, so headset connect/disconnect events
+                            // should not affect A2DP routing
+                            ringtoneMask = AudioSystem.ROUTE_ALL & ~AudioSystem.ROUTE_BLUETOOTH_A2DP;
+                            normalMask = AudioSystem.ROUTE_ALL & ~AudioSystem.ROUTE_BLUETOOTH_A2DP;
+                        }
+                    }
+                    break;
+
+                case AudioSystem.ROUTE_BLUETOOTH_A2DP:
+                    // handle setBluetoothA2dpOn()
+                    if (routes != 0 && !mBluetoothA2dpIsConnected) {
+                        mBluetoothA2dpIsConnected = true;
+                        mRoutes[AudioSystem.MODE_RINGTONE] |= AudioSystem.ROUTE_BLUETOOTH_A2DP;
+                        mRoutes[AudioSystem.MODE_NORMAL] |= AudioSystem.ROUTE_BLUETOOTH_A2DP;
+                        // the audio flinger chooses A2DP as a higher priority,
+                        // so there is no need to disable other routes.
+                        ringtoneMask = AudioSystem.ROUTE_BLUETOOTH_A2DP;
+                        normalMask = AudioSystem.ROUTE_BLUETOOTH_A2DP;
+                    } else if (mBluetoothA2dpIsConnected) {
+                        mBluetoothA2dpIsConnected = false;
+                        mRoutes[AudioSystem.MODE_RINGTONE] &= ~AudioSystem.ROUTE_BLUETOOTH_A2DP;
+                        mRoutes[AudioSystem.MODE_NORMAL] &= ~AudioSystem.ROUTE_BLUETOOTH_A2DP;
+                        // the audio flinger chooses A2DP as a higher priority,
+                        // so there is no need to disable other routes.
+                        ringtoneMask = AudioSystem.ROUTE_BLUETOOTH_A2DP;
+                        normalMask = AudioSystem.ROUTE_BLUETOOTH_A2DP;
+                    }
+                    break;
+                }
+                
+                // incallMask is != 0 means we must apply ne routing to MODE_IN_CALL mode
+                if (incallMask != 0) {
+                    AudioSystem.setRouting(AudioSystem.MODE_IN_CALL,
+                                           mRoutes[AudioSystem.MODE_IN_CALL],
+                                           incallMask);
+                }
+                // ringtoneMask is != 0 means we must apply ne routing to MODE_RINGTONE mode
+                if (ringtoneMask != 0) {
+                    AudioSystem.setRouting(AudioSystem.MODE_RINGTONE,
+                                           mRoutes[AudioSystem.MODE_RINGTONE],
+                                           ringtoneMask);
+                }
+                // normalMask is != 0 means we must apply ne routing to MODE_NORMAL mode
+                if (normalMask != 0) {
+                    AudioSystem.setRouting(AudioSystem.MODE_NORMAL,
+                                           mRoutes[AudioSystem.MODE_NORMAL],
+                                           normalMask);
+                }
+
+                int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
+                int index = mStreamStates[streamType].mIndex;
+                syncRingerAndNotificationStreamVolume(streamType, index, true);
+                setStreamVolumeInt(streamType, index, true);
             }
-            int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
-            int index = mStreamStates[streamType].mIndex;
-            syncRingerAndNotificationStreamVolume(streamType, index, true);
-            setStreamVolumeInt(streamType, index, true);
         }
     }