audio framework: manage stream volume per device

Improve volume management by keeping track of volume for each type
of device independently.
Volume for each stream (MUSIC, RINGTONE, VOICE_CALL...) is now maintained
per device.

The main changes are:
- AudioService now keeps tracks of stream volumes per device:
 volume indexes are kept in a HashMap < device , index>.
 active device is queried from policy manager when a volume change request
 is received
 initalization, mute and unmute happen on all device simultaneously
- Settings: suffixes is added to volume keys to store each device
volume independently.
- AudioSystem/AudioPolicyService/AudioPolicyInterface: added a device argument
to setStreamVolumeIndex() and getStreamVolumeIndex() to address each
device independently.
- AudioPolicyManagerBase: keep track of stream volumes for each device
and apply volume according to current device selection.

Change-Id: I61ef1c45caadca04d16363bca4140e0f81901b3f
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 29f9d54..41756a1 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -183,16 +183,29 @@
 }
 
 static int
-android_media_AudioSystem_setStreamVolumeIndex(JNIEnv *env, jobject thiz, jint stream, jint index)
+android_media_AudioSystem_setStreamVolumeIndex(JNIEnv *env,
+                                               jobject thiz,
+                                               jint stream,
+                                               jint index,
+                                               jint device)
 {
-    return check_AudioSystem_Command(AudioSystem::setStreamVolumeIndex(static_cast <audio_stream_type_t>(stream), index));
+    return check_AudioSystem_Command(
+            AudioSystem::setStreamVolumeIndex(static_cast <audio_stream_type_t>(stream),
+                                              index,
+                                              (audio_devices_t)device));
 }
 
 static int
-android_media_AudioSystem_getStreamVolumeIndex(JNIEnv *env, jobject thiz, jint stream)
+android_media_AudioSystem_getStreamVolumeIndex(JNIEnv *env,
+                                               jobject thiz,
+                                               jint stream,
+                                               jint device)
 {
     int index;
-    if (AudioSystem::getStreamVolumeIndex(static_cast <audio_stream_type_t>(stream), &index) != NO_ERROR) {
+    if (AudioSystem::getStreamVolumeIndex(static_cast <audio_stream_type_t>(stream),
+                                          &index,
+                                          (audio_devices_t)device)
+            != NO_ERROR) {
         index = -1;
     }
     return index;
@@ -219,8 +232,8 @@
     {"setForceUse",         "(II)I",    (void *)android_media_AudioSystem_setForceUse},
     {"getForceUse",         "(I)I",     (void *)android_media_AudioSystem_getForceUse},
     {"initStreamVolume",    "(III)I",   (void *)android_media_AudioSystem_initStreamVolume},
-    {"setStreamVolumeIndex","(II)I",    (void *)android_media_AudioSystem_setStreamVolumeIndex},
-    {"getStreamVolumeIndex","(I)I",     (void *)android_media_AudioSystem_getStreamVolumeIndex},
+    {"setStreamVolumeIndex","(III)I",   (void *)android_media_AudioSystem_setStreamVolumeIndex},
+    {"getStreamVolumeIndex","(II)I",    (void *)android_media_AudioSystem_getStreamVolumeIndex},
     {"getDevicesForStream", "(I)I",     (void *)android_media_AudioSystem_getDevicesForStream},
 };
 
diff --git a/include/media/AudioSystem.h b/include/media/AudioSystem.h
index e49d8f5..13beaa5 100644
--- a/include/media/AudioSystem.h
+++ b/include/media/AudioSystem.h
@@ -170,8 +170,12 @@
     static status_t initStreamVolume(audio_stream_type_t stream,
                                       int indexMin,
                                       int indexMax);
-    static status_t setStreamVolumeIndex(audio_stream_type_t stream, int index);
-    static status_t getStreamVolumeIndex(audio_stream_type_t stream, int *index);
+    static status_t setStreamVolumeIndex(audio_stream_type_t stream,
+                                         int index,
+                                         audio_devices_t device);
+    static status_t getStreamVolumeIndex(audio_stream_type_t stream,
+                                         int *index,
+                                         audio_devices_t device);
 
     static uint32_t getStrategyForStream(audio_stream_type_t stream);
     static uint32_t getDevicesForStream(audio_stream_type_t stream);
diff --git a/include/media/IAudioPolicyService.h b/include/media/IAudioPolicyService.h
index 1ac0bbf..5a4d59b 100644
--- a/include/media/IAudioPolicyService.h
+++ b/include/media/IAudioPolicyService.h
@@ -73,8 +73,12 @@
     virtual status_t initStreamVolume(audio_stream_type_t stream,
                                       int indexMin,
                                       int indexMax) = 0;
-    virtual status_t setStreamVolumeIndex(audio_stream_type_t stream, int index) = 0;
-    virtual status_t getStreamVolumeIndex(audio_stream_type_t stream, int *index) = 0;
+    virtual status_t setStreamVolumeIndex(audio_stream_type_t stream,
+                                          int index,
+                                          audio_devices_t device) = 0;
+    virtual status_t getStreamVolumeIndex(audio_stream_type_t stream,
+                                          int *index,
+                                          audio_devices_t device) = 0;
     virtual uint32_t getStrategyForStream(audio_stream_type_t stream) = 0;
     virtual uint32_t getDevicesForStream(audio_stream_type_t stream) = 0;
     virtual audio_io_handle_t getOutputForEffect(effect_descriptor_t *desc) = 0;
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 72876d7..2eafd68 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -110,7 +110,7 @@
     private static final int SENDMSG_QUEUE = 2;
 
     // AudioHandler message.whats
-    private static final int MSG_SET_SYSTEM_VOLUME = 0;
+    private static final int MSG_SET_DEVICE_VOLUME = 0;
     private static final int MSG_PERSIST_VOLUME = 1;
     private static final int MSG_PERSIST_RINGER_MODE = 3;
     private static final int MSG_PERSIST_VIBRATE_SETTING = 4;
@@ -124,6 +124,7 @@
     private static final int MSG_BT_HEADSET_CNCT_FAILED = 12;
     private static final int MSG_RCDISPLAY_CLEAR = 13;
     private static final int MSG_RCDISPLAY_UPDATE = 14;
+    private static final int MSG_SET_ALL_VOLUMES = 15;
 
 
     // flags for MSG_PERSIST_VOLUME indicating if current and/or last audible volume should be
@@ -329,6 +330,7 @@
     // Keyguard manager proxy
     private KeyguardManager mKeyguardManager;
 
+
     ///////////////////////////////////////////////////////////////////////////
     // Construction
     ///////////////////////////////////////////////////////////////////////////
@@ -431,12 +433,17 @@
 
         // Correct stream index values for streams with aliases
         for (int i = 0; i < numStreamTypes; i++) {
+            int device = getDeviceForStream(i);
             if (STREAM_VOLUME_ALIAS[i] != i) {
-                int index = rescaleIndex(streams[i].mIndex, STREAM_VOLUME_ALIAS[i], i);
-                streams[i].mIndex = streams[i].getValidIndex(index);
-                setStreamVolumeIndex(i, index);
-                index = rescaleIndex(streams[i].mLastAudibleIndex, STREAM_VOLUME_ALIAS[i], i);
-                streams[i].mLastAudibleIndex = streams[i].getValidIndex(index);
+                int index = rescaleIndex(streams[i].getIndex(device, false  /* lastAudible */),
+                                STREAM_VOLUME_ALIAS[i],
+                                i);
+                streams[i].mIndex.put(device, streams[i].getValidIndex(index));
+                streams[i].applyDeviceVolume(device);
+                index = rescaleIndex(streams[i].getIndex(device, true  /* lastAudible */),
+                            STREAM_VOLUME_ALIAS[i],
+                            i);
+                streams[i].mLastAudibleIndex.put(device, streams[i].getValidIndex(index));
             }
         }
     }
@@ -488,10 +495,6 @@
         restoreMediaButtonReceiver();
     }
 
-    private void setStreamVolumeIndex(int stream, int index) {
-        AudioSystem.setStreamVolumeIndex(stream, (index + 5)/10);
-    }
-
     private int rescaleIndex(int index, int srcStream, int dstStream) {
         return (index * mStreamStates[dstStream].getMaxIndex() + mStreamStates[srcStream].getMaxIndex() / 2) / mStreamStates[srcStream].getMaxIndex();
     }
@@ -535,7 +538,11 @@
         // checkForRingerModeChange() in place of STREAM_RING or STREAM_NOTIFICATION)
         int streamTypeAlias = STREAM_VOLUME_ALIAS[streamType];
         VolumeStreamState streamState = mStreamStates[streamTypeAlias];
-        final int oldIndex = (streamState.muteCount() != 0) ? streamState.mLastAudibleIndex : streamState.mIndex;
+
+        final int device = getDeviceForStream(streamTypeAlias);
+        // get last audible index if stream is muted, current index otherwise
+        final int oldIndex = streamState.getIndex(device,
+                                                  (streamState.muteCount() != 0) /* lastAudible */);
         boolean adjustVolume = true;
 
         // If either the client forces allowing ringer modes for this adjustment,
@@ -564,32 +571,32 @@
                     if (STREAM_VOLUME_ALIAS[i] == streamTypeAlias) {
                         VolumeStreamState s = mStreamStates[i];
 
-                        s.adjustLastAudibleIndex(direction);
+                        s.adjustLastAudibleIndex(direction, device);
                         // Post a persist volume msg
                         sendMsg(mAudioHandler,
                                 MSG_PERSIST_VOLUME,
                                 SENDMSG_REPLACE,
                                 PERSIST_LAST_AUDIBLE,
-                                0,
+                                device,
                                 s,
                                 PERSIST_DELAY);
                     }
                 }
             }
-            index = streamState.mLastAudibleIndex;
+            index = streamState.getIndex(device, true  /* lastAudible */);
         } else {
-            if (adjustVolume && streamState.adjustIndex(direction)) {
+            if (adjustVolume && streamState.adjustIndex(direction, device)) {
                 // Post message to set system volume (it in turn will post a message
                 // to persist). Do not change volume if stream is muted.
                 sendMsg(mAudioHandler,
-                        MSG_SET_SYSTEM_VOLUME,
+                        MSG_SET_DEVICE_VOLUME,
                         SENDMSG_NOOP,
-                        0,
+                        device,
                         0,
                         streamState,
                         0);
             }
-            index = streamState.mIndex;
+            index = streamState.getIndex(device, false  /* lastAudible */);
         }
 
         sendVolumeUpdate(streamType, oldIndex, index, flags);
@@ -600,7 +607,10 @@
         ensureValidStreamType(streamType);
         VolumeStreamState streamState = mStreamStates[STREAM_VOLUME_ALIAS[streamType]];
 
-        final int oldIndex = (streamState.muteCount() != 0) ? streamState.mLastAudibleIndex : streamState.mIndex;
+        final int device = getDeviceForStream(streamType);
+        // get last audible index if stream is muted, current index otherwise
+        final int oldIndex = streamState.getIndex(device,
+                                                  (streamState.muteCount() != 0) /* lastAudible */);
 
         // setting ring or notifications volume to 0 on voice capable devices enters silent mode
         if (mVoiceCapable && (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
@@ -610,7 +620,11 @@
                 newRingerMode = System.getInt(mContentResolver, System.VIBRATE_IN_SILENT, 1) == 1
                     ? AudioManager.RINGER_MODE_VIBRATE
                     : AudioManager.RINGER_MODE_SILENT;
-                setStreamVolumeInt(STREAM_VOLUME_ALIAS[streamType], index, false, true);
+                setStreamVolumeInt(STREAM_VOLUME_ALIAS[streamType],
+                                   index,
+                                   device,
+                                   false,
+                                   true);
             } else {
                 newRingerMode = AudioManager.RINGER_MODE_NORMAL;
             }
@@ -618,9 +632,10 @@
         }
 
         index = rescaleIndex(index * 10, streamType, STREAM_VOLUME_ALIAS[streamType]);
-        setStreamVolumeInt(STREAM_VOLUME_ALIAS[streamType], index, false, true);
-
-        index = (streamState.muteCount() != 0) ? streamState.mLastAudibleIndex : streamState.mIndex;
+        setStreamVolumeInt(STREAM_VOLUME_ALIAS[streamType], index, device, false, true);
+        // get last audible index if stream is muted, current index otherwise
+        index = streamState.getIndex(device,
+                                     (streamState.muteCount() != 0) /* lastAudible */);
 
         sendVolumeUpdate(streamType, oldIndex, index, flags);
     }
@@ -648,35 +663,40 @@
      *
      * @param streamType Type of the stream
      * @param index Desired volume index of the stream
+     * @param device the device whose volume must be changed
      * @param force If true, set the volume even if the desired volume is same
      * as the current volume.
      * @param lastAudible If true, stores new index as last audible one
      */
-    private void setStreamVolumeInt(int streamType, int index, boolean force, boolean lastAudible) {
+    private void setStreamVolumeInt(int streamType,
+                                    int index,
+                                    int device,
+                                    boolean force,
+                                    boolean lastAudible) {
         VolumeStreamState streamState = mStreamStates[streamType];
 
         // If stream is muted, set last audible index only
         if (streamState.muteCount() != 0) {
             // Do not allow last audible index to be 0
             if (index != 0) {
-                streamState.setLastAudibleIndex(index);
+                streamState.setLastAudibleIndex(index, device);
                 // Post a persist volume msg
                 sendMsg(mAudioHandler,
                         MSG_PERSIST_VOLUME,
                         SENDMSG_REPLACE,
                         PERSIST_LAST_AUDIBLE,
-                        0,
+                        device,
                         streamState,
                         PERSIST_DELAY);
             }
         } else {
-            if (streamState.setIndex(index, lastAudible) || force) {
+            if (streamState.setIndex(index, device, lastAudible) || force) {
                 // Post message to set system volume (it in turn will post a message
                 // to persist).
                 sendMsg(mAudioHandler,
-                        MSG_SET_SYSTEM_VOLUME,
+                        MSG_SET_DEVICE_VOLUME,
                         SENDMSG_NOOP,
-                        0,
+                        device,
                         0,
                         streamState,
                         0);
@@ -708,7 +728,8 @@
     /** @see AudioManager#getStreamVolume(int) */
     public int getStreamVolume(int streamType) {
         ensureValidStreamType(streamType);
-        return (mStreamStates[streamType].mIndex + 5) / 10;
+        int device = getDeviceForStream(streamType);
+        return (mStreamStates[streamType].getIndex(device, false  /* lastAudible */) + 5) / 10;
     }
 
     /** @see AudioManager#getStreamMaxVolume(int) */
@@ -721,7 +742,8 @@
     /** Get last audible volume before stream was muted. */
     public int getLastAudibleStreamVolume(int streamType) {
         ensureValidStreamType(streamType);
-        return (mStreamStates[streamType].mLastAudibleIndex + 5) / 10;
+        int device = getDeviceForStream(streamType);
+        return (mStreamStates[streamType].getIndex(device, true  /* lastAudible */) + 5) / 10;
     }
 
     /** @see AudioManager#getRingerMode() */
@@ -764,9 +786,16 @@
                     // ring and notifications volume should never be 0 when not silenced
                     // on voice capable devices
                     if (mVoiceCapable &&
-                            STREAM_VOLUME_ALIAS[streamType] == AudioSystem.STREAM_RING &&
-                            mStreamStates[streamType].mLastAudibleIndex == 0) {
-                        mStreamStates[streamType].mLastAudibleIndex = 10;
+                            STREAM_VOLUME_ALIAS[streamType] == AudioSystem.STREAM_RING) {
+
+                        Set set = mStreamStates[streamType].mLastAudibleIndex.entrySet();
+                        Iterator i = set.iterator();
+                        while (i.hasNext()) {
+                            Map.Entry entry = (Map.Entry)i.next();
+                            if ((Integer)entry.getValue() == 0) {
+                                entry.setValue(10);
+                            }
+                        }
                     }
                     mStreamStates[streamType].mute(null, false);
                     mRingerModeMutedStreams &= ~(1 << streamType);
@@ -988,8 +1017,9 @@
                 }
             }
             int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
-            int index = mStreamStates[STREAM_VOLUME_ALIAS[streamType]].mIndex;
-            setStreamVolumeInt(STREAM_VOLUME_ALIAS[streamType], index, true, false);
+            int device = getDeviceForStream(streamType);
+            int index = mStreamStates[STREAM_VOLUME_ALIAS[streamType]].getIndex(device, false);
+            setStreamVolumeInt(STREAM_VOLUME_ALIAS[streamType], index, device, true, false);
         }
         return newModeOwnerPid;
     }
@@ -1253,28 +1283,7 @@
         for (int streamType = 0; streamType < numStreamTypes; streamType++) {
             VolumeStreamState streamState = mStreamStates[streamType];
 
-            String settingName = System.VOLUME_SETTINGS[STREAM_VOLUME_ALIAS[streamType]];
-            String lastAudibleSettingName = settingName + System.APPEND_FOR_LAST_AUDIBLE;
-            int index = Settings.System.getInt(mContentResolver,
-                                           settingName,
-                                           AudioManager.DEFAULT_STREAM_VOLUME[streamType]);
-            if (STREAM_VOLUME_ALIAS[streamType] != streamType) {
-                index = rescaleIndex(index * 10, STREAM_VOLUME_ALIAS[streamType], streamType);
-            } else {
-                index *= 10;
-            }
-            streamState.mIndex = streamState.getValidIndex(index);
-
-            index = (index + 5) / 10;
-            index = Settings.System.getInt(mContentResolver,
-                                            lastAudibleSettingName,
-                                            (index > 0) ? index : AudioManager.DEFAULT_STREAM_VOLUME[streamType]);
-            if (STREAM_VOLUME_ALIAS[streamType] != streamType) {
-                index = rescaleIndex(index * 10, STREAM_VOLUME_ALIAS[streamType], streamType);
-            } else {
-                index *= 10;
-            }
-            streamState.mLastAudibleIndex = streamState.getValidIndex(index);
+            streamState.readSettings();
 
             // unmute stream that was muted but is not affect by mute anymore
             if (streamState.muteCount() != 0 && !isStreamAffectedByMute(streamType)) {
@@ -1286,7 +1295,7 @@
             }
             // apply stream volume
             if (streamState.muteCount() == 0) {
-                setStreamVolumeIndex(streamType, streamState.mIndex);
+                streamState.applyAllVolumes();
             }
         }
 
@@ -1877,6 +1886,22 @@
         return false;
     }
 
+    private int getDeviceForStream(int stream) {
+        int device = AudioSystem.getDevicesForStream(stream);
+        if ((device & (device - 1)) != 0) {
+            // Multiple device selection is either:
+            //  - speaker + one other device: give priority to speaker in this case.
+            //  - one A2DP device + another device: happens with duplicated output. In this case
+            // retain the device on the A2DP output as the other must not correspond to an active
+            // selection if not the speaker.
+            if ((device & AudioSystem.DEVICE_OUT_SPEAKER) != 0) {
+                device = AudioSystem.DEVICE_OUT_SPEAKER;
+            } else {
+                device &= AudioSystem.DEVICE_OUT_ALL_A2DP;
+            }
+        }
+        return device;
+    }
 
     ///////////////////////////////////////////////////////////////////////////
     // Inner classes
@@ -1888,54 +1913,127 @@
         private String mVolumeIndexSettingName;
         private String mLastAudibleVolumeIndexSettingName;
         private int mIndexMax;
-        private int mIndex;
-        private int mLastAudibleIndex;
-        private ArrayList<VolumeDeathHandler> mDeathHandlers; //handles mute/solo requests client death
+        private final HashMap <Integer, Integer> mIndex = new HashMap <Integer, Integer>();
+        private final HashMap <Integer, Integer> mLastAudibleIndex =
+                                                                new HashMap <Integer, Integer>();
+        private ArrayList<VolumeDeathHandler> mDeathHandlers; //handles mute/solo clients death
 
         private VolumeStreamState(String settingName, int streamType) {
 
-            setVolumeIndexSettingName(settingName);
+            mVolumeIndexSettingName = settingName;
+            mLastAudibleVolumeIndexSettingName = settingName + System.APPEND_FOR_LAST_AUDIBLE;
 
             mStreamType = streamType;
-
-            final ContentResolver cr = mContentResolver;
             mIndexMax = MAX_STREAM_VOLUME[streamType];
-            mIndex = Settings.System.getInt(cr,
-                                            mVolumeIndexSettingName,
-                                            AudioManager.DEFAULT_STREAM_VOLUME[streamType]);
-            mLastAudibleIndex = Settings.System.getInt(cr,
-                                                       mLastAudibleVolumeIndexSettingName,
-                                                       (mIndex > 0) ? mIndex : AudioManager.DEFAULT_STREAM_VOLUME[streamType]);
             AudioSystem.initStreamVolume(streamType, 0, mIndexMax);
             mIndexMax *= 10;
-            mIndex = getValidIndex(10 * mIndex);
-            mLastAudibleIndex = getValidIndex(10 * mLastAudibleIndex);
-            setStreamVolumeIndex(streamType, mIndex);
+
+            readSettings();
+
+            applyAllVolumes();
+
             mDeathHandlers = new ArrayList<VolumeDeathHandler>();
         }
 
-        public void setVolumeIndexSettingName(String settingName) {
-            mVolumeIndexSettingName = settingName;
-            mLastAudibleVolumeIndexSettingName = settingName + System.APPEND_FOR_LAST_AUDIBLE;
+        public String getSettingNameForDevice(boolean lastAudible, int device) {
+            String name = lastAudible ?
+                            mLastAudibleVolumeIndexSettingName :
+                            mVolumeIndexSettingName;
+            String suffix = AudioSystem.getDeviceName(device);
+            if (suffix.isEmpty()) {
+                return name;
+            }
+            return name + "_" + suffix;
         }
 
-        public boolean adjustIndex(int deltaIndex) {
-            return setIndex(mIndex + deltaIndex * 10, true);
+        public void readSettings() {
+            int index = Settings.System.getInt(mContentResolver,
+                            mVolumeIndexSettingName,
+                            AudioManager.DEFAULT_STREAM_VOLUME[mStreamType]);
+
+            mIndex.clear();
+            mIndex.put(AudioSystem.DEVICE_OUT_DEFAULT, index);
+
+            index = Settings.System.getInt(mContentResolver,
+                            mLastAudibleVolumeIndexSettingName,
+                            (index > 0) ? index : AudioManager.DEFAULT_STREAM_VOLUME[mStreamType]);
+            mLastAudibleIndex.clear();
+            mLastAudibleIndex.put(AudioSystem.DEVICE_OUT_DEFAULT, index);
+
+            int remainingDevices = AudioSystem.DEVICE_OUT_ALL;
+            for (int i = 0; remainingDevices != 0; i++) {
+                int device = (1 << i);
+                if ((device & remainingDevices) == 0) {
+                    continue;
+                }
+                remainingDevices &= ~device;
+
+                // retrieve current volume for device
+                String name = getSettingNameForDevice(false, device);
+                index = Settings.System.getInt(mContentResolver, name, -1);
+                if (index == -1) {
+                    continue;
+                }
+                mIndex.put(device, getValidIndex(10 * index));
+
+                // retrieve last audible volume for device
+                name = getSettingNameForDevice(true, device);
+                index = Settings.System.getInt(mContentResolver, name, -1);
+                mLastAudibleIndex.put(device, getValidIndex(10 * index));
+            }
         }
 
-        public boolean setIndex(int index, boolean lastAudible) {
-            int oldIndex = mIndex;
-            mIndex = getValidIndex(index);
+        public void applyDeviceVolume(int device) {
+            AudioSystem.setStreamVolumeIndex(mStreamType,
+                                             (getIndex(device, false  /* lastAudible */) + 5)/10,
+                                             device);
+        }
 
-            if (oldIndex != mIndex) {
+        public void applyAllVolumes() {
+            // apply default volume first: by convention this will reset all
+            // devices volumes in audio policy manager to the supplied value
+            AudioSystem.setStreamVolumeIndex(mStreamType,
+                    (getIndex(AudioSystem.DEVICE_OUT_DEFAULT, false /* lastAudible */) + 5)/10,
+                    AudioSystem.DEVICE_OUT_DEFAULT);
+            // then apply device specific volumes
+            Set set = mIndex.entrySet();
+            Iterator i = set.iterator();
+            while (i.hasNext()) {
+                Map.Entry entry = (Map.Entry)i.next();
+                int device = ((Integer)entry.getKey()).intValue();
+                if (device != AudioSystem.DEVICE_OUT_DEFAULT) {
+                    AudioSystem.setStreamVolumeIndex(mStreamType,
+                                                     ((Integer)entry.getValue() + 5)/10,
+                                                     device);
+                }
+            }
+        }
+
+        public boolean adjustIndex(int deltaIndex, int device) {
+            return setIndex(getIndex(device,
+                                     false  /* lastAudible */) + deltaIndex * 10,
+                            device,
+                            true  /* lastAudible */);
+        }
+
+        public boolean setIndex(int index, int device, boolean lastAudible) {
+            int oldIndex = getIndex(device, false  /* lastAudible */);
+            index = getValidIndex(index);
+            mIndex.put(device, getValidIndex(index));
+
+            if (oldIndex != index) {
                 if (lastAudible) {
-                    mLastAudibleIndex = mIndex;
+                    mLastAudibleIndex.put(device, index);
                 }
                 // Apply change to all streams using this one as alias
                 int numStreamTypes = AudioSystem.getNumStreamTypes();
                 for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
                     if (streamType != mStreamType && STREAM_VOLUME_ALIAS[streamType] == mStreamType) {
-                        mStreamStates[streamType].setIndex(rescaleIndex(mIndex, mStreamType, streamType), lastAudible);
+                        mStreamStates[streamType].setIndex(rescaleIndex(index,
+                                                                        mStreamType,
+                                                                        streamType),
+                                                           device,
+                                                           lastAudible);
                     }
                 }
                 return true;
@@ -1944,12 +2042,29 @@
             }
         }
 
-        public void setLastAudibleIndex(int index) {
-            mLastAudibleIndex = getValidIndex(index);
+        public int getIndex(int device, boolean lastAudible) {
+            HashMap <Integer, Integer> indexes;
+            if (lastAudible) {
+                indexes = mLastAudibleIndex;
+            } else {
+                indexes = mIndex;
+            }
+            Integer index = indexes.get(device);
+            if (index == null) {
+                // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT
+                index = indexes.get(AudioSystem.DEVICE_OUT_DEFAULT);
+            }
+            return index.intValue();
         }
 
-        public void adjustLastAudibleIndex(int deltaIndex) {
-            setLastAudibleIndex(mLastAudibleIndex + deltaIndex * 10);
+        public void setLastAudibleIndex(int index, int device) {
+            mLastAudibleIndex.put(device, getValidIndex(index));
+        }
+
+        public void adjustLastAudibleIndex(int deltaIndex, int device) {
+            setLastAudibleIndex(getIndex(device,
+                                         true  /* lastAudible */) + deltaIndex * 10,
+                                device);
         }
 
         public int getMaxIndex() {
@@ -1994,10 +2109,20 @@
                                     mICallback.linkToDeath(this, 0);
                                 }
                                 mDeathHandlers.add(this);
-                                // If the stream is not yet muted by any client, set lvel to 0
+                                // If the stream is not yet muted by any client, set level to 0
                                 if (muteCount() == 0) {
-                                    setIndex(0, false);
-                                    sendMsg(mAudioHandler, MSG_SET_SYSTEM_VOLUME, SENDMSG_NOOP, 0, 0,
+                                    Set set = mIndex.entrySet();
+                                    Iterator i = set.iterator();
+                                    while (i.hasNext()) {
+                                        Map.Entry entry = (Map.Entry)i.next();
+                                        int device = ((Integer)entry.getKey()).intValue();
+                                        setIndex(0, device, false /* lastAudible */);
+                                    }
+                                    sendMsg(mAudioHandler,
+                                            MSG_SET_ALL_VOLUMES,
+                                            SENDMSG_NOOP,
+                                            0,
+                                            0,
                                             VolumeStreamState.this, 0);
                                 }
                             } catch (RemoteException e) {
@@ -2026,9 +2151,22 @@
                                     // If the stream is not muted any more, restore it's volume if
                                     // ringer mode allows it
                                     if (!isStreamAffectedByRingerMode(mStreamType) ||
-                                            getRingerMode() == AudioManager.RINGER_MODE_NORMAL) {
-                                        setIndex(mLastAudibleIndex, false);
-                                        sendMsg(mAudioHandler, MSG_SET_SYSTEM_VOLUME, SENDMSG_NOOP, 0, 0,
+                                            mRingerMode == AudioManager.RINGER_MODE_NORMAL) {
+                                        Set set = mIndex.entrySet();
+                                        Iterator i = set.iterator();
+                                        while (i.hasNext()) {
+                                            Map.Entry entry = (Map.Entry)i.next();
+                                            int device = ((Integer)entry.getKey()).intValue();
+                                            setIndex(getIndex(device,
+                                                              true  /* lastAudible */),
+                                                     device,
+                                                     false  /* lastAudible */);
+                                        }
+                                        sendMsg(mAudioHandler,
+                                                MSG_SET_ALL_VOLUMES,
+                                                SENDMSG_NOOP,
+                                                0,
+                                                0,
                                                 VolumeStreamState.this, 0);
                                     }
                                 }
@@ -2107,17 +2245,17 @@
     /** Handles internal volume messages in separate volume thread. */
     private class AudioHandler extends Handler {
 
-        private void setSystemVolume(VolumeStreamState streamState) {
+        private void setDeviceVolume(VolumeStreamState streamState, int device) {
 
-            // Adjust volume
-            setStreamVolumeIndex(streamState.mStreamType, streamState.mIndex);
+            // Apply volume
+            streamState.applyDeviceVolume(device);
 
             // Apply change to all streams using this one as alias
             int numStreamTypes = AudioSystem.getNumStreamTypes();
             for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
                 if (streamType != streamState.mStreamType &&
-                    STREAM_VOLUME_ALIAS[streamType] == streamState.mStreamType) {
-                    setStreamVolumeIndex(streamType, mStreamStates[streamType].mIndex);
+                        STREAM_VOLUME_ALIAS[streamType] == streamState.mStreamType) {
+                    mStreamStates[streamType].applyDeviceVolume(device);
                 }
             }
 
@@ -2126,21 +2264,39 @@
                     MSG_PERSIST_VOLUME,
                     SENDMSG_REPLACE,
                     PERSIST_CURRENT|PERSIST_LAST_AUDIBLE,
-                    0,
+                    device,
                     streamState,
                     PERSIST_DELAY);
 
         }
 
+        private void setAllVolumes(VolumeStreamState streamState) {
+
+            // Apply volume
+            streamState.applyAllVolumes();
+
+            // Apply change to all streams using this one as alias
+            int numStreamTypes = AudioSystem.getNumStreamTypes();
+            for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
+                if (streamType != streamState.mStreamType &&
+                        STREAM_VOLUME_ALIAS[streamType] == streamState.mStreamType) {
+                    mStreamStates[streamType].applyAllVolumes();
+                }
+            }
+        }
+
         private void persistVolume(VolumeStreamState streamState,
-                                   int persistType) {
+                                   int persistType,
+                                   int device) {
             if ((persistType & PERSIST_CURRENT) != 0) {
-                System.putInt(mContentResolver, streamState.mVolumeIndexSettingName,
-                              (streamState.mIndex + 5)/ 10);
+                System.putInt(mContentResolver,
+                          streamState.getSettingNameForDevice(false /* lastAudible */, device),
+                          (streamState.getIndex(device, false /* lastAudible */) + 5)/ 10);
             }
             if ((persistType & PERSIST_LAST_AUDIBLE) != 0) {
-                System.putInt(mContentResolver, streamState.mLastAudibleVolumeIndexSettingName,
-                    (streamState.mLastAudibleIndex + 5) / 10);
+                System.putInt(mContentResolver,
+                        streamState.getSettingNameForDevice(true /* lastAudible */, device),
+                        (streamState.getIndex(device, true  /* lastAudible */) + 5) / 10);
             }
         }
 
@@ -2223,12 +2379,16 @@
 
             switch (msg.what) {
 
-                case MSG_SET_SYSTEM_VOLUME:
-                    setSystemVolume((VolumeStreamState) msg.obj);
+                case MSG_SET_DEVICE_VOLUME:
+                    setDeviceVolume((VolumeStreamState) msg.obj, msg.arg1);
+                    break;
+
+                case MSG_SET_ALL_VOLUMES:
+                    setAllVolumes((VolumeStreamState) msg.obj);
                     break;
 
                 case MSG_PERSIST_VOLUME:
-                    persistVolume((VolumeStreamState) msg.obj, msg.arg1);
+                    persistVolume((VolumeStreamState) msg.obj, msg.arg1, msg.arg2);
                     break;
 
                 case MSG_PERSIST_RINGER_MODE:
@@ -2264,7 +2424,7 @@
                     synchronized (mConnectedDevices) {
                         Set set = mConnectedDevices.entrySet();
                         Iterator i = set.iterator();
-                        while(i.hasNext()){
+                        while (i.hasNext()) {
                             Map.Entry device = (Map.Entry)i.next();
                             AudioSystem.setDeviceConnectionState(
                                                             ((Integer)device.getKey()).intValue(),
@@ -2282,15 +2442,10 @@
                     // Restore stream volumes
                     int numStreamTypes = AudioSystem.getNumStreamTypes();
                     for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
-                        int index;
                         VolumeStreamState streamState = mStreamStates[streamType];
                         AudioSystem.initStreamVolume(streamType, 0, (streamState.mIndexMax + 5) / 10);
-                        if (streamState.muteCount() == 0) {
-                            index = streamState.mIndex;
-                        } else {
-                            index = 0;
-                        }
-                        setStreamVolumeIndex(streamType, index);
+
+                        streamState.applyAllVolumes();
                     }
 
                     // Restore ringer mode
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 474a842..81145d3 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -183,6 +183,7 @@
         }
     }
 
+
     /*
      * AudioPolicyService methods
      */
@@ -202,6 +203,23 @@
     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;
+    public static final int 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_ANLG_DOCK_HEADSET |
+                                              DEVICE_OUT_DGTL_DOCK_HEADSET |
+                                              DEVICE_OUT_DEFAULT);
+    public static final int DEVICE_OUT_ALL_A2DP = (DEVICE_OUT_BLUETOOTH_A2DP |
+                                                   DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
+                                                   DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER);
     // input devices
     public static final int DEVICE_IN_COMMUNICATION = 0x10000;
     public static final int DEVICE_IN_AMBIENT = 0x20000;
@@ -218,6 +236,54 @@
     public static final int DEVICE_STATE_AVAILABLE = 1;
     private static final int NUM_DEVICE_STATES = 1;
 
+    public static final String DEVICE_OUT_EARPIECE_NAME = "earpiece";
+    public static final String DEVICE_OUT_SPEAKER_NAME = "speaker";
+    public static final String DEVICE_OUT_WIRED_HEADSET_NAME = "headset";
+    public static final String DEVICE_OUT_WIRED_HEADPHONE_NAME = "headphone";
+    public static final String DEVICE_OUT_BLUETOOTH_SCO_NAME = "bt_sco";
+    public static final String DEVICE_OUT_BLUETOOTH_SCO_HEADSET_NAME = "bt_sco_hs";
+    public static final String DEVICE_OUT_BLUETOOTH_SCO_CARKIT_NAME = "bt_sco_carkit";
+    public static final String DEVICE_OUT_BLUETOOTH_A2DP_NAME = "bt_a2dp";
+    public static final String DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES_NAME = "bt_a2dp_hp";
+    public static final String DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER_NAME = "bt_a2dp_spk";
+    public static final String DEVICE_OUT_AUX_DIGITAL_NAME = "aux_digital";
+    public static final String DEVICE_OUT_ANLG_DOCK_HEADSET_NAME = "analog_dock";
+    public static final String DEVICE_OUT_DGTL_DOCK_HEADSET_NAME = "digital_dock";
+
+    public static String getDeviceName(int device)
+    {
+        switch(device) {
+        case DEVICE_OUT_EARPIECE:
+            return DEVICE_OUT_EARPIECE_NAME;
+        case DEVICE_OUT_SPEAKER:
+            return DEVICE_OUT_SPEAKER_NAME;
+        case DEVICE_OUT_WIRED_HEADSET:
+            return DEVICE_OUT_WIRED_HEADSET_NAME;
+        case DEVICE_OUT_WIRED_HEADPHONE:
+            return DEVICE_OUT_WIRED_HEADPHONE_NAME;
+        case DEVICE_OUT_BLUETOOTH_SCO:
+            return DEVICE_OUT_BLUETOOTH_SCO_NAME;
+        case DEVICE_OUT_BLUETOOTH_SCO_HEADSET:
+            return DEVICE_OUT_BLUETOOTH_SCO_HEADSET_NAME;
+        case DEVICE_OUT_BLUETOOTH_SCO_CARKIT:
+            return DEVICE_OUT_BLUETOOTH_SCO_CARKIT_NAME;
+        case DEVICE_OUT_BLUETOOTH_A2DP:
+            return DEVICE_OUT_BLUETOOTH_A2DP_NAME;
+        case DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES:
+            return DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES_NAME;
+        case DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER:
+            return DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER_NAME;
+        case DEVICE_OUT_AUX_DIGITAL:
+            return DEVICE_OUT_AUX_DIGITAL_NAME;
+        case DEVICE_OUT_ANLG_DOCK_HEADSET:
+            return DEVICE_OUT_ANLG_DOCK_HEADSET_NAME;
+        case DEVICE_OUT_DGTL_DOCK_HEADSET:
+            return DEVICE_OUT_DGTL_DOCK_HEADSET_NAME;
+        default:
+            return "";
+        }
+    }
+
     // phone state, match audio_mode???
     public static final int PHONE_STATE_OFFCALL = 0;
     public static final int PHONE_STATE_RINGING = 1;
@@ -251,7 +317,7 @@
     public static native int setForceUse(int usage, int config);
     public static native int getForceUse(int usage);
     public static native int initStreamVolume(int stream, int indexMin, int indexMax);
-    public static native int setStreamVolumeIndex(int stream, int index);
-    public static native int getStreamVolumeIndex(int stream);
+    public static native int setStreamVolumeIndex(int stream, int index, int device);
+    public static native int getStreamVolumeIndex(int stream, int device);
     public static native int getDevicesForStream(int stream);
 }
diff --git a/media/libmedia/AudioSystem.cpp b/media/libmedia/AudioSystem.cpp
index 5193848..d52f8f8 100644
--- a/media/libmedia/AudioSystem.cpp
+++ b/media/libmedia/AudioSystem.cpp
@@ -668,18 +668,22 @@
     return aps->initStreamVolume(stream, indexMin, indexMax);
 }
 
-status_t AudioSystem::setStreamVolumeIndex(audio_stream_type_t stream, int index)
+status_t AudioSystem::setStreamVolumeIndex(audio_stream_type_t stream,
+                                           int index,
+                                           audio_devices_t device)
 {
     const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service();
     if (aps == 0) return PERMISSION_DENIED;
-    return aps->setStreamVolumeIndex(stream, index);
+    return aps->setStreamVolumeIndex(stream, index, device);
 }
 
-status_t AudioSystem::getStreamVolumeIndex(audio_stream_type_t stream, int *index)
+status_t AudioSystem::getStreamVolumeIndex(audio_stream_type_t stream,
+                                           int *index,
+                                           audio_devices_t device)
 {
     const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service();
     if (aps == 0) return PERMISSION_DENIED;
-    return aps->getStreamVolumeIndex(stream, index);
+    return aps->getStreamVolumeIndex(stream, index, device);
 }
 
 uint32_t AudioSystem::getStrategyForStream(audio_stream_type_t stream)
diff --git a/media/libmedia/IAudioPolicyService.cpp b/media/libmedia/IAudioPolicyService.cpp
index 67d9c45..952d7b2 100644
--- a/media/libmedia/IAudioPolicyService.cpp
+++ b/media/libmedia/IAudioPolicyService.cpp
@@ -240,21 +240,28 @@
         return static_cast <status_t> (reply.readInt32());
     }
 
-    virtual status_t setStreamVolumeIndex(audio_stream_type_t stream, int index)
+    virtual status_t setStreamVolumeIndex(audio_stream_type_t stream,
+                                          int index,
+                                          audio_devices_t device)
     {
         Parcel data, reply;
         data.writeInterfaceToken(IAudioPolicyService::getInterfaceDescriptor());
         data.writeInt32(static_cast <uint32_t>(stream));
         data.writeInt32(index);
+        data.writeInt32(static_cast <uint32_t>(device));
         remote()->transact(SET_STREAM_VOLUME, data, &reply);
         return static_cast <status_t> (reply.readInt32());
     }
 
-    virtual status_t getStreamVolumeIndex(audio_stream_type_t stream, int *index)
+    virtual status_t getStreamVolumeIndex(audio_stream_type_t stream,
+                                          int *index,
+                                          audio_devices_t device)
     {
         Parcel data, reply;
         data.writeInterfaceToken(IAudioPolicyService::getInterfaceDescriptor());
         data.writeInt32(static_cast <uint32_t>(stream));
+        data.writeInt32(static_cast <uint32_t>(device));
+
         remote()->transact(GET_STREAM_VOLUME, data, &reply);
         int lIndex = reply.readInt32();
         if (index) *index = lIndex;
@@ -525,7 +532,10 @@
             audio_stream_type_t stream =
                     static_cast <audio_stream_type_t>(data.readInt32());
             int index = data.readInt32();
-            reply->writeInt32(static_cast <uint32_t>(setStreamVolumeIndex(stream, index)));
+            audio_devices_t device = static_cast <audio_devices_t>(data.readInt32());
+            reply->writeInt32(static_cast <uint32_t>(setStreamVolumeIndex(stream,
+                                                                          index,
+                                                                          device)));
             return NO_ERROR;
         } break;
 
@@ -533,8 +543,9 @@
             CHECK_INTERFACE(IAudioPolicyService, data, reply);
             audio_stream_type_t stream =
                     static_cast <audio_stream_type_t>(data.readInt32());
+            audio_devices_t device = static_cast <audio_devices_t>(data.readInt32());
             int index;
-            status_t status = getStreamVolumeIndex(stream, &index);
+            status_t status = getStreamVolumeIndex(stream, &index, device);
             reply->writeInt32(index);
             reply->writeInt32(static_cast <uint32_t>(status));
             return NO_ERROR;
diff --git a/services/audioflinger/AudioPolicyService.cpp b/services/audioflinger/AudioPolicyService.cpp
index 44311d0..586406b 100644
--- a/services/audioflinger/AudioPolicyService.cpp
+++ b/services/audioflinger/AudioPolicyService.cpp
@@ -407,7 +407,9 @@
     return NO_ERROR;
 }
 
-status_t AudioPolicyService::setStreamVolumeIndex(audio_stream_type_t stream, int index)
+status_t AudioPolicyService::setStreamVolumeIndex(audio_stream_type_t stream,
+                                                  int index,
+                                                  audio_devices_t device)
 {
     if (mpAudioPolicy == NULL) {
         return NO_INIT;
@@ -419,10 +421,19 @@
         return BAD_VALUE;
     }
 
-    return mpAudioPolicy->set_stream_volume_index(mpAudioPolicy, stream, index);
+    if (mpAudioPolicy->set_stream_volume_index_for_device) {
+        return mpAudioPolicy->set_stream_volume_index_for_device(mpAudioPolicy,
+                                                                stream,
+                                                                index,
+                                                                device);
+    } else {
+        return mpAudioPolicy->set_stream_volume_index(mpAudioPolicy, stream, index);
+    }
 }
 
-status_t AudioPolicyService::getStreamVolumeIndex(audio_stream_type_t stream, int *index)
+status_t AudioPolicyService::getStreamVolumeIndex(audio_stream_type_t stream,
+                                                  int *index,
+                                                  audio_devices_t device)
 {
     if (mpAudioPolicy == NULL) {
         return NO_INIT;
@@ -430,7 +441,14 @@
     if (uint32_t(stream) >= AUDIO_STREAM_CNT) {
         return BAD_VALUE;
     }
-    return mpAudioPolicy->get_stream_volume_index(mpAudioPolicy, stream, index);
+    if (mpAudioPolicy->get_stream_volume_index_for_device) {
+        return mpAudioPolicy->get_stream_volume_index_for_device(mpAudioPolicy,
+                                                                stream,
+                                                                index,
+                                                                device);
+    } else {
+        return mpAudioPolicy->get_stream_volume_index(mpAudioPolicy, stream, index);
+    }
 }
 
 uint32_t AudioPolicyService::getStrategyForStream(audio_stream_type_t stream)
diff --git a/services/audioflinger/AudioPolicyService.h b/services/audioflinger/AudioPolicyService.h
index e98ec68..333bc02 100644
--- a/services/audioflinger/AudioPolicyService.h
+++ b/services/audioflinger/AudioPolicyService.h
@@ -88,8 +88,12 @@
     virtual status_t initStreamVolume(audio_stream_type_t stream,
                                       int indexMin,
                                       int indexMax);
-    virtual status_t setStreamVolumeIndex(audio_stream_type_t stream, int index);
-    virtual status_t getStreamVolumeIndex(audio_stream_type_t stream, int *index);
+    virtual status_t setStreamVolumeIndex(audio_stream_type_t stream,
+                                          int index,
+                                          audio_devices_t device);
+    virtual status_t getStreamVolumeIndex(audio_stream_type_t stream,
+                                          int *index,
+                                          audio_devices_t device);
 
     virtual uint32_t getStrategyForStream(audio_stream_type_t stream);
     virtual uint32_t getDevicesForStream(audio_stream_type_t stream);