am 586b8ee6: am 2f442304: Merge "Remote volume handling" into jb-dev

* commit '586b8ee62e0245e3c88ce9db51e1c1c3b4f1fb2c':
  Remote volume handling
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 352f996..746bb84 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -27,6 +27,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Rect;
+import android.media.RemoteControlClient;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -2170,6 +2171,19 @@
             "android.intent.action.USB_AUDIO_DEVICE_PLUG";
 
     /**
+     * @hide (to be un-hidden)
+     * Broadcast Action: the volume handled by the receiver should be updated based on the
+     * mutually exclusive extras, {@link #EXTRA_VOLUME_UPDATE_DIRECTION}
+     * and {@link #EXTRA_VOLUME_UPDATE_VALUE}.
+     *
+     * @see #EXTRA_VOLUME_UPDATE_DIRECTION
+     * @see #EXTRA_VOLUME_UPDATE_VALUE
+     * @see android.media.RemoteControlClient
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_VOLUME_UPDATE = "android.intent.action.VOLUME_UPDATE";
+
+    /**
      * <p>Broadcast Action: The user has switched on advanced settings in the settings app:</p>
      * <ul>
      *   <li><em>state</em> - A boolean value indicating whether the settings is on or off.</li>
@@ -2857,6 +2871,27 @@
      */
     public static final String EXTRA_USERID =
             "android.intent.extra.user_id";
+
+    /**
+     * @hide (to be un-hidden)
+     * An integer indicating whether the volume is to be increased (positive value) or decreased
+     * (negative value). For bundled changes, the absolute value indicates the number of changes
+     * in the same direction, e.g. +3 corresponds to three "volume up" changes.
+     * @see #ACTION_VOLUME_UPDATE
+     */
+    public static final String EXTRA_VOLUME_UPDATE_DIRECTION =
+            "android.intent.extra.VOLUME_UPDATE_DIRECTION";
+
+    /**
+     * @hide (to be un-hidden)
+     * An integer indicating the new volume value, always between 0 and the value set for
+     * {@link RemoteControlClient#PLAYBACKINFO_VOLUME_MAX} with
+     * {@link RemoteControlClient#setPlaybackInformation(int, int)}
+     * @see #ACTION_VOLUME_UPDATE
+     */
+    public static final String EXTRA_VOLUME_UPDATE_VALUE =
+            "android.intent.extra.VOLUME_UPDATE_VALUE";
+
     // ---------------------------------------------------------------------
     // ---------------------------------------------------------------------
     // Intent flags (see mFlags variable).
diff --git a/core/java/android/view/VolumePanel.java b/core/java/android/view/VolumePanel.java
index 30d87c4..4d4e985c 100644
--- a/core/java/android/view/VolumePanel.java
+++ b/core/java/android/view/VolumePanel.java
@@ -34,10 +34,7 @@
 import android.net.Uri;
 import android.os.Handler;
 import android.os.Message;
-import android.os.RemoteException;
 import android.os.Vibrator;
-import android.provider.Settings;
-import android.provider.Settings.System;
 import android.util.Log;
 import android.view.WindowManager.LayoutParams;
 import android.widget.ImageView;
@@ -92,9 +89,13 @@
     private static final int MSG_TIMEOUT = 5;
     private static final int MSG_RINGER_MODE_CHANGED = 6;
     private static final int MSG_MUTE_CHANGED = 7;
+    private static final int MSG_REMOTE_VOLUME_CHANGED = 8;
+    private static final int MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN = 9;
+    private static final int MSG_SLIDER_VISIBILITY_CHANGED = 10;
 
     // Pseudo stream type for master volume
     private static final int STREAM_MASTER = -100;
+    // Pseudo stream type for remote volume is defined in AudioService.STREAM_REMOTE_MUSIC
 
     protected Context mContext;
     private AudioManager mAudioManager;
@@ -158,10 +159,15 @@
                 true),
         // for now, use media resources for master volume
         MasterStream(STREAM_MASTER,
-                R.string.volume_icon_description_media,
+                R.string.volume_icon_description_media, //FIXME should have its own description
                 R.drawable.ic_audio_vol,
                 R.drawable.ic_audio_vol_mute,
-                false);
+                false),
+        RemoteStream(AudioService.STREAM_REMOTE_MUSIC,
+                R.string.volume_icon_description_media, //FIXME should have its own description
+                R.drawable.ic_media_route_on_holo_dark,
+                R.drawable.ic_media_route_disabled_holo_dark,
+                false);// will be dynamically updated
 
         int streamType;
         int descRes;
@@ -187,7 +193,8 @@
         StreamResources.MediaStream,
         StreamResources.NotificationStream,
         StreamResources.AlarmStream,
-        StreamResources.MasterStream
+        StreamResources.MasterStream,
+        StreamResources.RemoteStream
     };
 
     /** Object that contains data for each slider */
@@ -307,6 +314,8 @@
     private boolean isMuted(int streamType) {
         if (streamType == STREAM_MASTER) {
             return mAudioManager.isMasterMute();
+        } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
+            return (mAudioService.getRemoteStreamVolume() <= 0);
         } else {
             return mAudioManager.isStreamMute(streamType);
         }
@@ -315,6 +324,8 @@
     private int getStreamMaxVolume(int streamType) {
         if (streamType == STREAM_MASTER) {
             return mAudioManager.getMasterMaxVolume();
+        } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
+            return mAudioService.getRemoteStreamMaxVolume();
         } else {
             return mAudioManager.getStreamMaxVolume(streamType);
         }
@@ -323,6 +334,8 @@
     private int getStreamVolume(int streamType) {
         if (streamType == STREAM_MASTER) {
             return mAudioManager.getMasterVolume();
+        } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
+            return mAudioService.getRemoteStreamVolume();
         } else {
             return mAudioManager.getStreamVolume(streamType);
         }
@@ -331,6 +344,8 @@
     private void setStreamVolume(int streamType, int index, int flags) {
         if (streamType == STREAM_MASTER) {
             mAudioManager.setMasterVolume(index, flags);
+        } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
+            mAudioService.setRemoteStreamVolume(index);
         } else {
             mAudioManager.setStreamVolume(streamType, index, flags);
         }
@@ -408,7 +423,11 @@
                 mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) {
             sc.icon.setImageResource(R.drawable.ic_audio_ring_notif_vibrate);
         }
-        if (sc.streamType != mAudioManager.getMasterStreamType() && muted) {
+        if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) {
+            // never disable touch interactions for remote playback, the muting is not tied to
+            // the state of the phone.
+            sc.seekbarView.setEnabled(true);
+        } else if (sc.streamType != mAudioManager.getMasterStreamType() && muted) {
             sc.seekbarView.setEnabled(false);
         } else {
             sc.seekbarView.setEnabled(true);
@@ -456,6 +475,40 @@
         obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget();
     }
 
+    public void postRemoteVolumeChanged(int streamType, int flags) {
+        if (hasMessages(MSG_REMOTE_VOLUME_CHANGED)) return;
+        synchronized (this) {
+            if (mStreamControls == null) {
+                createSliders();
+            }
+        }
+        removeMessages(MSG_FREE_RESOURCES);
+        obtainMessage(MSG_REMOTE_VOLUME_CHANGED, streamType, flags).sendToTarget();
+    }
+
+    public void postRemoteSliderVisibility(boolean visible) {
+        obtainMessage(MSG_SLIDER_VISIBILITY_CHANGED,
+                AudioService.STREAM_REMOTE_MUSIC, visible ? 1 : 0).sendToTarget();
+    }
+
+    /**
+     * Called by AudioService when it has received new remote playback information that
+     * would affect the VolumePanel display (mainly volumes). The difference with
+     * {@link #postRemoteVolumeChanged(int, int)} is that the handling of the posted message
+     * (MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN) will only update the volume slider if it is being
+     * displayed.
+     * This special code path is due to the fact that remote volume updates arrive to AudioService
+     * asynchronously. So after AudioService has sent the volume update (which should be treated
+     * as a request to update the volume), the application will likely set a new volume. If the UI
+     * is still up, we need to refresh the display to show this new value.
+     */
+    public void postHasNewRemotePlaybackInfo() {
+        if (hasMessages(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN)) return;
+        // don't create or prevent resources to be freed, if they disappear, this update came too
+        //   late and shouldn't warrant the panel to be displayed longer
+        obtainMessage(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN).sendToTarget();
+    }
+
     public void postMasterVolumeChanged(int flags) {
         postVolumeChanged(STREAM_MASTER, flags);
     }
@@ -595,6 +648,11 @@
                 max++;
                 break;
             }
+
+            case AudioService.STREAM_REMOTE_MUSIC: {
+                if (LOGD) { Log.d(TAG, "showing remote volume "+index+" over "+ max); }
+                break;
+            }
         }
 
         StreamControl sc = mStreamControls.get(streamType);
@@ -603,7 +661,8 @@
                 sc.seekbarView.setMax(max);
             }
             sc.seekbarView.setProgress(index);
-            if (streamType != mAudioManager.getMasterStreamType() && isMuted(streamType)) {
+            if (streamType != mAudioManager.getMasterStreamType()
+                    && streamType != AudioService.STREAM_REMOTE_MUSIC && isMuted(streamType)) {
                 sc.seekbarView.setEnabled(false);
             } else {
                 sc.seekbarView.setEnabled(true);
@@ -611,7 +670,9 @@
         }
 
         if (!mDialog.isShowing()) {
-            mAudioManager.forceVolumeControlStream(streamType);
+            int stream = (streamType == AudioService.STREAM_REMOTE_MUSIC) ? -1 : streamType;
+            // when the stream is for remote playback, use -1 to reset the stream type evaluation
+            mAudioManager.forceVolumeControlStream(stream);
             mDialog.setContentView(mView);
             // Showing dialog - use collapsed state
             if (mShowCombinedVolumes) {
@@ -621,7 +682,8 @@
         }
 
         // Do a little vibrate if applicable (only when going into vibrate mode)
-        if ((flags & AudioManager.FLAG_VIBRATE) != 0 &&
+        if ((streamType != AudioService.STREAM_REMOTE_MUSIC) &&
+                ((flags & AudioManager.FLAG_VIBRATE) != 0) &&
                 mAudioService.isStreamAffectedByRingerMode(streamType) &&
                 mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) {
             sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY);
@@ -668,6 +730,72 @@
         mVibrator.vibrate(VIBRATE_DURATION);
     }
 
+    protected void onRemoteVolumeChanged(int streamType, int flags) {
+        // streamType is the real stream type being affected, but for the UI sliders, we
+        // refer to AudioService.STREAM_REMOTE_MUSIC. We still play the beeps on the real
+        // stream type.
+        if (LOGD) Log.d(TAG, "onRemoteVolumeChanged(stream:"+streamType+", flags: " + flags + ")");
+
+        if (((flags & AudioManager.FLAG_SHOW_UI) != 0) || mDialog.isShowing()) {
+            synchronized (this) {
+                if (mActiveStreamType != AudioService.STREAM_REMOTE_MUSIC) {
+                    reorderSliders(AudioService.STREAM_REMOTE_MUSIC);
+                }
+                onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, flags);
+            }
+        } else {
+            if (LOGD) Log.d(TAG, "not calling onShowVolumeChanged(), no FLAG_SHOW_UI or no UI");
+        }
+
+        if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) {
+            removeMessages(MSG_PLAY_SOUND);
+            sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY);
+        }
+
+        if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) {
+            removeMessages(MSG_PLAY_SOUND);
+            removeMessages(MSG_VIBRATE);
+            onStopSounds();
+        }
+
+        removeMessages(MSG_FREE_RESOURCES);
+        sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY);
+
+        resetTimeout();
+    }
+
+    protected void onRemoteVolumeUpdateIfShown() {
+        if (LOGD) Log.d(TAG, "onRemoteVolumeUpdateIfShown()");
+        if (mDialog.isShowing()
+                && (mActiveStreamType == AudioService.STREAM_REMOTE_MUSIC)
+                && (mStreamControls != null)) {
+            onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, 0);
+        }
+    }
+
+
+    /**
+     * Handler for MSG_SLIDER_VISIBILITY_CHANGED
+     * Hide or show a slider
+     * @param streamType can be a valid stream type value, or VolumePanel.STREAM_MASTER,
+     *                   or AudioService.STREAM_REMOTE_MUSIC
+     * @param visible
+     */
+    synchronized protected void onSliderVisibilityChanged(int streamType, int visible) {
+        if (LOGD) Log.d(TAG, "onSliderVisibilityChanged(stream="+streamType+", visi="+visible+")");
+        boolean isVisible = (visible == 1);
+        for (int i = STREAMS.length - 1 ; i >= 0 ; i--) {
+            StreamResources streamRes = STREAMS[i];
+            if (streamRes.streamType == streamType) {
+                streamRes.show = isVisible;
+                if (!isVisible && (mActiveStreamType == streamType)) {
+                    mActiveStreamType = -1;
+                }
+                break;
+            }
+        }
+    }
+
     /**
      * Lock on this VolumePanel instance as long as you use the returned ToneGenerator.
      */
@@ -769,6 +897,19 @@
                 }
                 break;
             }
+
+            case MSG_REMOTE_VOLUME_CHANGED: {
+                onRemoteVolumeChanged(msg.arg1, msg.arg2);
+                break;
+            }
+
+            case MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN:
+                onRemoteVolumeUpdateIfShown();
+                break;
+
+            case MSG_SLIDER_VISIBILITY_CHANGED:
+                onSliderVisibilityChanged(msg.arg1, msg.arg2);
+                break;
         }
     }
 
@@ -798,6 +939,17 @@
     }
 
     public void onStopTrackingTouch(SeekBar seekBar) {
+        final Object tag = seekBar.getTag();
+        if (tag instanceof StreamControl) {
+            StreamControl sc = (StreamControl) tag;
+            // because remote volume updates are asynchronous, AudioService might have received
+            // a new remote volume value since the finger adjusted the slider. So when the
+            // progress of the slider isn't being tracked anymore, adjust the slider to the last
+            // "published" remote volume value, so the UI reflects the actual volume.
+            if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) {
+                seekBar.setProgress(getStreamVolume(AudioService.STREAM_REMOTE_MUSIC));
+            }
+        }
     }
 
     public void onClick(View v) {
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 69d8ef1..a82df87 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1032,6 +1032,8 @@
   <java-symbol type="drawable" name="notification_template_icon_bg" />
   <java-symbol type="drawable" name="notification_template_icon_low_bg" />
   <java-symbol type="drawable" name="ic_lockscreen_unlock_phantom" />
+  <java-symbol type="drawable" name="ic_media_route_on_holo_dark" />
+  <java-symbol type="drawable" name="ic_media_route_disabled_holo_dark" />
 
   <java-symbol type="layout" name="action_bar_home" />
   <java-symbol type="layout" name="action_bar_title_item" />
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 7b1113b..b5613f3 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -99,7 +99,10 @@
 
     /**
      * @hide Broadcast intent when the volume for a particular stream type changes.
-     * Includes the stream, the new volume and previous volumes
+     * Includes the stream, the new volume and previous volumes.
+     * Notes:
+     *  - for internal platform use only, do not make public,
+     *  - never used for "remote" volume changes
      *
      * @see #EXTRA_VOLUME_STREAM_TYPE
      * @see #EXTRA_VOLUME_STREAM_VALUE
@@ -1501,6 +1504,24 @@
         return AudioSystem.isStreamActive(STREAM_MUSIC, 0);
     }
 
+    /**
+     * @hide
+     * If the stream is active locally or remotely, adjust its volume according to the enforced
+     * priority rules.
+     * Note: only AudioManager.STREAM_MUSIC is supported at the moment
+     */
+    public void adjustLocalOrRemoteStreamVolume(int streamType, int direction) {
+        if (streamType != STREAM_MUSIC) {
+            Log.w(TAG, "adjustLocalOrRemoteStreamVolume() doesn't support stream " + streamType);
+        }
+        IAudioService service = getService();
+        try {
+            service.adjustLocalOrRemoteStreamVolume(streamType, direction);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in adjustLocalOrRemoteStreamVolume", e);
+        }
+    }
+
     /*
      * Sets a generic audio configuration parameter. The use of these parameters
      * are platform dependant, see libaudio
@@ -2077,10 +2098,12 @@
         }
         IAudioService service = getService();
         try {
-            service.registerRemoteControlClient(rcClient.getRcMediaIntent(),   /* mediaIntent   */
-                    rcClient.getIRemoteControlClient(),                        /* rcClient      */
+            int rcseId = service.registerRemoteControlClient(
+                    rcClient.getRcMediaIntent(),       /* mediaIntent   */
+                    rcClient.getIRemoteControlClient(),/* rcClient      */
                     // used to match media button event receiver and audio focus
-                    mContext.getPackageName());                                /* packageName   */
+                    mContext.getPackageName());        /* packageName   */
+            rcClient.setRcseId(rcseId);
         } catch (RemoteException e) {
             Log.e(TAG, "Dead object in registerRemoteControlClient"+e);
         }
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 27dc6e3..418dc52 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -101,6 +101,8 @@
 
     /** Debug remote control client/display feature */
     protected static final boolean DEBUG_RC = false;
+    /** Debug volumes */
+    protected static final boolean DEBUG_VOL = false;
 
     /** How long to delay before persisting a change in volume/ringer mode. */
     private static final int PERSIST_DELAY = 500;
@@ -120,7 +122,7 @@
     /** If the msg is already queued, queue this one and leave the old. */
     private static final int SENDMSG_QUEUE = 2;
 
-    // AudioHandler message.whats
+    // AudioHandler messages
     private static final int MSG_SET_DEVICE_VOLUME = 0;
     private static final int MSG_PERSIST_VOLUME = 1;
     private static final int MSG_PERSIST_MASTER_VOLUME = 2;
@@ -138,11 +140,13 @@
     private static final int MSG_SET_ALL_VOLUMES = 14;
     private static final int MSG_PERSIST_MASTER_VOLUME_MUTE = 15;
     private static final int MSG_REPORT_NEW_ROUTES = 16;
-    // messages handled under wakelock, can only be queued, i.e. sent with queueMsgUnderWakeLock(),
+    private static final int MSG_REEVALUATE_REMOTE = 17;
+    // start of messages handled under wakelock
+    //   these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
     //   and not with sendMsg(..., ..., SENDMSG_QUEUE, ...)
-    private static final int MSG_SET_WIRED_DEVICE_CONNECTION_STATE = 17;
-    private static final int MSG_SET_A2DP_CONNECTION_STATE = 18;
-
+    private static final int MSG_SET_WIRED_DEVICE_CONNECTION_STATE = 18;
+    private static final int MSG_SET_A2DP_CONNECTION_STATE = 19;
+    // end of messages handled under wakelock
 
     // flags for MSG_PERSIST_VOLUME indicating if current and/or last audible volume should be
     // persisted
@@ -405,6 +409,11 @@
     final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers
             = new RemoteCallbackList<IAudioRoutesObserver>();
 
+    /**
+     * A fake stream type to match the notion of remote media playback
+     */
+    public final static int STREAM_REMOTE_MUSIC = -200;
+
     ///////////////////////////////////////////////////////////////////////////
     // Construction
     ///////////////////////////////////////////////////////////////////////////
@@ -488,6 +497,11 @@
         mMasterVolumeRamp = context.getResources().getIntArray(
                 com.android.internal.R.array.config_masterVolumeRamp);
 
+        mMainRemote = new RemotePlaybackState(-1, MAX_STREAM_VOLUME[AudioManager.STREAM_MUSIC],
+                MAX_STREAM_VOLUME[AudioManager.STREAM_MUSIC]);
+        mHasRemotePlayback = false;
+        mMainRemoteIsActive = false;
+        postReevaluateRemote();
     }
 
     private void createAudioSystemThread() {
@@ -657,9 +671,20 @@
         adjustSuggestedStreamVolume(direction, AudioManager.USE_DEFAULT_STREAM_TYPE, flags);
     }
 
+    /** @see AudioManager#adjustLocalOrRemoteStreamVolume(int, int) with current assumption
+     *  on streamType: fixed to STREAM_MUSIC */
+    public void adjustLocalOrRemoteStreamVolume(int streamType, int direction) {
+        if (DEBUG_VOL) Log.d(TAG, "adjustLocalOrRemoteStreamVolume(dir="+direction+")");
+        if (checkUpdateRemoteStateIfActive(AudioSystem.STREAM_MUSIC)) {
+            adjustRemoteVolume(AudioSystem.STREAM_MUSIC, direction, 0);
+        } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) {
+            adjustStreamVolume(AudioSystem.STREAM_MUSIC, direction, 0);
+        }
+    }
+
     /** @see AudioManager#adjustVolume(int, int, int) */
     public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags) {
-
+        if (DEBUG_VOL) Log.d(TAG, "adjustSuggestedStreamVolume() stream="+suggestedStreamType);
         int streamType;
         if (mVolumeControlStream != -1) {
             streamType = mVolumeControlStream;
@@ -668,17 +693,27 @@
         }
 
         // Play sounds on STREAM_RING only and if lock screen is not on.
-        if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 &&
+        if ((streamType != STREAM_REMOTE_MUSIC) &&
+                (flags & AudioManager.FLAG_PLAY_SOUND) != 0 &&
                 ((mStreamVolumeAlias[streamType] != AudioSystem.STREAM_RING)
                  || (mKeyguardManager != null && mKeyguardManager.isKeyguardLocked()))) {
             flags &= ~AudioManager.FLAG_PLAY_SOUND;
         }
 
-        adjustStreamVolume(streamType, direction, flags);
+        if (streamType == STREAM_REMOTE_MUSIC) {
+            // don't play sounds for remote
+            flags &= ~AudioManager.FLAG_PLAY_SOUND;
+            //if (DEBUG_VOL) Log.i(TAG, "Need to adjust remote volume: calling adjustRemoteVolume()");
+            adjustRemoteVolume(AudioSystem.STREAM_MUSIC, direction, flags);
+        } else {
+            adjustStreamVolume(streamType, direction, flags);
+        }
     }
 
     /** @see AudioManager#adjustStreamVolume(int, int, int) */
     public void adjustStreamVolume(int streamType, int direction, int flags) {
+        if (DEBUG_VOL) Log.d(TAG, "adjustStreamVolume() stream="+streamType+", dir="+direction);
+
         ensureValidDirection(direction);
         ensureValidStreamType(streamType);
 
@@ -1370,6 +1405,10 @@
                 }
             }
             int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
+            if (streamType == STREAM_REMOTE_MUSIC) {
+                // here handle remote media playback the same way as local playback
+                streamType = AudioManager.STREAM_MUSIC;
+            }
             int device = getDeviceForStream(streamType);
             int index = mStreamStates[mStreamVolumeAlias[streamType]].getIndex(device, false);
             setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, true, false);
@@ -2169,40 +2208,61 @@
                     // Log.v(TAG, "getActiveStreamType: Forcing STREAM_VOICE_CALL...");
                     return AudioSystem.STREAM_VOICE_CALL;
                 }
-            } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) {
-                // Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC...");
-                return AudioSystem.STREAM_MUSIC;
             } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
-                // Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING..."
-                //        + " b/c USE_DEFAULT_STREAM_TYPE...");
-                return AudioSystem.STREAM_RING;
+                // Having the suggested stream be USE_DEFAULT_STREAM_TYPE is how remote control
+                // volume can have priority over STREAM_MUSIC
+                if (checkUpdateRemoteStateIfActive(AudioSystem.STREAM_MUSIC)) {
+                    if (DEBUG_VOL)
+                        Log.v(TAG, "getActiveStreamType: Forcing STREAM_REMOTE_MUSIC");
+                    return STREAM_REMOTE_MUSIC;
+                } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) {
+                    if (DEBUG_VOL)
+                        Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active");
+                    return AudioSystem.STREAM_MUSIC;
+                } else {
+                    if (DEBUG_VOL)
+                        Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING b/c default");
+                    return AudioSystem.STREAM_RING;
+                }
+            } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) {
+                if (DEBUG_VOL)
+                    Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active");
+                return AudioSystem.STREAM_MUSIC;
             } else {
-                // Log.v(TAG, "getActiveStreamType: Returning suggested type " + suggestedStreamType);
+                if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Returning suggested type "
+                        + suggestedStreamType);
                 return suggestedStreamType;
             }
         } else {
             if (isInCommunication()) {
                 if (AudioSystem.getForceUse(AudioSystem.FOR_COMMUNICATION)
                         == AudioSystem.FORCE_BT_SCO) {
-                    // Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO...");
+                    if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO");
                     return AudioSystem.STREAM_BLUETOOTH_SCO;
                 } else {
-                    // Log.v(TAG, "getActiveStreamType: Forcing STREAM_VOICE_CALL...");
+                    if (DEBUG_VOL)  Log.v(TAG, "getActiveStreamType: Forcing STREAM_VOICE_CALL");
                     return AudioSystem.STREAM_VOICE_CALL;
                 }
             } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_NOTIFICATION,
-                            NOTIFICATION_VOLUME_DELAY_MS) ||
-                       AudioSystem.isStreamActive(AudioSystem.STREAM_RING,
+                    NOTIFICATION_VOLUME_DELAY_MS) ||
+                    AudioSystem.isStreamActive(AudioSystem.STREAM_RING,
                             NOTIFICATION_VOLUME_DELAY_MS)) {
-                // Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION...");
+                if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION");
                 return AudioSystem.STREAM_NOTIFICATION;
-            } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) ||
-                       (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE)) {
-                // Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC "
-                //        + " b/c USE_DEFAULT_STREAM_TYPE...");
-                return AudioSystem.STREAM_MUSIC;
+            } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
+                if (checkUpdateRemoteStateIfActive(AudioSystem.STREAM_MUSIC)) {
+                    // Having the suggested stream be USE_DEFAULT_STREAM_TYPE is how remote control
+                    // volume can have priority over STREAM_MUSIC
+                    if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_REMOTE_MUSIC");
+                    return STREAM_REMOTE_MUSIC;
+                } else {
+                    if (DEBUG_VOL)
+                        Log.v(TAG, "getActiveStreamType: using STREAM_MUSIC as default");
+                    return AudioSystem.STREAM_MUSIC;
+                }
             } else {
-                // Log.v(TAG, "getActiveStreamType: Returning suggested type " + suggestedStreamType);
+                if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Returning suggested type "
+                        + suggestedStreamType);
                 return suggestedStreamType;
             }
         }
@@ -3036,6 +3096,10 @@
                     mRoutesObservers.finishBroadcast();
                     break;
                 }
+
+                case MSG_REEVALUATE_REMOTE:
+                    onReevaluateRemote();
+                    break;
             }
         }
     }
@@ -4103,6 +4167,8 @@
             // remote control client died, make sure the displays don't use it anymore
             //  by setting its remote control client to null
             registerRemoteControlClient(mMediaIntent, null/*rcClient*/, null/*ignored*/);
+            // the dead client was maybe handling remote playback, reevaluate
+            postReevaluateRemote();
         }
 
         public IBinder getBinder() {
@@ -4110,7 +4176,46 @@
         }
     }
 
+    /**
+     * A global counter for RemoteControlClient identifiers
+     */
+    private static int sLastRccId = 0;
+
+    private class RemotePlaybackState {
+        int mRccId;
+        int mVolume;
+        int mVolumeMax;
+        int mVolumeHandling;
+
+        private RemotePlaybackState(int id, int vol, int volMax) {
+            mRccId = id;
+            mVolume = vol;
+            mVolumeMax = volMax;
+            mVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
+        }
+    }
+
+    /**
+     * Internal cache for the playback information of the RemoteControlClient whose volume gets to
+     * be controlled by the volume keys ("main"), so we don't have to iterate over the RC stack
+     * every time we need this info.
+     */
+    private RemotePlaybackState mMainRemote;
+    /**
+     * Indicates whether the "main" RemoteControlClient is considered active.
+     * Use synchronized on mMainRemote.
+     */
+    private boolean mMainRemoteIsActive;
+    /**
+     * Indicates whether there is remote playback going on. True even if there is no "active"
+     * remote playback (mMainRemoteIsActive is false), but a RemoteControlClient has declared it
+     * handles remote playback.
+     * Use synchronized on mMainRemote.
+     */
+    private boolean mHasRemotePlayback;
+
     private static class RemoteControlStackEntry {
+        public int mRccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
         /**
          * The target for the ACTION_MEDIA_BUTTON events.
          * Always non null.
@@ -4129,6 +4234,24 @@
          *     but no remote control client has been registered) */
         public IRemoteControlClient mRcClient;
         public RcClientDeathHandler mRcClientDeathHandler;
+        /**
+         * Information only used for non-local playback
+         */
+        public int mPlaybackType;
+        public int mPlaybackVolume;
+        public int mPlaybackVolumeMax;
+        public int mPlaybackVolumeHandling;
+        public int mPlaybackStream;
+        public int mPlaybackState;
+
+        public void resetPlaybackInfo() {
+            mPlaybackType = RemoteControlClient.PLAYBACK_TYPE_LOCAL;
+            mPlaybackVolume = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
+            mPlaybackVolumeMax = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
+            mPlaybackVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
+            mPlaybackStream = AudioManager.STREAM_MUSIC;
+            mPlaybackState = RemoteControlClient.PLAYSTATE_STOPPED;
+        }
 
         /** precondition: mediaIntent != null, eventReceiver != null */
         public RemoteControlStackEntry(PendingIntent mediaIntent, ComponentName eventReceiver) {
@@ -4136,6 +4259,9 @@
             mReceiverComponent = eventReceiver;
             mCallingUid = -1;
             mRcClient = null;
+            mRccId = ++sLastRccId;
+
+            resetPlaybackInfo();
         }
 
         public void unlinkToRcClientDeath() {
@@ -4185,13 +4311,48 @@
                 pw.println("  pi: " + rcse.mMediaIntent +
                         "  -- ercvr: " + rcse.mReceiverComponent +
                         "  -- client: " + rcse.mRcClient +
-                        "  -- uid: " + rcse.mCallingUid);
+                        "  -- uid: " + rcse.mCallingUid +
+                        "  -- type: " + rcse.mPlaybackType +
+                        "  state: " + rcse.mPlaybackState);
             }
         }
     }
 
     /**
      * Helper function:
+     * Display in the log the current entries in the remote control stack, focusing
+     * on RemoteControlClient data
+     */
+    private void dumpRCCStack(PrintWriter pw) {
+        pw.println("\nRemote Control Client stack entries:");
+        synchronized(mRCStack) {
+            Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+            while(stackIterator.hasNext()) {
+                RemoteControlStackEntry rcse = stackIterator.next();
+                pw.println("  uid: " + rcse.mCallingUid +
+                        "  -- id: " + rcse.mRccId +
+                        "  -- type: " + rcse.mPlaybackType +
+                        "  -- state: " + rcse.mPlaybackState +
+                        "  -- vol handling: " + rcse.mPlaybackVolumeHandling +
+                        "  -- vol: " + rcse.mPlaybackVolume +
+                        "  -- volMax: " + rcse.mPlaybackVolumeMax);
+            }
+        }
+        synchronized (mMainRemote) {
+            pw.println("\nRemote Volume State:");
+            pw.println("  has remote: " + mHasRemotePlayback);
+            pw.println("  is remote active: " + mMainRemoteIsActive);
+            pw.println("  rccId: " + mMainRemote.mRccId);
+            pw.println("  volume handling: "
+                    + ((mMainRemote.mVolumeHandling == RemoteControlClient.PLAYBACK_VOLUME_FIXED) ?
+                            "PLAYBACK_VOLUME_FIXED(0)" : "PLAYBACK_VOLUME_VARIABLE(1)"));
+            pw.println("  volume: " + mMainRemote.mVolume);
+            pw.println("  volume steps: " + mMainRemote.mVolumeMax);
+        }
+    }
+
+    /**
+     * Helper function:
      * Remove any entry in the remote control stack that has the same package name as packageName
      * Pre-condition: packageName != null
      */
@@ -4556,13 +4717,15 @@
 
     /**
      * see AudioManager.registerRemoteControlClient(ComponentName eventReceiver, ...)
+     * @return the unique ID of the RemoteControlStackEntry associated with the RemoteControlClient
      * Note: using this method with rcClient == null is a way to "disable" the IRemoteControlClient
      *     without modifying the RC stack, but while still causing the display to refresh (will
      *     become blank as a result of this)
      */
-    public void registerRemoteControlClient(PendingIntent mediaIntent,
+    public int registerRemoteControlClient(PendingIntent mediaIntent,
             IRemoteControlClient rcClient, String callingPackageName) {
         if (DEBUG_RC) Log.i(TAG, "Register remote control client rcClient="+rcClient);
+        int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
         synchronized(mAudioFocusLock) {
             synchronized(mRCStack) {
                 // store the new display information
@@ -4581,8 +4744,10 @@
                         rcse.mCallingUid = Binder.getCallingUid();
                         if (rcClient == null) {
                             // here rcse.mRcClientDeathHandler is null;
+                            rcse.resetPlaybackInfo();
                             break;
                         }
+                        rccId = rcse.mRccId;
 
                         // there is a new (non-null) client:
                         // 1/ give the new client the current display (if any)
@@ -4616,6 +4781,7 @@
                 }
             }
         }
+        return rccId;
     }
 
     /**
@@ -4790,6 +4956,248 @@
         }
     }
 
+    // FIXME send a message instead of updating the stack synchronously
+    public void setPlaybackInfoForRcc(int rccId, int what, int value) {
+        if(DEBUG_RC) Log.d(TAG, "setPlaybackInfoForRcc(id="+rccId+", what="+what+",val="+value+")");
+        synchronized(mRCStack) {
+            Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+            while(stackIterator.hasNext()) {
+                RemoteControlStackEntry rcse = stackIterator.next();
+                if (rcse.mRccId == rccId) {
+                    switch (what) {
+                        case RemoteControlClient.PLAYBACKINFO_PLAYBACK_TYPE:
+                            rcse.mPlaybackType = value;
+                            postReevaluateRemote();
+                            break;
+                        case RemoteControlClient.PLAYBACKINFO_VOLUME:
+                            rcse.mPlaybackVolume = value;
+                            synchronized (mMainRemote) {
+                                if (rccId == mMainRemote.mRccId) {
+                                    mMainRemote.mVolume = value;
+                                    mVolumePanel.postHasNewRemotePlaybackInfo();
+                                }
+                            }
+                            break;
+                        case RemoteControlClient.PLAYBACKINFO_VOLUME_MAX:
+                            rcse.mPlaybackVolumeMax = value;
+                            synchronized (mMainRemote) {
+                                if (rccId == mMainRemote.mRccId) {
+                                    mMainRemote.mVolumeMax = value;
+                                    mVolumePanel.postHasNewRemotePlaybackInfo();
+                                }
+                            }
+                            break;
+                        case RemoteControlClient.PLAYBACKINFO_VOLUME_HANDLING:
+                            rcse.mPlaybackVolumeHandling = value;
+                            synchronized (mMainRemote) {
+                                if (rccId == mMainRemote.mRccId) {
+                                    mMainRemote.mVolumeHandling = value;
+                                    mVolumePanel.postHasNewRemotePlaybackInfo();
+                                }
+                            }
+                            break;
+                        case RemoteControlClient.PLAYBACKINFO_USES_STREAM:
+                            rcse.mPlaybackStream = value;
+                            break;
+                        case RemoteControlClient.PLAYBACKINFO_PLAYSTATE:
+                            rcse.mPlaybackState = value;
+                            synchronized (mMainRemote) {
+                                if (rccId == mMainRemote.mRccId) {
+                                    mMainRemoteIsActive = isPlaystateActive(value);
+                                    postReevaluateRemote();
+                                }
+                            }
+                            break;
+                        default:
+                            Log.e(TAG, "unhandled key " + what + " for RCC " + rccId);
+                            break;
+                    }
+                    return;
+                }
+            }
+        }
+    }
+
+    /**
+     * Checks if a remote client is active on the supplied stream type. Update the remote stream
+     * volume state if found and playing
+     * @param streamType
+     * @return false if no remote playing is currently playing
+     */
+    private boolean checkUpdateRemoteStateIfActive(int streamType) {
+        synchronized(mRCStack) {
+            Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+            while(stackIterator.hasNext()) {
+                RemoteControlStackEntry rcse = stackIterator.next();
+                if ((rcse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE)
+                        && isPlaystateActive(rcse.mPlaybackState)
+                        && (rcse.mPlaybackStream == streamType)) {
+                    if (DEBUG_RC) Log.d(TAG, "remote playback active on stream " + streamType
+                            + ", vol =" + rcse.mPlaybackVolume);
+                    synchronized (mMainRemote) {
+                        mMainRemote.mRccId = rcse.mRccId;
+                        mMainRemote.mVolume = rcse.mPlaybackVolume;
+                        mMainRemote.mVolumeMax = rcse.mPlaybackVolumeMax;
+                        mMainRemote.mVolumeHandling = rcse.mPlaybackVolumeHandling;
+                        mMainRemoteIsActive = true;
+                    }
+                    return true;
+                }
+            }
+        }
+        synchronized (mMainRemote) {
+            mMainRemoteIsActive = false;
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if the given playback state is considered "active", i.e. it describes a state
+     * where playback is happening, or about to
+     * @param playState the playback state to evaluate
+     * @return true if active, false otherwise (inactive or unknown)
+     */
+    private static boolean isPlaystateActive(int playState) {
+        switch (playState) {
+            case RemoteControlClient.PLAYSTATE_PLAYING:
+            case RemoteControlClient.PLAYSTATE_BUFFERING:
+            case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
+            case RemoteControlClient.PLAYSTATE_REWINDING:
+            case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
+            case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private void adjustRemoteVolume(int streamType, int direction, int flags) {
+        int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
+        boolean volFixed = false;
+        synchronized (mMainRemote) {
+            if (!mMainRemoteIsActive) {
+                if (DEBUG_VOL) Log.w(TAG, "adjustRemoteVolume didn't find an active client");
+                return;
+            }
+            rccId = mMainRemote.mRccId;
+            volFixed = (mMainRemote.mVolumeHandling ==
+                    RemoteControlClient.PLAYBACK_VOLUME_FIXED);
+        }
+        // unlike "local" stream volumes, we can't compute the new volume based on the direction,
+        // we can only notify the remote that volume needs to be updated, and we'll get an async'
+        // update through setPlaybackInfoForRcc()
+        if (!volFixed) {
+            sendVolumeUpdateToRemote(rccId, direction);
+        }
+
+        // fire up the UI
+        mVolumePanel.postRemoteVolumeChanged(streamType, flags);
+    }
+
+    private void sendVolumeUpdateToRemote(int rccId, int direction) {
+        if (DEBUG_VOL) { Log.d(TAG, "sendVolumeUpdateToRemote(rccId="+rccId+" , dir="+direction); }
+        if (direction == 0) {
+            // only handling discrete events
+            return;
+        }
+        String packageForRcc = null;
+        synchronized (mRCStack) {
+            Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+            while(stackIterator.hasNext()) {
+                RemoteControlStackEntry rcse = stackIterator.next();
+                //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate?
+                if (rcse.mRccId == rccId) {
+                    packageForRcc = rcse.mReceiverComponent.getPackageName();
+                    break;
+                }
+            }
+        }
+        if (packageForRcc != null) {
+            Intent intent = new Intent(Intent.ACTION_VOLUME_UPDATE);
+            intent.putExtra(Intent.EXTRA_VOLUME_UPDATE_DIRECTION, direction);
+            intent.setPackage(packageForRcc);
+            mContext.sendBroadcast(intent);
+        }
+    }
+
+    public int getRemoteStreamMaxVolume() {
+        synchronized (mMainRemote) {
+            if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) {
+                return 0;
+            }
+            return mMainRemote.mVolumeMax;
+        }
+    }
+
+    public int getRemoteStreamVolume() {
+        synchronized (mMainRemote) {
+            if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) {
+                return 0;
+            }
+            return mMainRemote.mVolume;
+        }
+    }
+
+    public void setRemoteStreamVolume(int vol) {
+        if (DEBUG_VOL) { Log.d(TAG, "setRemoteStreamVolume(vol="+vol+")"); }
+        int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
+        synchronized (mMainRemote) {
+            if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) {
+                return;
+            }
+            rccId = mMainRemote.mRccId;
+        }
+        String packageForRcc = null;
+        synchronized (mRCStack) {
+            Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+            while(stackIterator.hasNext()) {
+                RemoteControlStackEntry rcse = stackIterator.next();
+                if (rcse.mRccId == rccId) {
+                    //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate?
+                    packageForRcc = rcse.mReceiverComponent.getPackageName();
+                    break;
+                }
+            }
+        }
+        if (packageForRcc != null) {
+            Intent intent = new Intent(Intent.ACTION_VOLUME_UPDATE);
+            intent.putExtra(Intent.EXTRA_VOLUME_UPDATE_VALUE, vol);
+            intent.setPackage(packageForRcc);
+            mContext.sendBroadcast(intent);
+        }
+    }
+
+    /**
+     * Call to make AudioService reevaluate whether it's in a mode where remote players should
+     * have their volume controlled. In this implementation this is only to reset whether
+     * VolumePanel should display remote volumes
+     */
+    private void postReevaluateRemote() {
+        sendMsg(mAudioHandler, MSG_REEVALUATE_REMOTE, SENDMSG_QUEUE, 0, 0, null, 0);
+    }
+
+    private void onReevaluateRemote() {
+        if (DEBUG_VOL) { Log.w(TAG, "onReevaluateRemote()"); }
+        // is there a registered RemoteControlClient that is handling remote playback
+        boolean hasRemotePlayback = false;
+        synchronized (mRCStack) {
+            Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+            while(stackIterator.hasNext()) {
+                RemoteControlStackEntry rcse = stackIterator.next();
+                if (rcse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE) {
+                    hasRemotePlayback = true;
+                    break;
+                }
+            }
+        }
+        synchronized (mMainRemote) {
+            if (mHasRemotePlayback != hasRemotePlayback) {
+                mHasRemotePlayback = hasRemotePlayback;
+                mVolumePanel.postRemoteSliderVisibility(hasRemotePlayback);
+            }
+        }
+    }
+
     //==========================================================================================
     // Device orientation
     //==========================================================================================
@@ -4871,9 +5279,9 @@
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
 
-        // TODO probably a lot more to do here than just the audio focus and remote control stacks
         dumpFocusStack(pw);
         dumpRCStack(pw);
+        dumpRCCStack(pw);
         dumpStreamStates(pw);
         pw.println("\nAudio routes:");
         pw.print("  mMainType=0x"); pw.println(Integer.toHexString(mCurAudioRoutes.mMainType));
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index fc5b8f1..83483c6 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -35,6 +35,8 @@
     
     void adjustVolume(int direction, int flags);
 
+    oneway void adjustLocalOrRemoteStreamVolume(int streamType, int direction);
+
     void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags);
 
     void adjustStreamVolume(int streamType, int direction, int flags);
@@ -43,6 +45,8 @@
 
     void setStreamVolume(int streamType, int index, int flags);
 
+    oneway void setRemoteStreamVolume(int index);
+
     void setMasterVolume(int index, int flags);
     
     void setStreamSolo(int streamType, boolean state, IBinder cb);
@@ -119,7 +123,7 @@
     oneway void registerMediaButtonEventReceiverForCalls(in ComponentName c);
     oneway void unregisterMediaButtonEventReceiverForCalls();
 
-    oneway void registerRemoteControlClient(in PendingIntent mediaIntent,
+    int registerRemoteControlClient(in PendingIntent mediaIntent,
            in IRemoteControlClient rcClient, in String callingPackageName);
     oneway void unregisterRemoteControlClient(in PendingIntent mediaIntent,
            in IRemoteControlClient rcClient);
@@ -128,6 +132,10 @@
     oneway void unregisterRemoteControlDisplay(in IRemoteControlDisplay rcd);
     oneway void remoteControlDisplayUsesBitmapSize(in IRemoteControlDisplay rcd, int w, int h);
 
+    oneway void setPlaybackInfoForRcc(int rccId, int what, int value);
+           int  getRemoteStreamMaxVolume();
+           int  getRemoteStreamVolume();
+
     void startBluetoothSco(IBinder cb);
     void stopBluetoothSco(IBinder cb);
 
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index f1c4d34..5b8035e 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -18,6 +18,7 @@
 
 import android.app.PendingIntent;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -26,9 +27,11 @@
 import android.media.MediaMetadataRetriever;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.util.Log;
 
@@ -131,6 +134,88 @@
     public final static int PLAYSTATE_NONE               = 0;
 
     /**
+     * @hide (to be un-hidden)
+     * The default playback type, "local", indicating the presentation of the media is happening on
+     * the same device (e.g. a phone, a tablet) as where it is controlled from.
+     */
+    public final static int PLAYBACK_TYPE_LOCAL = 0;
+    /**
+     * @hide (to be un-hidden)
+     * A playback type indicating the presentation of the media is happening on
+     * a different device (i.e. the remote device) than where it is controlled from.
+     */
+    public final static int PLAYBACK_TYPE_REMOTE = 1;
+    private final static int PLAYBACK_TYPE_MIN = PLAYBACK_TYPE_LOCAL;
+    private final static int PLAYBACK_TYPE_MAX = PLAYBACK_TYPE_REMOTE;
+    /**
+     * @hide (to be un-hidden)
+     * Playback information indicating the playback volume is fixed, i.e. it cannot be controlled
+     * from this object. An example of fixed playback volume is a remote player, playing over HDMI
+     * where the user prefer to control the volume on the HDMI sink, rather than attenuate at the
+     * source.
+     * @see #PLAYBACKINFO_VOLUME_HANDLING.
+     */
+    public final static int PLAYBACK_VOLUME_FIXED = 0;
+    /**
+     * @hide (to be un-hidden)
+     * Playback information indicating the playback volume is variable and can be controlled from
+     * this object.
+     * @see #PLAYBACKINFO_VOLUME_HANDLING.
+     */
+    public final static int PLAYBACK_VOLUME_VARIABLE = 1;
+    /**
+     * @hide (to be un-hidden)
+     * The playback information value indicating the value of a given information type is invalid.
+     * @see #PLAYBACKINFO_VOLUME_HANDLING.
+     */
+    public final static int PLAYBACKINFO_INVALID_VALUE = Integer.MIN_VALUE;
+
+    //==========================================
+    // Public keys for playback information
+    /**
+     * @hide (to be un-hidden)
+     * Playback information that defines the type of playback associated with this
+     * RemoteControlClient. See {@link #PLAYBACK_TYPE_LOCAL} and {@link #PLAYBACK_TYPE_REMOTE}.
+     */
+    public final static int PLAYBACKINFO_PLAYBACK_TYPE = 1;
+    /**
+     * @hide (to be un-hidden)
+     * Playback information that defines at what volume the playback associated with this
+     * RemoteControlClient is performed. This information is only used when the playback type is not
+     * local (see {@link #PLAYBACKINFO_PLAYBACK_TYPE}).
+     */
+    public final static int PLAYBACKINFO_VOLUME = 2;
+    /**
+     * @hide (to be un-hidden)
+     * Playback information that defines the maximum volume volume value that is supported
+     * by the playback associated with this RemoteControlClient. This information is only used
+     * when the playback type is not local (see {@link #PLAYBACKINFO_PLAYBACK_TYPE}).
+     */
+    public final static int PLAYBACKINFO_VOLUME_MAX = 3;
+    /**
+     * @hide (to be un-hidden)
+     * Playback information that defines how volume is handled for the presentation of the media.
+     * @see #PLAYBACK_VOLUME_FIXED
+     * @see #PLAYBACK_VOLUME_VARIABLE
+     */
+    public final static int PLAYBACKINFO_VOLUME_HANDLING = 4;
+    /**
+     * @hide (to be un-hidden)
+     * Playback information that defines over what stream type the media is presented.
+     */
+    public final static int PLAYBACKINFO_USES_STREAM = 5;
+
+    //==========================================
+    // Private keys for playback information
+    /**
+     * @hide
+     * Used internally to relay playback state (set by the application with
+     * {@link #setPlaybackState(int)}) to AudioService
+     */
+    public final static int PLAYBACKINFO_PLAYSTATE = 255;
+
+
+    /**
      * Flag indicating a RemoteControlClient makes use of the "previous" media key.
      *
      * @see #setTransportControlFlags(int)
@@ -516,6 +601,8 @@
 
                 // send to remote control display if conditions are met
                 sendPlaybackState_syncCacheLock();
+                // update AudioService
+                sendAudioServiceNewPlaybackInfo_syncCacheLock(PLAYBACKINFO_PLAYSTATE, state);
             }
         }
     }
@@ -542,6 +629,122 @@
         }
     }
 
+    /** @hide */
+    public final static int DEFAULT_PLAYBACK_VOLUME_HANDLING = PLAYBACK_VOLUME_VARIABLE;
+    /** @hide */
+    // hard-coded to the same number of steps as AudioService.MAX_STREAM_VOLUME[STREAM_MUSIC]
+    public final static int DEFAULT_PLAYBACK_VOLUME = 15;
+
+    private int mPlaybackType = PLAYBACK_TYPE_LOCAL;
+    private int mPlaybackVolumeMax = DEFAULT_PLAYBACK_VOLUME;
+    private int mPlaybackVolume = DEFAULT_PLAYBACK_VOLUME;
+    private int mPlaybackVolumeHandling = DEFAULT_PLAYBACK_VOLUME_HANDLING;
+    private int mPlaybackStream = AudioManager.STREAM_MUSIC;
+
+    /**
+     * @hide  (to be un-hidden)
+     * Set information describing information related to the playback of media so the system
+     * can implement additional behavior to handle non-local playback usecases.
+     * @param what a key to specify the type of information to set. Valid keys are
+     *        {@link #PLAYBACKINFO_PLAYBACK_TYPE},
+     *        {@link #PLAYBACKINFO_USES_STREAM},
+     *        {@link #PLAYBACKINFO_VOLUME},
+     *        {@link #PLAYBACKINFO_VOLUME_MAX},
+     *        and {@link #PLAYBACKINFO_VOLUME_HANDLING}.
+     * @param value the value for the supplied information to set.
+     */
+    public void setPlaybackInformation(int what, int value) {
+        synchronized(mCacheLock) {
+            switch (what) {
+                case PLAYBACKINFO_PLAYBACK_TYPE:
+                    if ((value >= PLAYBACK_TYPE_MIN) && (value <= PLAYBACK_TYPE_MAX)) {
+                        if (mPlaybackType != value) {
+                            mPlaybackType = value;
+                            sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
+                        }
+                    } else {
+                        Log.w(TAG, "using invalid value for PLAYBACKINFO_PLAYBACK_TYPE");
+                    }
+                    break;
+                case PLAYBACKINFO_VOLUME:
+                    if ((value > -1) && (value <= mPlaybackVolumeMax)) {
+                        if (mPlaybackVolume != value) {
+                            mPlaybackVolume = value;
+                            sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
+                        }
+                    } else {
+                        Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME");
+                    }
+                    break;
+                case PLAYBACKINFO_VOLUME_MAX:
+                    if (value > 0) {
+                        if (mPlaybackVolumeMax != value) {
+                            mPlaybackVolumeMax = value;
+                            sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
+                        }
+                    } else {
+                        Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME_MAX");
+                    }
+                    break;
+                case PLAYBACKINFO_USES_STREAM:
+                    if ((value >= 0) && (value < AudioSystem.getNumStreamTypes())) {
+                        mPlaybackStream = value;
+                    } else {
+                        Log.w(TAG, "using invalid value for PLAYBACKINFO_USES_STREAM");
+                    }
+                    break;
+                case PLAYBACKINFO_VOLUME_HANDLING:
+                    if ((value >= PLAYBACK_VOLUME_FIXED) && (value <= PLAYBACK_VOLUME_VARIABLE)) {
+                        if (mPlaybackVolumeHandling != value) {
+                            mPlaybackVolumeHandling = value;
+                            sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
+                        }
+                    } else {
+                        Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME_HANDLING");
+                    }
+                    break;
+                default:
+                    // not throwing an exception or returning an error if more keys are to be
+                    // supported in the future
+                    Log.w(TAG, "setPlaybackInformation() ignoring unknown key " + what);
+                    break;
+            }
+        }
+    }
+
+    /**
+     * @hide  (to be un-hidden)
+     * Return playback information represented as an integer value.
+     * @param what a key to specify the type of information to retrieve. Valid keys are
+     *        {@link #PLAYBACKINFO_PLAYBACK_TYPE},
+     *        {@link #PLAYBACKINFO_USES_STREAM},
+     *        {@link #PLAYBACKINFO_VOLUME},
+     *        {@link #PLAYBACKINFO_VOLUME_MAX},
+     *        and {@link #PLAYBACKINFO_VOLUME_HANDLING}.
+     * @return the current value for the given information type, or
+     *   {@link #PLAYBACKINFO_INVALID_VALUE} if an error occurred or the request is invalid, or
+     *   the value is unknown.
+     */
+    public int getIntPlaybackInformation(int what) {
+        synchronized(mCacheLock) {
+            switch (what) {
+                case PLAYBACKINFO_PLAYBACK_TYPE:
+                    return mPlaybackType;
+                case PLAYBACKINFO_VOLUME:
+                    return mPlaybackVolume;
+                case PLAYBACKINFO_VOLUME_MAX:
+                    return mPlaybackVolumeMax;
+                case PLAYBACKINFO_USES_STREAM:
+                    return mPlaybackStream;
+                case PLAYBACKINFO_VOLUME_HANDLING:
+                    return mPlaybackVolumeHandling;
+                default:
+                    Log.e(TAG, "getIntPlaybackInformation() unknown key " + what);
+                    return PLAYBACKINFO_INVALID_VALUE;
+            }
+        }
+    }
+
     /**
      * Lock for all cached data
      */
@@ -675,6 +878,27 @@
         }
     };
 
+    /**
+     * @hide
+     * Default value for the unique identifier
+     */
+    public final static int RCSE_ID_UNREGISTERED = -1;
+    /**
+     * Unique identifier of the RemoteControlStackEntry in AudioService with which
+     * this RemoteControlClient is associated.
+     */
+    private int mRcseId = RCSE_ID_UNREGISTERED;
+    /**
+     * @hide
+     * To be only used by AudioManager after it has received the unique id from
+     * IAudioService.registerRemoteControlClient()
+     * @param id the unique identifier of the RemoteControlStackEntry in AudioService with which
+     *              this RemoteControlClient is associated.
+     */
+    public void setRcseId(int id) {
+        mRcseId = id;
+    }
+
     private EventHandler mEventHandler;
     private final static int MSG_REQUEST_PLAYBACK_STATE = 1;
     private final static int MSG_REQUEST_METADATA = 2;
@@ -731,6 +955,9 @@
         }
     }
 
+    //===========================================================
+    // Communication with IRemoteControlDisplay
+
     private void detachFromDisplay_syncCacheLock() {
         mRcDisplay = null;
         mArtworkExpectedWidth = ARTWORK_INVALID_SIZE;
@@ -802,6 +1029,37 @@
         }
     }
 
+    //===========================================================
+    // Communication with AudioService
+
+    private static IAudioService sService;
+
+    private static IAudioService getService()
+    {
+        if (sService != null) {
+            return sService;
+        }
+        IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
+        sService = IAudioService.Stub.asInterface(b);
+        return sService;
+    }
+
+    private void sendAudioServiceNewPlaybackInfo_syncCacheLock(int what, int value) {
+        if (mRcseId == RCSE_ID_UNREGISTERED) {
+            return;
+        }
+        Log.d(TAG, "sending to AudioService key=" + what + ", value=" + value);
+        IAudioService service = getService();
+        try {
+            service.setPlaybackInfoForRcc(mRcseId, what, value);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in sendAudioServiceNewPlaybackInfo_syncCacheLock", e);
+        }
+    }
+
+    //===========================================================
+    // Message handlers
+
     private void onNewInternalClientGen(Integer clientGeneration, int artWidth, int artHeight) {
         synchronized (mCacheLock) {
             // this remote control client is told it is the "focused" one:
@@ -836,6 +1094,9 @@
         }
     }
 
+    //===========================================================
+    // Internal utilities
+
     /**
      * Scale a bitmap to fit the smallest dimension by uniformly scaling the incoming bitmap.
      * If the bitmap fits, then do nothing and return the original.
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewBase.java b/policy/src/com/android/internal/policy/impl/KeyguardViewBase.java
index 5aa764b..29a5573 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardViewBase.java
+++ b/policy/src/com/android/internal/policy/impl/KeyguardViewBase.java
@@ -213,16 +213,13 @@
                                         Context.AUDIO_SERVICE);
                             }
                         }
-                        // Volume buttons should only function for music.
-                        if (mAudioManager.isMusicActive()) {
-                            // TODO: Actually handle MUTE.
-                            mAudioManager.adjustStreamVolume(
-                                        AudioManager.STREAM_MUSIC,
-                                        keyCode == KeyEvent.KEYCODE_VOLUME_UP
-                                                ? AudioManager.ADJUST_RAISE
-                                                : AudioManager.ADJUST_LOWER,
-                                        0);
-                        }
+                        // Volume buttons should only function for music (local or remote).
+                        // TODO: Actually handle MUTE.
+                        mAudioManager.adjustLocalOrRemoteStreamVolume(
+                                AudioManager.STREAM_MUSIC,
+                                keyCode == KeyEvent.KEYCODE_VOLUME_UP
+                                        ? AudioManager.ADJUST_RAISE
+                                        : AudioManager.ADJUST_LOWER);
                         // Don't execute default volume behavior
                         return true;
                     } else {