Skip to content

Commit 2f44230

Browse files
jmtriviAndroid (Google) Code Review
authored andcommitted
Merge "Remote volume handling" into jb-dev
2 parents 5bb835a + 3114ce3 commit 2f44230

File tree

8 files changed

+939
-53
lines changed

8 files changed

+939
-53
lines changed

core/java/android/content/Intent.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import android.content.res.Resources;
2828
import android.content.res.TypedArray;
2929
import android.graphics.Rect;
30+
import android.media.RemoteControlClient;
3031
import android.net.Uri;
3132
import android.os.Bundle;
3233
import android.os.IBinder;
@@ -2151,6 +2152,19 @@ public static Intent createChooser(Intent target, CharSequence title) {
21512152
public static final String ACTION_USB_AUDIO_DEVICE_PLUG =
21522153
"android.intent.action.USB_AUDIO_DEVICE_PLUG";
21532154

2155+
/**
2156+
* @hide (to be un-hidden)
2157+
* Broadcast Action: the volume handled by the receiver should be updated based on the
2158+
* mutually exclusive extras, {@link #EXTRA_VOLUME_UPDATE_DIRECTION}
2159+
* and {@link #EXTRA_VOLUME_UPDATE_VALUE}.
2160+
*
2161+
* @see #EXTRA_VOLUME_UPDATE_DIRECTION
2162+
* @see #EXTRA_VOLUME_UPDATE_VALUE
2163+
* @see android.media.RemoteControlClient
2164+
*/
2165+
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
2166+
public static final String ACTION_VOLUME_UPDATE = "android.intent.action.VOLUME_UPDATE";
2167+
21542168
/**
21552169
* <p>Broadcast Action: The user has switched on advanced settings in the settings app:</p>
21562170
* <ul>
@@ -2839,6 +2853,27 @@ public static Intent createChooser(Intent target, CharSequence title) {
28392853
*/
28402854
public static final String EXTRA_USERID =
28412855
"android.intent.extra.user_id";
2856+
2857+
/**
2858+
* @hide (to be un-hidden)
2859+
* An integer indicating whether the volume is to be increased (positive value) or decreased
2860+
* (negative value). For bundled changes, the absolute value indicates the number of changes
2861+
* in the same direction, e.g. +3 corresponds to three "volume up" changes.
2862+
* @see #ACTION_VOLUME_UPDATE
2863+
*/
2864+
public static final String EXTRA_VOLUME_UPDATE_DIRECTION =
2865+
"android.intent.extra.VOLUME_UPDATE_DIRECTION";
2866+
2867+
/**
2868+
* @hide (to be un-hidden)
2869+
* An integer indicating the new volume value, always between 0 and the value set for
2870+
* {@link RemoteControlClient#PLAYBACKINFO_VOLUME_MAX} with
2871+
* {@link RemoteControlClient#setPlaybackInformation(int, int)}
2872+
* @see #ACTION_VOLUME_UPDATE
2873+
*/
2874+
public static final String EXTRA_VOLUME_UPDATE_VALUE =
2875+
"android.intent.extra.VOLUME_UPDATE_VALUE";
2876+
28422877
// ---------------------------------------------------------------------
28432878
// ---------------------------------------------------------------------
28442879
// Intent flags (see mFlags variable).

core/java/android/view/VolumePanel.java

Lines changed: 162 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,7 @@
3434
import android.net.Uri;
3535
import android.os.Handler;
3636
import android.os.Message;
37-
import android.os.RemoteException;
3837
import android.os.Vibrator;
39-
import android.provider.Settings;
40-
import android.provider.Settings.System;
4138
import android.util.Log;
4239
import android.view.WindowManager.LayoutParams;
4340
import android.widget.ImageView;
@@ -92,9 +89,13 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
9289
private static final int MSG_TIMEOUT = 5;
9390
private static final int MSG_RINGER_MODE_CHANGED = 6;
9491
private static final int MSG_MUTE_CHANGED = 7;
92+
private static final int MSG_REMOTE_VOLUME_CHANGED = 8;
93+
private static final int MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN = 9;
94+
private static final int MSG_SLIDER_VISIBILITY_CHANGED = 10;
9595

9696
// Pseudo stream type for master volume
9797
private static final int STREAM_MASTER = -100;
98+
// Pseudo stream type for remote volume is defined in AudioService.STREAM_REMOTE_MUSIC
9899

99100
protected Context mContext;
100101
private AudioManager mAudioManager;
@@ -155,10 +156,15 @@ private enum StreamResources {
155156
true),
156157
// for now, use media resources for master volume
157158
MasterStream(STREAM_MASTER,
158-
R.string.volume_icon_description_media,
159+
R.string.volume_icon_description_media, //FIXME should have its own description
159160
R.drawable.ic_audio_vol,
160161
R.drawable.ic_audio_vol_mute,
161-
false);
162+
false),
163+
RemoteStream(AudioService.STREAM_REMOTE_MUSIC,
164+
R.string.volume_icon_description_media, //FIXME should have its own description
165+
R.drawable.ic_media_route_on_holo_dark,
166+
R.drawable.ic_media_route_disabled_holo_dark,
167+
false);// will be dynamically updated
162168

163169
int streamType;
164170
int descRes;
@@ -184,7 +190,8 @@ private enum StreamResources {
184190
StreamResources.MediaStream,
185191
StreamResources.NotificationStream,
186192
StreamResources.AlarmStream,
187-
StreamResources.MasterStream
193+
StreamResources.MasterStream,
194+
StreamResources.RemoteStream
188195
};
189196

190197
/** Object that contains data for each slider */
@@ -297,6 +304,8 @@ public void onReceive(Context context, Intent intent) {
297304
private boolean isMuted(int streamType) {
298305
if (streamType == STREAM_MASTER) {
299306
return mAudioManager.isMasterMute();
307+
} else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
308+
return (mAudioService.getRemoteStreamVolume() <= 0);
300309
} else {
301310
return mAudioManager.isStreamMute(streamType);
302311
}
@@ -305,6 +314,8 @@ private boolean isMuted(int streamType) {
305314
private int getStreamMaxVolume(int streamType) {
306315
if (streamType == STREAM_MASTER) {
307316
return mAudioManager.getMasterMaxVolume();
317+
} else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
318+
return mAudioService.getRemoteStreamMaxVolume();
308319
} else {
309320
return mAudioManager.getStreamMaxVolume(streamType);
310321
}
@@ -313,6 +324,8 @@ private int getStreamMaxVolume(int streamType) {
313324
private int getStreamVolume(int streamType) {
314325
if (streamType == STREAM_MASTER) {
315326
return mAudioManager.getMasterVolume();
327+
} else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
328+
return mAudioService.getRemoteStreamVolume();
316329
} else {
317330
return mAudioManager.getStreamVolume(streamType);
318331
}
@@ -321,6 +334,8 @@ private int getStreamVolume(int streamType) {
321334
private void setStreamVolume(int streamType, int index, int flags) {
322335
if (streamType == STREAM_MASTER) {
323336
mAudioManager.setMasterVolume(index, flags);
337+
} else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
338+
mAudioService.setRemoteStreamVolume(index);
324339
} else {
325340
mAudioManager.setStreamVolume(streamType, index, flags);
326341
}
@@ -398,7 +413,11 @@ private void updateSlider(StreamControl sc) {
398413
mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) {
399414
sc.icon.setImageResource(R.drawable.ic_audio_ring_notif_vibrate);
400415
}
401-
if (sc.streamType != mAudioManager.getMasterStreamType() && muted) {
416+
if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) {
417+
// never disable touch interactions for remote playback, the muting is not tied to
418+
// the state of the phone.
419+
sc.seekbarView.setEnabled(true);
420+
} else if (sc.streamType != mAudioManager.getMasterStreamType() && muted) {
402421
sc.seekbarView.setEnabled(false);
403422
} else {
404423
sc.seekbarView.setEnabled(true);
@@ -446,6 +465,40 @@ public void postVolumeChanged(int streamType, int flags) {
446465
obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget();
447466
}
448467

468+
public void postRemoteVolumeChanged(int streamType, int flags) {
469+
if (hasMessages(MSG_REMOTE_VOLUME_CHANGED)) return;
470+
synchronized (this) {
471+
if (mStreamControls == null) {
472+
createSliders();
473+
}
474+
}
475+
removeMessages(MSG_FREE_RESOURCES);
476+
obtainMessage(MSG_REMOTE_VOLUME_CHANGED, streamType, flags).sendToTarget();
477+
}
478+
479+
public void postRemoteSliderVisibility(boolean visible) {
480+
obtainMessage(MSG_SLIDER_VISIBILITY_CHANGED,
481+
AudioService.STREAM_REMOTE_MUSIC, visible ? 1 : 0).sendToTarget();
482+
}
483+
484+
/**
485+
* Called by AudioService when it has received new remote playback information that
486+
* would affect the VolumePanel display (mainly volumes). The difference with
487+
* {@link #postRemoteVolumeChanged(int, int)} is that the handling of the posted message
488+
* (MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN) will only update the volume slider if it is being
489+
* displayed.
490+
* This special code path is due to the fact that remote volume updates arrive to AudioService
491+
* asynchronously. So after AudioService has sent the volume update (which should be treated
492+
* as a request to update the volume), the application will likely set a new volume. If the UI
493+
* is still up, we need to refresh the display to show this new value.
494+
*/
495+
public void postHasNewRemotePlaybackInfo() {
496+
if (hasMessages(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN)) return;
497+
// don't create or prevent resources to be freed, if they disappear, this update came too
498+
// late and shouldn't warrant the panel to be displayed longer
499+
obtainMessage(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN).sendToTarget();
500+
}
501+
449502
public void postMasterVolumeChanged(int flags) {
450503
postVolumeChanged(STREAM_MASTER, flags);
451504
}
@@ -585,6 +638,11 @@ protected void onShowVolumeChanged(int streamType, int flags) {
585638
max++;
586639
break;
587640
}
641+
642+
case AudioService.STREAM_REMOTE_MUSIC: {
643+
if (LOGD) { Log.d(TAG, "showing remote volume "+index+" over "+ max); }
644+
break;
645+
}
588646
}
589647

590648
StreamControl sc = mStreamControls.get(streamType);
@@ -593,15 +651,18 @@ protected void onShowVolumeChanged(int streamType, int flags) {
593651
sc.seekbarView.setMax(max);
594652
}
595653
sc.seekbarView.setProgress(index);
596-
if (streamType != mAudioManager.getMasterStreamType() && isMuted(streamType)) {
654+
if (streamType != mAudioManager.getMasterStreamType()
655+
&& streamType != AudioService.STREAM_REMOTE_MUSIC && isMuted(streamType)) {
597656
sc.seekbarView.setEnabled(false);
598657
} else {
599658
sc.seekbarView.setEnabled(true);
600659
}
601660
}
602661

603662
if (!mDialog.isShowing()) {
604-
mAudioManager.forceVolumeControlStream(streamType);
663+
int stream = (streamType == AudioService.STREAM_REMOTE_MUSIC) ? -1 : streamType;
664+
// when the stream is for remote playback, use -1 to reset the stream type evaluation
665+
mAudioManager.forceVolumeControlStream(stream);
605666
mDialog.setContentView(mView);
606667
// Showing dialog - use collapsed state
607668
if (mShowCombinedVolumes) {
@@ -611,7 +672,8 @@ protected void onShowVolumeChanged(int streamType, int flags) {
611672
}
612673

613674
// Do a little vibrate if applicable (only when going into vibrate mode)
614-
if ((flags & AudioManager.FLAG_VIBRATE) != 0 &&
675+
if ((streamType != AudioService.STREAM_REMOTE_MUSIC) &&
676+
((flags & AudioManager.FLAG_VIBRATE) != 0) &&
615677
mAudioService.isStreamAffectedByRingerMode(streamType) &&
616678
mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) {
617679
sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY);
@@ -658,6 +720,72 @@ protected void onVibrate() {
658720
mVibrator.vibrate(VIBRATE_DURATION);
659721
}
660722

723+
protected void onRemoteVolumeChanged(int streamType, int flags) {
724+
// streamType is the real stream type being affected, but for the UI sliders, we
725+
// refer to AudioService.STREAM_REMOTE_MUSIC. We still play the beeps on the real
726+
// stream type.
727+
if (LOGD) Log.d(TAG, "onRemoteVolumeChanged(stream:"+streamType+", flags: " + flags + ")");
728+
729+
if (((flags & AudioManager.FLAG_SHOW_UI) != 0) || mDialog.isShowing()) {
730+
synchronized (this) {
731+
if (mActiveStreamType != AudioService.STREAM_REMOTE_MUSIC) {
732+
reorderSliders(AudioService.STREAM_REMOTE_MUSIC);
733+
}
734+
onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, flags);
735+
}
736+
} else {
737+
if (LOGD) Log.d(TAG, "not calling onShowVolumeChanged(), no FLAG_SHOW_UI or no UI");
738+
}
739+
740+
if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) {
741+
removeMessages(MSG_PLAY_SOUND);
742+
sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY);
743+
}
744+
745+
if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) {
746+
removeMessages(MSG_PLAY_SOUND);
747+
removeMessages(MSG_VIBRATE);
748+
onStopSounds();
749+
}
750+
751+
removeMessages(MSG_FREE_RESOURCES);
752+
sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY);
753+
754+
resetTimeout();
755+
}
756+
757+
protected void onRemoteVolumeUpdateIfShown() {
758+
if (LOGD) Log.d(TAG, "onRemoteVolumeUpdateIfShown()");
759+
if (mDialog.isShowing()
760+
&& (mActiveStreamType == AudioService.STREAM_REMOTE_MUSIC)
761+
&& (mStreamControls != null)) {
762+
onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, 0);
763+
}
764+
}
765+
766+
767+
/**
768+
* Handler for MSG_SLIDER_VISIBILITY_CHANGED
769+
* Hide or show a slider
770+
* @param streamType can be a valid stream type value, or VolumePanel.STREAM_MASTER,
771+
* or AudioService.STREAM_REMOTE_MUSIC
772+
* @param visible
773+
*/
774+
synchronized protected void onSliderVisibilityChanged(int streamType, int visible) {
775+
if (LOGD) Log.d(TAG, "onSliderVisibilityChanged(stream="+streamType+", visi="+visible+")");
776+
boolean isVisible = (visible == 1);
777+
for (int i = STREAMS.length - 1 ; i >= 0 ; i--) {
778+
StreamResources streamRes = STREAMS[i];
779+
if (streamRes.streamType == streamType) {
780+
streamRes.show = isVisible;
781+
if (!isVisible && (mActiveStreamType == streamType)) {
782+
mActiveStreamType = -1;
783+
}
784+
break;
785+
}
786+
}
787+
}
788+
661789
/**
662790
* Lock on this VolumePanel instance as long as you use the returned ToneGenerator.
663791
*/
@@ -750,6 +878,19 @@ public void handleMessage(Message msg) {
750878
}
751879
break;
752880
}
881+
882+
case MSG_REMOTE_VOLUME_CHANGED: {
883+
onRemoteVolumeChanged(msg.arg1, msg.arg2);
884+
break;
885+
}
886+
887+
case MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN:
888+
onRemoteVolumeUpdateIfShown();
889+
break;
890+
891+
case MSG_SLIDER_VISIBILITY_CHANGED:
892+
onSliderVisibilityChanged(msg.arg1, msg.arg2);
893+
break;
753894
}
754895
}
755896

@@ -779,6 +920,17 @@ public void onStartTrackingTouch(SeekBar seekBar) {
779920
}
780921

781922
public void onStopTrackingTouch(SeekBar seekBar) {
923+
final Object tag = seekBar.getTag();
924+
if (tag instanceof StreamControl) {
925+
StreamControl sc = (StreamControl) tag;
926+
// because remote volume updates are asynchronous, AudioService might have received
927+
// a new remote volume value since the finger adjusted the slider. So when the
928+
// progress of the slider isn't being tracked anymore, adjust the slider to the last
929+
// "published" remote volume value, so the UI reflects the actual volume.
930+
if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) {
931+
seekBar.setProgress(getStreamVolume(AudioService.STREAM_REMOTE_MUSIC));
932+
}
933+
}
782934
}
783935

784936
public void onClick(View v) {

core/res/res/values/public.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,6 +1024,8 @@
10241024
<java-symbol type="drawable" name="notification_template_icon_bg" />
10251025
<java-symbol type="drawable" name="notification_template_icon_low_bg" />
10261026
<java-symbol type="drawable" name="ic_lockscreen_unlock_phantom" />
1027+
<java-symbol type="drawable" name="ic_media_route_on_holo_dark" />
1028+
<java-symbol type="drawable" name="ic_media_route_disabled_holo_dark" />
10271029

10281030
<java-symbol type="layout" name="action_bar_home" />
10291031
<java-symbol type="layout" name="action_bar_title_item" />

0 commit comments

Comments
 (0)