3434import android .net .Uri ;
3535import android .os .Handler ;
3636import android .os .Message ;
37- import android .os .RemoteException ;
3837import android .os .Vibrator ;
39- import android .provider .Settings ;
40- import android .provider .Settings .System ;
4138import android .util .Log ;
4239import android .view .WindowManager .LayoutParams ;
4340import 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 ) {
0 commit comments