• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.view;
18 
19 import com.android.internal.R;
20 
21 import android.app.AlertDialog;
22 import android.app.Dialog;
23 import android.content.DialogInterface.OnDismissListener;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.content.res.Resources;
30 import android.media.AudioManager;
31 import android.media.AudioService;
32 import android.media.AudioSystem;
33 import android.media.RingtoneManager;
34 import android.media.ToneGenerator;
35 import android.net.Uri;
36 import android.os.Handler;
37 import android.os.Message;
38 import android.os.Vibrator;
39 import android.util.Log;
40 import android.view.WindowManager.LayoutParams;
41 import android.widget.ImageView;
42 import android.widget.SeekBar;
43 import android.widget.SeekBar.OnSeekBarChangeListener;
44 
45 import java.util.HashMap;
46 
47 /**
48  * Handle the volume up and down keys.
49  *
50  * This code really should be moved elsewhere.
51  *
52  * Seriously, it really really should be moved elsewhere.  This is used by
53  * android.media.AudioService, which actually runs in the system process, to
54  * show the volume dialog when the user changes the volume.  What a mess.
55  *
56  * @hide
57  */
58 public class VolumePanel extends Handler implements OnSeekBarChangeListener, View.OnClickListener
59 {
60     private static final String TAG = "VolumePanel";
61     private static boolean LOGD = false;
62 
63     /**
64      * The delay before playing a sound. This small period exists so the user
65      * can press another key (non-volume keys, too) to have it NOT be audible.
66      * <p>
67      * PhoneWindow will implement this part.
68      */
69     public static final int PLAY_SOUND_DELAY = 300;
70 
71     /**
72      * The delay before vibrating. This small period exists so if the user is
73      * moving to silent mode, it will not emit a short vibrate (it normally
74      * would since vibrate is between normal mode and silent mode using hardware
75      * keys).
76      */
77     public static final int VIBRATE_DELAY = 300;
78 
79     private static final int VIBRATE_DURATION = 300;
80     private static final int BEEP_DURATION = 150;
81     private static final int MAX_VOLUME = 100;
82     private static final int FREE_DELAY = 10000;
83     private static final int TIMEOUT_DELAY = 3000;
84 
85     private static final int MSG_VOLUME_CHANGED = 0;
86     private static final int MSG_FREE_RESOURCES = 1;
87     private static final int MSG_PLAY_SOUND = 2;
88     private static final int MSG_STOP_SOUNDS = 3;
89     private static final int MSG_VIBRATE = 4;
90     private static final int MSG_TIMEOUT = 5;
91     private static final int MSG_RINGER_MODE_CHANGED = 6;
92     private static final int MSG_MUTE_CHANGED = 7;
93     private static final int MSG_REMOTE_VOLUME_CHANGED = 8;
94     private static final int MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN = 9;
95     private static final int MSG_SLIDER_VISIBILITY_CHANGED = 10;
96     private static final int MSG_DISPLAY_SAFE_VOLUME_WARNING = 11;
97 
98     // Pseudo stream type for master volume
99     private static final int STREAM_MASTER = -100;
100     // Pseudo stream type for remote volume is defined in AudioService.STREAM_REMOTE_MUSIC
101 
102     protected Context mContext;
103     private AudioManager mAudioManager;
104     protected AudioService mAudioService;
105     private boolean mRingIsSilent;
106     private boolean mShowCombinedVolumes;
107     private boolean mVoiceCapable;
108 
109     // True if we want to play tones on the system stream when the master stream is specified.
110     private final boolean mPlayMasterStreamTones;
111 
112     /** Dialog containing all the sliders */
113     private final Dialog mDialog;
114     /** Dialog's content view */
115     private final View mView;
116 
117     /** The visible portion of the volume overlay */
118     private final ViewGroup mPanel;
119     /** Contains the sliders and their touchable icons */
120     private final ViewGroup mSliderGroup;
121     /** The button that expands the dialog to show all sliders */
122     private final View mMoreButton;
123     /** Dummy divider icon that needs to vanish with the more button */
124     private final View mDivider;
125 
126     /** Currently active stream that shows up at the top of the list of sliders */
127     private int mActiveStreamType = -1;
128     /** All the slider controls mapped by stream type */
129     private HashMap<Integer,StreamControl> mStreamControls;
130 
131     private enum StreamResources {
132         BluetoothSCOStream(AudioManager.STREAM_BLUETOOTH_SCO,
133                 R.string.volume_icon_description_bluetooth,
134                 R.drawable.ic_audio_bt,
135                 R.drawable.ic_audio_bt,
136                 false),
137         RingerStream(AudioManager.STREAM_RING,
138                 R.string.volume_icon_description_ringer,
139                 R.drawable.ic_audio_ring_notif,
140                 R.drawable.ic_audio_ring_notif_mute,
141                 false),
142         VoiceStream(AudioManager.STREAM_VOICE_CALL,
143                 R.string.volume_icon_description_incall,
144                 R.drawable.ic_audio_phone,
145                 R.drawable.ic_audio_phone,
146                 false),
147         AlarmStream(AudioManager.STREAM_ALARM,
148                 R.string.volume_alarm,
149                 R.drawable.ic_audio_alarm,
150                 R.drawable.ic_audio_alarm_mute,
151                 false),
152         MediaStream(AudioManager.STREAM_MUSIC,
153                 R.string.volume_icon_description_media,
154                 R.drawable.ic_audio_vol,
155                 R.drawable.ic_audio_vol_mute,
156                 true),
157         NotificationStream(AudioManager.STREAM_NOTIFICATION,
158                 R.string.volume_icon_description_notification,
159                 R.drawable.ic_audio_notification,
160                 R.drawable.ic_audio_notification_mute,
161                 true),
162         // for now, use media resources for master volume
163         MasterStream(STREAM_MASTER,
164                 R.string.volume_icon_description_media, //FIXME should have its own description
165                 R.drawable.ic_audio_vol,
166                 R.drawable.ic_audio_vol_mute,
167                 false),
168         RemoteStream(AudioService.STREAM_REMOTE_MUSIC,
169                 R.string.volume_icon_description_media, //FIXME should have its own description
170                 R.drawable.ic_media_route_on_holo_dark,
171                 R.drawable.ic_media_route_disabled_holo_dark,
172                 false);// will be dynamically updated
173 
174         int streamType;
175         int descRes;
176         int iconRes;
177         int iconMuteRes;
178         // RING, VOICE_CALL & BLUETOOTH_SCO are hidden unless explicitly requested
179         boolean show;
180 
StreamResources(int streamType, int descRes, int iconRes, int iconMuteRes, boolean show)181         StreamResources(int streamType, int descRes, int iconRes, int iconMuteRes, boolean show) {
182             this.streamType = streamType;
183             this.descRes = descRes;
184             this.iconRes = iconRes;
185             this.iconMuteRes = iconMuteRes;
186             this.show = show;
187         }
188     };
189 
190     // List of stream types and their order
191     private static final StreamResources[] STREAMS = {
192         StreamResources.BluetoothSCOStream,
193         StreamResources.RingerStream,
194         StreamResources.VoiceStream,
195         StreamResources.MediaStream,
196         StreamResources.NotificationStream,
197         StreamResources.AlarmStream,
198         StreamResources.MasterStream,
199         StreamResources.RemoteStream
200     };
201 
202     /** Object that contains data for each slider */
203     private class StreamControl {
204         int streamType;
205         ViewGroup group;
206         ImageView icon;
207         SeekBar seekbarView;
208         int iconRes;
209         int iconMuteRes;
210     }
211 
212     // Synchronize when accessing this
213     private ToneGenerator mToneGenerators[];
214     private Vibrator mVibrator;
215 
216     private static AlertDialog sConfirmSafeVolumeDialog;
217     private static Object sConfirmSafeVolumeLock = new Object();
218 
219     private static class WarningDialogReceiver extends BroadcastReceiver
220             implements DialogInterface.OnDismissListener {
221         private final Context mContext;
222         private final Dialog mDialog;
223         private final VolumePanel mVolumePanel;
224 
WarningDialogReceiver(Context context, Dialog dialog, VolumePanel volumePanel)225         WarningDialogReceiver(Context context, Dialog dialog, VolumePanel volumePanel) {
226             mContext = context;
227             mDialog = dialog;
228             mVolumePanel = volumePanel;
229             IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
230             context.registerReceiver(this, filter);
231         }
232 
233         @Override
onReceive(Context context, Intent intent)234         public void onReceive(Context context, Intent intent) {
235             mDialog.cancel();
236             cleanUp();
237         }
238 
onDismiss(DialogInterface unused)239         public void onDismiss(DialogInterface unused) {
240             mContext.unregisterReceiver(this);
241             cleanUp();
242         }
243 
cleanUp()244         private void cleanUp() {
245             synchronized (sConfirmSafeVolumeLock) {
246                 sConfirmSafeVolumeDialog = null;
247             }
248             mVolumePanel.forceTimeout();
249             mVolumePanel.updateStates();
250         }
251     }
252 
253 
VolumePanel(final Context context, AudioService volumeService)254     public VolumePanel(final Context context, AudioService volumeService) {
255         mContext = context;
256         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
257         mAudioService = volumeService;
258 
259         // For now, only show master volume if master volume is supported
260         boolean useMasterVolume = context.getResources().getBoolean(
261                 com.android.internal.R.bool.config_useMasterVolume);
262         if (useMasterVolume) {
263             for (int i = 0; i < STREAMS.length; i++) {
264                 StreamResources streamRes = STREAMS[i];
265                 streamRes.show = (streamRes.streamType == STREAM_MASTER);
266             }
267         }
268 
269         LayoutInflater inflater = (LayoutInflater) context
270                 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
271         View view = mView = inflater.inflate(R.layout.volume_adjust, null);
272         mView.setOnTouchListener(new View.OnTouchListener() {
273             public boolean onTouch(View v, MotionEvent event) {
274                 resetTimeout();
275                 return false;
276             }
277         });
278         mPanel = (ViewGroup) mView.findViewById(R.id.visible_panel);
279         mSliderGroup = (ViewGroup) mView.findViewById(R.id.slider_group);
280         mMoreButton = (ImageView) mView.findViewById(R.id.expand_button);
281         mDivider = (ImageView) mView.findViewById(R.id.expand_button_divider);
282 
283         mDialog = new Dialog(context, R.style.Theme_Panel_Volume) {
284             public boolean onTouchEvent(MotionEvent event) {
285                 if (isShowing() && event.getAction() == MotionEvent.ACTION_OUTSIDE &&
286                         sConfirmSafeVolumeDialog == null) {
287                     forceTimeout();
288                     return true;
289                 }
290                 return false;
291             }
292         };
293         mDialog.setTitle("Volume control"); // No need to localize
294         mDialog.setContentView(mView);
295         mDialog.setOnDismissListener(new OnDismissListener() {
296             public void onDismiss(DialogInterface dialog) {
297                 mActiveStreamType = -1;
298                 mAudioManager.forceVolumeControlStream(mActiveStreamType);
299             }
300         });
301         // Change some window properties
302         Window window = mDialog.getWindow();
303         window.setGravity(Gravity.TOP);
304         LayoutParams lp = window.getAttributes();
305         lp.token = null;
306         // Offset from the top
307         lp.y = mContext.getResources().getDimensionPixelOffset(
308                 com.android.internal.R.dimen.volume_panel_top);
309         lp.type = LayoutParams.TYPE_VOLUME_OVERLAY;
310         lp.width = LayoutParams.WRAP_CONTENT;
311         lp.height = LayoutParams.WRAP_CONTENT;
312         lp.privateFlags |= LayoutParams.PRIVATE_FLAG_FORCE_SHOW_NAV_BAR;
313         window.setAttributes(lp);
314         window.addFlags(LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_NOT_TOUCH_MODAL
315                 | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
316 
317         mToneGenerators = new ToneGenerator[AudioSystem.getNumStreamTypes()];
318         mVibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
319 
320         mVoiceCapable = context.getResources().getBoolean(R.bool.config_voice_capable);
321         mShowCombinedVolumes = !mVoiceCapable && !useMasterVolume;
322         // If we don't want to show multiple volumes, hide the settings button and divider
323         if (!mShowCombinedVolumes) {
324             mMoreButton.setVisibility(View.GONE);
325             mDivider.setVisibility(View.GONE);
326         } else {
327             mMoreButton.setOnClickListener(this);
328         }
329 
330         boolean masterVolumeOnly = context.getResources().getBoolean(
331                 com.android.internal.R.bool.config_useMasterVolume);
332         boolean masterVolumeKeySounds = mContext.getResources().getBoolean(
333                 com.android.internal.R.bool.config_useVolumeKeySounds);
334 
335         mPlayMasterStreamTones = masterVolumeOnly && masterVolumeKeySounds;
336 
337         listenToRingerMode();
338     }
339 
setLayoutDirection(int layoutDirection)340     public void setLayoutDirection(int layoutDirection) {
341         mPanel.setLayoutDirection(layoutDirection);
342         updateStates();
343     }
344 
listenToRingerMode()345     private void listenToRingerMode() {
346         final IntentFilter filter = new IntentFilter();
347         filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
348         mContext.registerReceiver(new BroadcastReceiver() {
349 
350             public void onReceive(Context context, Intent intent) {
351                 final String action = intent.getAction();
352 
353                 if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(action)) {
354                     removeMessages(MSG_RINGER_MODE_CHANGED);
355                     sendMessage(obtainMessage(MSG_RINGER_MODE_CHANGED));
356                 }
357             }
358         }, filter);
359     }
360 
isMuted(int streamType)361     private boolean isMuted(int streamType) {
362         if (streamType == STREAM_MASTER) {
363             return mAudioManager.isMasterMute();
364         } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
365             return (mAudioService.getRemoteStreamVolume() <= 0);
366         } else {
367             return mAudioManager.isStreamMute(streamType);
368         }
369     }
370 
getStreamMaxVolume(int streamType)371     private int getStreamMaxVolume(int streamType) {
372         if (streamType == STREAM_MASTER) {
373             return mAudioManager.getMasterMaxVolume();
374         } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
375             return mAudioService.getRemoteStreamMaxVolume();
376         } else {
377             return mAudioManager.getStreamMaxVolume(streamType);
378         }
379     }
380 
getStreamVolume(int streamType)381     private int getStreamVolume(int streamType) {
382         if (streamType == STREAM_MASTER) {
383             return mAudioManager.getMasterVolume();
384         } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
385             return mAudioService.getRemoteStreamVolume();
386         } else {
387             return mAudioManager.getStreamVolume(streamType);
388         }
389     }
390 
setStreamVolume(int streamType, int index, int flags)391     private void setStreamVolume(int streamType, int index, int flags) {
392         if (streamType == STREAM_MASTER) {
393             mAudioManager.setMasterVolume(index, flags);
394         } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
395             mAudioService.setRemoteStreamVolume(index);
396         } else {
397             mAudioManager.setStreamVolume(streamType, index, flags);
398         }
399     }
400 
createSliders()401     private void createSliders() {
402         LayoutInflater inflater = (LayoutInflater) mContext
403                 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
404         mStreamControls = new HashMap<Integer, StreamControl>(STREAMS.length);
405         Resources res = mContext.getResources();
406         for (int i = 0; i < STREAMS.length; i++) {
407             StreamResources streamRes = STREAMS[i];
408             int streamType = streamRes.streamType;
409             if (mVoiceCapable && streamRes == StreamResources.NotificationStream) {
410                 streamRes = StreamResources.RingerStream;
411             }
412             StreamControl sc = new StreamControl();
413             sc.streamType = streamType;
414             sc.group = (ViewGroup) inflater.inflate(R.layout.volume_adjust_item, null);
415             sc.group.setTag(sc);
416             sc.icon = (ImageView) sc.group.findViewById(R.id.stream_icon);
417             sc.icon.setTag(sc);
418             sc.icon.setContentDescription(res.getString(streamRes.descRes));
419             sc.iconRes = streamRes.iconRes;
420             sc.iconMuteRes = streamRes.iconMuteRes;
421             sc.icon.setImageResource(sc.iconRes);
422             sc.seekbarView = (SeekBar) sc.group.findViewById(R.id.seekbar);
423             int plusOne = (streamType == AudioSystem.STREAM_BLUETOOTH_SCO ||
424                     streamType == AudioSystem.STREAM_VOICE_CALL) ? 1 : 0;
425             sc.seekbarView.setMax(getStreamMaxVolume(streamType) + plusOne);
426             sc.seekbarView.setOnSeekBarChangeListener(this);
427             sc.seekbarView.setTag(sc);
428             mStreamControls.put(streamType, sc);
429         }
430     }
431 
reorderSliders(int activeStreamType)432     private void reorderSliders(int activeStreamType) {
433         mSliderGroup.removeAllViews();
434 
435         StreamControl active = mStreamControls.get(activeStreamType);
436         if (active == null) {
437             Log.e("VolumePanel", "Missing stream type! - " + activeStreamType);
438             mActiveStreamType = -1;
439         } else {
440             mSliderGroup.addView(active.group);
441             mActiveStreamType = activeStreamType;
442             active.group.setVisibility(View.VISIBLE);
443             updateSlider(active);
444         }
445 
446         addOtherVolumes();
447     }
448 
addOtherVolumes()449     private void addOtherVolumes() {
450         if (!mShowCombinedVolumes) return;
451 
452         for (int i = 0; i < STREAMS.length; i++) {
453             // Skip the phone specific ones and the active one
454             final int streamType = STREAMS[i].streamType;
455             if (!STREAMS[i].show || streamType == mActiveStreamType) {
456                 continue;
457             }
458             StreamControl sc = mStreamControls.get(streamType);
459             mSliderGroup.addView(sc.group);
460             updateSlider(sc);
461         }
462     }
463 
464     /** Update the mute and progress state of a slider */
updateSlider(StreamControl sc)465     private void updateSlider(StreamControl sc) {
466         sc.seekbarView.setProgress(getStreamVolume(sc.streamType));
467         final boolean muted = isMuted(sc.streamType);
468         // Force reloading the image resource
469         sc.icon.setImageDrawable(null);
470         sc.icon.setImageResource(muted ? sc.iconMuteRes : sc.iconRes);
471         if (((sc.streamType == AudioManager.STREAM_RING) ||
472                 (sc.streamType == AudioManager.STREAM_NOTIFICATION)) &&
473                 mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) {
474             sc.icon.setImageResource(R.drawable.ic_audio_ring_notif_vibrate);
475         }
476         if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) {
477             // never disable touch interactions for remote playback, the muting is not tied to
478             // the state of the phone.
479             sc.seekbarView.setEnabled(true);
480         } else if ((sc.streamType != mAudioManager.getMasterStreamType() && muted) ||
481                         (sConfirmSafeVolumeDialog != null)) {
482             sc.seekbarView.setEnabled(false);
483         } else {
484             sc.seekbarView.setEnabled(true);
485         }
486     }
487 
isExpanded()488     private boolean isExpanded() {
489         return mMoreButton.getVisibility() != View.VISIBLE;
490     }
491 
expand()492     private void expand() {
493         final int count = mSliderGroup.getChildCount();
494         for (int i = 0; i < count; i++) {
495             mSliderGroup.getChildAt(i).setVisibility(View.VISIBLE);
496         }
497         mMoreButton.setVisibility(View.INVISIBLE);
498         mDivider.setVisibility(View.INVISIBLE);
499     }
500 
collapse()501     private void collapse() {
502         mMoreButton.setVisibility(View.VISIBLE);
503         mDivider.setVisibility(View.VISIBLE);
504         final int count = mSliderGroup.getChildCount();
505         for (int i = 1; i < count; i++) {
506             mSliderGroup.getChildAt(i).setVisibility(View.GONE);
507         }
508     }
509 
updateStates()510     public void updateStates() {
511         final int count = mSliderGroup.getChildCount();
512         for (int i = 0; i < count; i++) {
513             StreamControl sc = (StreamControl) mSliderGroup.getChildAt(i).getTag();
514             updateSlider(sc);
515         }
516     }
517 
postVolumeChanged(int streamType, int flags)518     public void postVolumeChanged(int streamType, int flags) {
519         if (hasMessages(MSG_VOLUME_CHANGED)) return;
520         synchronized (this) {
521             if (mStreamControls == null) {
522                 createSliders();
523             }
524         }
525         removeMessages(MSG_FREE_RESOURCES);
526         obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget();
527     }
528 
postRemoteVolumeChanged(int streamType, int flags)529     public void postRemoteVolumeChanged(int streamType, int flags) {
530         if (hasMessages(MSG_REMOTE_VOLUME_CHANGED)) return;
531         synchronized (this) {
532             if (mStreamControls == null) {
533                 createSliders();
534             }
535         }
536         removeMessages(MSG_FREE_RESOURCES);
537         obtainMessage(MSG_REMOTE_VOLUME_CHANGED, streamType, flags).sendToTarget();
538     }
539 
postRemoteSliderVisibility(boolean visible)540     public void postRemoteSliderVisibility(boolean visible) {
541         obtainMessage(MSG_SLIDER_VISIBILITY_CHANGED,
542                 AudioService.STREAM_REMOTE_MUSIC, visible ? 1 : 0).sendToTarget();
543     }
544 
545     /**
546      * Called by AudioService when it has received new remote playback information that
547      * would affect the VolumePanel display (mainly volumes). The difference with
548      * {@link #postRemoteVolumeChanged(int, int)} is that the handling of the posted message
549      * (MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN) will only update the volume slider if it is being
550      * displayed.
551      * This special code path is due to the fact that remote volume updates arrive to AudioService
552      * asynchronously. So after AudioService has sent the volume update (which should be treated
553      * as a request to update the volume), the application will likely set a new volume. If the UI
554      * is still up, we need to refresh the display to show this new value.
555      */
postHasNewRemotePlaybackInfo()556     public void postHasNewRemotePlaybackInfo() {
557         if (hasMessages(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN)) return;
558         // don't create or prevent resources to be freed, if they disappear, this update came too
559         //   late and shouldn't warrant the panel to be displayed longer
560         obtainMessage(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN).sendToTarget();
561     }
562 
postMasterVolumeChanged(int flags)563     public void postMasterVolumeChanged(int flags) {
564         postVolumeChanged(STREAM_MASTER, flags);
565     }
566 
postMuteChanged(int streamType, int flags)567     public void postMuteChanged(int streamType, int flags) {
568         if (hasMessages(MSG_VOLUME_CHANGED)) return;
569         synchronized (this) {
570             if (mStreamControls == null) {
571                 createSliders();
572             }
573         }
574         removeMessages(MSG_FREE_RESOURCES);
575         obtainMessage(MSG_MUTE_CHANGED, streamType, flags).sendToTarget();
576     }
577 
postMasterMuteChanged(int flags)578     public void postMasterMuteChanged(int flags) {
579         postMuteChanged(STREAM_MASTER, flags);
580     }
581 
postDisplaySafeVolumeWarning(int flags)582     public void postDisplaySafeVolumeWarning(int flags) {
583         if (hasMessages(MSG_DISPLAY_SAFE_VOLUME_WARNING)) return;
584         obtainMessage(MSG_DISPLAY_SAFE_VOLUME_WARNING, flags, 0).sendToTarget();
585     }
586 
587     /**
588      * Override this if you have other work to do when the volume changes (for
589      * example, vibrating, playing a sound, etc.). Make sure to call through to
590      * the superclass implementation.
591      */
onVolumeChanged(int streamType, int flags)592     protected void onVolumeChanged(int streamType, int flags) {
593 
594         if (LOGD) Log.d(TAG, "onVolumeChanged(streamType: " + streamType + ", flags: " + flags + ")");
595 
596         if ((flags & AudioManager.FLAG_SHOW_UI) != 0) {
597             synchronized (this) {
598                 if (mActiveStreamType != streamType) {
599                     reorderSliders(streamType);
600                 }
601                 onShowVolumeChanged(streamType, flags);
602             }
603         }
604 
605         if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) {
606             removeMessages(MSG_PLAY_SOUND);
607             sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY);
608         }
609 
610         if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) {
611             removeMessages(MSG_PLAY_SOUND);
612             removeMessages(MSG_VIBRATE);
613             onStopSounds();
614         }
615 
616         removeMessages(MSG_FREE_RESOURCES);
617         sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY);
618         resetTimeout();
619     }
620 
onMuteChanged(int streamType, int flags)621     protected void onMuteChanged(int streamType, int flags) {
622 
623         if (LOGD) Log.d(TAG, "onMuteChanged(streamType: " + streamType + ", flags: " + flags + ")");
624 
625         StreamControl sc = mStreamControls.get(streamType);
626         if (sc != null) {
627             sc.icon.setImageResource(isMuted(sc.streamType) ? sc.iconMuteRes : sc.iconRes);
628         }
629 
630         onVolumeChanged(streamType, flags);
631     }
632 
onShowVolumeChanged(int streamType, int flags)633     protected void onShowVolumeChanged(int streamType, int flags) {
634         int index = getStreamVolume(streamType);
635 
636         mRingIsSilent = false;
637 
638         if (LOGD) {
639             Log.d(TAG, "onShowVolumeChanged(streamType: " + streamType
640                     + ", flags: " + flags + "), index: " + index);
641         }
642 
643         // get max volume for progress bar
644 
645         int max = getStreamMaxVolume(streamType);
646 
647         switch (streamType) {
648 
649             case AudioManager.STREAM_RING: {
650 //                setRingerIcon();
651                 Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri(
652                         mContext, RingtoneManager.TYPE_RINGTONE);
653                 if (ringuri == null) {
654                     mRingIsSilent = true;
655                 }
656                 break;
657             }
658 
659             case AudioManager.STREAM_MUSIC: {
660                 // Special case for when Bluetooth is active for music
661                 if ((mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC) &
662                         (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP |
663                         AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
664                         AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0) {
665                     setMusicIcon(R.drawable.ic_audio_bt, R.drawable.ic_audio_bt_mute);
666                 } else {
667                     setMusicIcon(R.drawable.ic_audio_vol, R.drawable.ic_audio_vol_mute);
668                 }
669                 break;
670             }
671 
672             case AudioManager.STREAM_VOICE_CALL: {
673                 /*
674                  * For in-call voice call volume, there is no inaudible volume.
675                  * Rescale the UI control so the progress bar doesn't go all
676                  * the way to zero and don't show the mute icon.
677                  */
678                 index++;
679                 max++;
680                 break;
681             }
682 
683             case AudioManager.STREAM_ALARM: {
684                 break;
685             }
686 
687             case AudioManager.STREAM_NOTIFICATION: {
688                 Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri(
689                         mContext, RingtoneManager.TYPE_NOTIFICATION);
690                 if (ringuri == null) {
691                     mRingIsSilent = true;
692                 }
693                 break;
694             }
695 
696             case AudioManager.STREAM_BLUETOOTH_SCO: {
697                 /*
698                  * For in-call voice call volume, there is no inaudible volume.
699                  * Rescale the UI control so the progress bar doesn't go all
700                  * the way to zero and don't show the mute icon.
701                  */
702                 index++;
703                 max++;
704                 break;
705             }
706 
707             case AudioService.STREAM_REMOTE_MUSIC: {
708                 if (LOGD) { Log.d(TAG, "showing remote volume "+index+" over "+ max); }
709                 break;
710             }
711         }
712 
713         StreamControl sc = mStreamControls.get(streamType);
714         if (sc != null) {
715             if (sc.seekbarView.getMax() != max) {
716                 sc.seekbarView.setMax(max);
717             }
718 
719             sc.seekbarView.setProgress(index);
720             if (((flags & AudioManager.FLAG_FIXED_VOLUME) != 0) ||
721                     (streamType != mAudioManager.getMasterStreamType() &&
722                      streamType != AudioService.STREAM_REMOTE_MUSIC &&
723                      isMuted(streamType)) ||
724                      sConfirmSafeVolumeDialog != null) {
725                 sc.seekbarView.setEnabled(false);
726             } else {
727                 sc.seekbarView.setEnabled(true);
728             }
729         }
730 
731         if (!mDialog.isShowing()) {
732             int stream = (streamType == AudioService.STREAM_REMOTE_MUSIC) ? -1 : streamType;
733             // when the stream is for remote playback, use -1 to reset the stream type evaluation
734             mAudioManager.forceVolumeControlStream(stream);
735             mDialog.setContentView(mView);
736             // Showing dialog - use collapsed state
737             if (mShowCombinedVolumes) {
738                 collapse();
739             }
740             mDialog.show();
741         }
742 
743         // Do a little vibrate if applicable (only when going into vibrate mode)
744         if ((streamType != AudioService.STREAM_REMOTE_MUSIC) &&
745                 ((flags & AudioManager.FLAG_VIBRATE) != 0) &&
746                 mAudioService.isStreamAffectedByRingerMode(streamType) &&
747                 mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) {
748             sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY);
749         }
750     }
751 
onPlaySound(int streamType, int flags)752     protected void onPlaySound(int streamType, int flags) {
753 
754         if (hasMessages(MSG_STOP_SOUNDS)) {
755             removeMessages(MSG_STOP_SOUNDS);
756             // Force stop right now
757             onStopSounds();
758         }
759 
760         synchronized (this) {
761             ToneGenerator toneGen = getOrCreateToneGenerator(streamType);
762             if (toneGen != null) {
763                 toneGen.startTone(ToneGenerator.TONE_PROP_BEEP);
764                 sendMessageDelayed(obtainMessage(MSG_STOP_SOUNDS), BEEP_DURATION);
765             }
766         }
767     }
768 
onStopSounds()769     protected void onStopSounds() {
770 
771         synchronized (this) {
772             int numStreamTypes = AudioSystem.getNumStreamTypes();
773             for (int i = numStreamTypes - 1; i >= 0; i--) {
774                 ToneGenerator toneGen = mToneGenerators[i];
775                 if (toneGen != null) {
776                     toneGen.stopTone();
777                 }
778             }
779         }
780     }
781 
onVibrate()782     protected void onVibrate() {
783 
784         // Make sure we ended up in vibrate ringer mode
785         if (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_VIBRATE) {
786             return;
787         }
788 
789         mVibrator.vibrate(VIBRATE_DURATION);
790     }
791 
onRemoteVolumeChanged(int streamType, int flags)792     protected void onRemoteVolumeChanged(int streamType, int flags) {
793         // streamType is the real stream type being affected, but for the UI sliders, we
794         // refer to AudioService.STREAM_REMOTE_MUSIC. We still play the beeps on the real
795         // stream type.
796         if (LOGD) Log.d(TAG, "onRemoteVolumeChanged(stream:"+streamType+", flags: " + flags + ")");
797 
798         if (((flags & AudioManager.FLAG_SHOW_UI) != 0) || mDialog.isShowing()) {
799             synchronized (this) {
800                 if (mActiveStreamType != AudioService.STREAM_REMOTE_MUSIC) {
801                     reorderSliders(AudioService.STREAM_REMOTE_MUSIC);
802                 }
803                 onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, flags);
804             }
805         } else {
806             if (LOGD) Log.d(TAG, "not calling onShowVolumeChanged(), no FLAG_SHOW_UI or no UI");
807         }
808 
809         if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) {
810             removeMessages(MSG_PLAY_SOUND);
811             sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY);
812         }
813 
814         if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) {
815             removeMessages(MSG_PLAY_SOUND);
816             removeMessages(MSG_VIBRATE);
817             onStopSounds();
818         }
819 
820         removeMessages(MSG_FREE_RESOURCES);
821         sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY);
822         resetTimeout();
823     }
824 
onRemoteVolumeUpdateIfShown()825     protected void onRemoteVolumeUpdateIfShown() {
826         if (LOGD) Log.d(TAG, "onRemoteVolumeUpdateIfShown()");
827         if (mDialog.isShowing()
828                 && (mActiveStreamType == AudioService.STREAM_REMOTE_MUSIC)
829                 && (mStreamControls != null)) {
830             onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, 0);
831         }
832     }
833 
834 
835     /**
836      * Handler for MSG_SLIDER_VISIBILITY_CHANGED
837      * Hide or show a slider
838      * @param streamType can be a valid stream type value, or VolumePanel.STREAM_MASTER,
839      *                   or AudioService.STREAM_REMOTE_MUSIC
840      * @param visible
841      */
onSliderVisibilityChanged(int streamType, int visible)842     synchronized protected void onSliderVisibilityChanged(int streamType, int visible) {
843         if (LOGD) Log.d(TAG, "onSliderVisibilityChanged(stream="+streamType+", visi="+visible+")");
844         boolean isVisible = (visible == 1);
845         for (int i = STREAMS.length - 1 ; i >= 0 ; i--) {
846             StreamResources streamRes = STREAMS[i];
847             if (streamRes.streamType == streamType) {
848                 streamRes.show = isVisible;
849                 if (!isVisible && (mActiveStreamType == streamType)) {
850                     mActiveStreamType = -1;
851                 }
852                 break;
853             }
854         }
855     }
856 
onDisplaySafeVolumeWarning(int flags)857     protected void onDisplaySafeVolumeWarning(int flags) {
858         if ((flags & AudioManager.FLAG_SHOW_UI) != 0 || mDialog.isShowing()) {
859             synchronized (sConfirmSafeVolumeLock) {
860                 if (sConfirmSafeVolumeDialog != null) {
861                     return;
862                 }
863                 sConfirmSafeVolumeDialog = new AlertDialog.Builder(mContext)
864                         .setMessage(com.android.internal.R.string.safe_media_volume_warning)
865                         .setPositiveButton(com.android.internal.R.string.yes,
866                                             new DialogInterface.OnClickListener() {
867                             public void onClick(DialogInterface dialog, int which) {
868                                 mAudioService.disableSafeMediaVolume();
869                             }
870                         })
871                         .setNegativeButton(com.android.internal.R.string.no, null)
872                         .setIconAttribute(android.R.attr.alertDialogIcon)
873                         .create();
874                 final WarningDialogReceiver warning = new WarningDialogReceiver(mContext,
875                         sConfirmSafeVolumeDialog, this);
876 
877                 sConfirmSafeVolumeDialog.setOnDismissListener(warning);
878                 sConfirmSafeVolumeDialog.getWindow().setType(
879                                                 WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
880                 sConfirmSafeVolumeDialog.show();
881             }
882             updateStates();
883         }
884         resetTimeout();
885     }
886 
887     /**
888      * Lock on this VolumePanel instance as long as you use the returned ToneGenerator.
889      */
getOrCreateToneGenerator(int streamType)890     private ToneGenerator getOrCreateToneGenerator(int streamType) {
891         if (streamType == STREAM_MASTER) {
892             // For devices that use the master volume setting only but still want to
893             // play a volume-changed tone, direct the master volume pseudostream to
894             // the system stream's tone generator.
895             if (mPlayMasterStreamTones) {
896                 streamType = AudioManager.STREAM_SYSTEM;
897             } else {
898                 return null;
899             }
900         }
901         synchronized (this) {
902             if (mToneGenerators[streamType] == null) {
903                 try {
904                     mToneGenerators[streamType] = new ToneGenerator(streamType, MAX_VOLUME);
905                 } catch (RuntimeException e) {
906                     if (LOGD) {
907                         Log.d(TAG, "ToneGenerator constructor failed with "
908                                 + "RuntimeException: " + e);
909                     }
910                 }
911             }
912             return mToneGenerators[streamType];
913         }
914     }
915 
916 
917     /**
918      * Switch between icons because Bluetooth music is same as music volume, but with
919      * different icons.
920      */
setMusicIcon(int resId, int resMuteId)921     private void setMusicIcon(int resId, int resMuteId) {
922         StreamControl sc = mStreamControls.get(AudioManager.STREAM_MUSIC);
923         if (sc != null) {
924             sc.iconRes = resId;
925             sc.iconMuteRes = resMuteId;
926             sc.icon.setImageResource(isMuted(sc.streamType) ? sc.iconMuteRes : sc.iconRes);
927         }
928     }
929 
onFreeResources()930     protected void onFreeResources() {
931         synchronized (this) {
932             for (int i = mToneGenerators.length - 1; i >= 0; i--) {
933                 if (mToneGenerators[i] != null) {
934                     mToneGenerators[i].release();
935                 }
936                 mToneGenerators[i] = null;
937             }
938         }
939     }
940 
941     @Override
handleMessage(Message msg)942     public void handleMessage(Message msg) {
943         switch (msg.what) {
944 
945             case MSG_VOLUME_CHANGED: {
946                 onVolumeChanged(msg.arg1, msg.arg2);
947                 break;
948             }
949 
950             case MSG_MUTE_CHANGED: {
951                 onMuteChanged(msg.arg1, msg.arg2);
952                 break;
953             }
954 
955             case MSG_FREE_RESOURCES: {
956                 onFreeResources();
957                 break;
958             }
959 
960             case MSG_STOP_SOUNDS: {
961                 onStopSounds();
962                 break;
963             }
964 
965             case MSG_PLAY_SOUND: {
966                 onPlaySound(msg.arg1, msg.arg2);
967                 break;
968             }
969 
970             case MSG_VIBRATE: {
971                 onVibrate();
972                 break;
973             }
974 
975             case MSG_TIMEOUT: {
976                 if (mDialog.isShowing()) {
977                     mDialog.dismiss();
978                     mActiveStreamType = -1;
979                 }
980                 synchronized (sConfirmSafeVolumeLock) {
981                     if (sConfirmSafeVolumeDialog != null) {
982                         sConfirmSafeVolumeDialog.dismiss();
983                     }
984                 }
985                 break;
986             }
987             case MSG_RINGER_MODE_CHANGED: {
988                 if (mDialog.isShowing()) {
989                     updateStates();
990                 }
991                 break;
992             }
993 
994             case MSG_REMOTE_VOLUME_CHANGED: {
995                 onRemoteVolumeChanged(msg.arg1, msg.arg2);
996                 break;
997             }
998 
999             case MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN:
1000                 onRemoteVolumeUpdateIfShown();
1001                 break;
1002 
1003             case MSG_SLIDER_VISIBILITY_CHANGED:
1004                 onSliderVisibilityChanged(msg.arg1, msg.arg2);
1005                 break;
1006 
1007             case MSG_DISPLAY_SAFE_VOLUME_WARNING:
1008                 onDisplaySafeVolumeWarning(msg.arg1);
1009                 break;
1010         }
1011     }
1012 
resetTimeout()1013     private void resetTimeout() {
1014         removeMessages(MSG_TIMEOUT);
1015         sendMessageDelayed(obtainMessage(MSG_TIMEOUT), TIMEOUT_DELAY);
1016     }
1017 
forceTimeout()1018     private void forceTimeout() {
1019         removeMessages(MSG_TIMEOUT);
1020         sendMessage(obtainMessage(MSG_TIMEOUT));
1021     }
1022 
onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)1023     public void onProgressChanged(SeekBar seekBar, int progress,
1024             boolean fromUser) {
1025         final Object tag = seekBar.getTag();
1026         if (fromUser && tag instanceof StreamControl) {
1027             StreamControl sc = (StreamControl) tag;
1028             if (getStreamVolume(sc.streamType) != progress) {
1029                 setStreamVolume(sc.streamType, progress, 0);
1030             }
1031         }
1032         resetTimeout();
1033     }
1034 
onStartTrackingTouch(SeekBar seekBar)1035     public void onStartTrackingTouch(SeekBar seekBar) {
1036     }
1037 
onStopTrackingTouch(SeekBar seekBar)1038     public void onStopTrackingTouch(SeekBar seekBar) {
1039         final Object tag = seekBar.getTag();
1040         if (tag instanceof StreamControl) {
1041             StreamControl sc = (StreamControl) tag;
1042             // because remote volume updates are asynchronous, AudioService might have received
1043             // a new remote volume value since the finger adjusted the slider. So when the
1044             // progress of the slider isn't being tracked anymore, adjust the slider to the last
1045             // "published" remote volume value, so the UI reflects the actual volume.
1046             if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) {
1047                 seekBar.setProgress(getStreamVolume(AudioService.STREAM_REMOTE_MUSIC));
1048             }
1049         }
1050     }
1051 
onClick(View v)1052     public void onClick(View v) {
1053         if (v == mMoreButton) {
1054             expand();
1055         }
1056         resetTimeout();
1057     }
1058 }
1059