Merge "Remote volume handling" into jb-dev
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index edd509b..50972e8 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;
@@ -2152,6 +2153,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>
@@ -2839,6 +2853,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 5ffc2c3..cf9bcdd 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;
@@ -155,10 +156,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;
@@ -184,7 +190,8 @@
StreamResources.MediaStream,
StreamResources.NotificationStream,
StreamResources.AlarmStream,
- StreamResources.MasterStream
+ StreamResources.MasterStream,
+ StreamResources.RemoteStream
};
/** Object that contains data for each slider */
@@ -297,6 +304,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);
}
@@ -305,6 +314,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);
}
@@ -313,6 +324,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);
}
@@ -321,6 +334,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);
}
@@ -398,7 +413,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);
@@ -446,6 +465,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);
}
@@ -585,6 +638,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);
@@ -593,7 +651,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);
@@ -601,7 +660,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) {
@@ -611,7 +672,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);
@@ -658,6 +720,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.
*/
@@ -750,6 +878,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;
}
}
@@ -779,6 +920,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 244aec8..946bfe6 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1024,6 +1024,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 e88c535..b6e4659 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -98,7 +98,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
@@ -1498,6 +1501,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
@@ -2074,10 +2095,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 {