• 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 Context mContext;
222         private Dialog mDialog;
223 
WarningDialogReceiver(Context context, Dialog dialog)224         WarningDialogReceiver(Context context, Dialog dialog) {
225             mContext = context;
226             mDialog = dialog;
227             IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
228             context.registerReceiver(this, filter);
229         }
230 
231         @Override
onReceive(Context context, Intent intent)232         public void onReceive(Context context, Intent intent) {
233             mDialog.cancel();
234             synchronized (sConfirmSafeVolumeLock) {
235                 sConfirmSafeVolumeDialog = null;
236             }
237         }
238 
onDismiss(DialogInterface unused)239         public void onDismiss(DialogInterface unused) {
240             mContext.unregisterReceiver(this);
241             synchronized (sConfirmSafeVolumeLock) {
242                 sConfirmSafeVolumeDialog = null;
243             }
244         }
245     }
246 
247 
VolumePanel(final Context context, AudioService volumeService)248     public VolumePanel(final Context context, AudioService volumeService) {
249         mContext = context;
250         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
251         mAudioService = volumeService;
252 
253         // For now, only show master volume if master volume is supported
254         boolean useMasterVolume = context.getResources().getBoolean(
255                 com.android.internal.R.bool.config_useMasterVolume);
256         if (useMasterVolume) {
257             for (int i = 0; i < STREAMS.length; i++) {
258                 StreamResources streamRes = STREAMS[i];
259                 streamRes.show = (streamRes.streamType == STREAM_MASTER);
260             }
261         }
262 
263         LayoutInflater inflater = (LayoutInflater) context
264                 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
265         View view = mView = inflater.inflate(R.layout.volume_adjust, null);
266         mView.setOnTouchListener(new View.OnTouchListener() {
267             public boolean onTouch(View v, MotionEvent event) {
268                 resetTimeout();
269                 return false;
270             }
271         });
272         mPanel = (ViewGroup) mView.findViewById(R.id.visible_panel);
273         mSliderGroup = (ViewGroup) mView.findViewById(R.id.slider_group);
274         mMoreButton = (ImageView) mView.findViewById(R.id.expand_button);
275         mDivider = (ImageView) mView.findViewById(R.id.expand_button_divider);
276 
277         mDialog = new Dialog(context, R.style.Theme_Panel_Volume) {
278             public boolean onTouchEvent(MotionEvent event) {
279                 if (isShowing() && event.getAction() == MotionEvent.ACTION_OUTSIDE) {
280                     forceTimeout();
281                     return true;
282                 }
283                 return false;
284             }
285         };
286         mDialog.setTitle("Volume control"); // No need to localize
287         mDialog.setContentView(mView);
288         mDialog.setOnDismissListener(new OnDismissListener() {
289             public void onDismiss(DialogInterface dialog) {
290                 mActiveStreamType = -1;
291                 mAudioManager.forceVolumeControlStream(mActiveStreamType);
292             }
293         });
294         // Change some window properties
295         Window window = mDialog.getWindow();
296         window.setGravity(Gravity.TOP);
297         LayoutParams lp = window.getAttributes();
298         lp.token = null;
299         // Offset from the top
300         lp.y = mContext.getResources().getDimensionPixelOffset(
301                 com.android.internal.R.dimen.volume_panel_top);
302         lp.type = LayoutParams.TYPE_VOLUME_OVERLAY;
303         lp.width = LayoutParams.WRAP_CONTENT;
304         lp.height = LayoutParams.WRAP_CONTENT;
305         lp.privateFlags |= LayoutParams.PRIVATE_FLAG_FORCE_SHOW_NAV_BAR;
306         window.setAttributes(lp);
307         window.addFlags(LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_NOT_TOUCH_MODAL
308                 | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
309 
310         mToneGenerators = new ToneGenerator[AudioSystem.getNumStreamTypes()];
311         mVibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
312 
313         mVoiceCapable = context.getResources().getBoolean(R.bool.config_voice_capable);
314         mShowCombinedVolumes = !mVoiceCapable && !useMasterVolume;
315         // If we don't want to show multiple volumes, hide the settings button and divider
316         if (!mShowCombinedVolumes) {
317             mMoreButton.setVisibility(View.GONE);
318             mDivider.setVisibility(View.GONE);
319         } else {
320             mMoreButton.setOnClickListener(this);
321         }
322 
323         boolean masterVolumeOnly = context.getResources().getBoolean(
324                 com.android.internal.R.bool.config_useMasterVolume);
325         boolean masterVolumeKeySounds = mContext.getResources().getBoolean(
326                 com.android.internal.R.bool.config_useVolumeKeySounds);
327 
328         mPlayMasterStreamTones = masterVolumeOnly && masterVolumeKeySounds;
329 
330         listenToRingerMode();
331     }
332 
listenToRingerMode()333     private void listenToRingerMode() {
334         final IntentFilter filter = new IntentFilter();
335         filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
336         mContext.registerReceiver(new BroadcastReceiver() {
337 
338             public void onReceive(Context context, Intent intent) {
339                 final String action = intent.getAction();
340 
341                 if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(action)) {
342                     removeMessages(MSG_RINGER_MODE_CHANGED);
343                     sendMessage(obtainMessage(MSG_RINGER_MODE_CHANGED));
344                 }
345             }
346         }, filter);
347     }
348 
isMuted(int streamType)349     private boolean isMuted(int streamType) {
350         if (streamType == STREAM_MASTER) {
351             return mAudioManager.isMasterMute();
352         } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
353             return (mAudioService.getRemoteStreamVolume() <= 0);
354         } else {
355             return mAudioManager.isStreamMute(streamType);
356         }
357     }
358 
getStreamMaxVolume(int streamType)359     private int getStreamMaxVolume(int streamType) {
360         if (streamType == STREAM_MASTER) {
361             return mAudioManager.getMasterMaxVolume();
362         } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
363             return mAudioService.getRemoteStreamMaxVolume();
364         } else {
365             return mAudioManager.getStreamMaxVolume(streamType);
366         }
367     }
368 
getStreamVolume(int streamType)369     private int getStreamVolume(int streamType) {
370         if (streamType == STREAM_MASTER) {
371             return mAudioManager.getMasterVolume();
372         } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
373             return mAudioService.getRemoteStreamVolume();
374         } else {
375             return mAudioManager.getStreamVolume(streamType);
376         }
377     }
378 
setStreamVolume(int streamType, int index, int flags)379     private void setStreamVolume(int streamType, int index, int flags) {
380         if (streamType == STREAM_MASTER) {
381             mAudioManager.setMasterVolume(index, flags);
382         } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
383             mAudioService.setRemoteStreamVolume(index);
384         } else {
385             mAudioManager.setStreamVolume(streamType, index, flags);
386         }
387     }
388 
createSliders()389     private void createSliders() {
390         LayoutInflater inflater = (LayoutInflater) mContext
391                 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
392         mStreamControls = new HashMap<Integer, StreamControl>(STREAMS.length);
393         Resources res = mContext.getResources();
394         for (int i = 0; i < STREAMS.length; i++) {
395             StreamResources streamRes = STREAMS[i];
396             int streamType = streamRes.streamType;
397             if (mVoiceCapable && streamRes == StreamResources.NotificationStream) {
398                 streamRes = StreamResources.RingerStream;
399             }
400             StreamControl sc = new StreamControl();
401             sc.streamType = streamType;
402             sc.group = (ViewGroup) inflater.inflate(R.layout.volume_adjust_item, null);
403             sc.group.setTag(sc);
404             sc.icon = (ImageView) sc.group.findViewById(R.id.stream_icon);
405             sc.icon.setTag(sc);
406             sc.icon.setContentDescription(res.getString(streamRes.descRes));
407             sc.iconRes = streamRes.iconRes;
408             sc.iconMuteRes = streamRes.iconMuteRes;
409             sc.icon.setImageResource(sc.iconRes);
410             sc.seekbarView = (SeekBar) sc.group.findViewById(R.id.seekbar);
411             int plusOne = (streamType == AudioSystem.STREAM_BLUETOOTH_SCO ||
412                     streamType == AudioSystem.STREAM_VOICE_CALL) ? 1 : 0;
413             sc.seekbarView.setMax(getStreamMaxVolume(streamType) + plusOne);
414             sc.seekbarView.setOnSeekBarChangeListener(this);
415             sc.seekbarView.setTag(sc);
416             mStreamControls.put(streamType, sc);
417         }
418     }
419 
reorderSliders(int activeStreamType)420     private void reorderSliders(int activeStreamType) {
421         mSliderGroup.removeAllViews();
422 
423         StreamControl active = mStreamControls.get(activeStreamType);
424         if (active == null) {
425             Log.e("VolumePanel", "Missing stream type! - " + activeStreamType);
426             mActiveStreamType = -1;
427         } else {
428             mSliderGroup.addView(active.group);
429             mActiveStreamType = activeStreamType;
430             active.group.setVisibility(View.VISIBLE);
431             updateSlider(active);
432         }
433 
434         addOtherVolumes();
435     }
436 
addOtherVolumes()437     private void addOtherVolumes() {
438         if (!mShowCombinedVolumes) return;
439 
440         for (int i = 0; i < STREAMS.length; i++) {
441             // Skip the phone specific ones and the active one
442             final int streamType = STREAMS[i].streamType;
443             if (!STREAMS[i].show || streamType == mActiveStreamType) {
444                 continue;
445             }
446             StreamControl sc = mStreamControls.get(streamType);
447             mSliderGroup.addView(sc.group);
448             updateSlider(sc);
449         }
450     }
451 
452     /** Update the mute and progress state of a slider */
updateSlider(StreamControl sc)453     private void updateSlider(StreamControl sc) {
454         sc.seekbarView.setProgress(getStreamVolume(sc.streamType));
455         final boolean muted = isMuted(sc.streamType);
456         sc.icon.setImageResource(muted ? sc.iconMuteRes : sc.iconRes);
457         if (sc.streamType == AudioManager.STREAM_RING &&
458                 mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) {
459             sc.icon.setImageResource(R.drawable.ic_audio_ring_notif_vibrate);
460         }
461         if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) {
462             // never disable touch interactions for remote playback, the muting is not tied to
463             // the state of the phone.
464             sc.seekbarView.setEnabled(true);
465         } else if (sc.streamType != mAudioManager.getMasterStreamType() && muted) {
466             sc.seekbarView.setEnabled(false);
467         } else {
468             sc.seekbarView.setEnabled(true);
469         }
470     }
471 
isExpanded()472     private boolean isExpanded() {
473         return mMoreButton.getVisibility() != View.VISIBLE;
474     }
475 
expand()476     private void expand() {
477         final int count = mSliderGroup.getChildCount();
478         for (int i = 0; i < count; i++) {
479             mSliderGroup.getChildAt(i).setVisibility(View.VISIBLE);
480         }
481         mMoreButton.setVisibility(View.INVISIBLE);
482         mDivider.setVisibility(View.INVISIBLE);
483     }
484 
collapse()485     private void collapse() {
486         mMoreButton.setVisibility(View.VISIBLE);
487         mDivider.setVisibility(View.VISIBLE);
488         final int count = mSliderGroup.getChildCount();
489         for (int i = 1; i < count; i++) {
490             mSliderGroup.getChildAt(i).setVisibility(View.GONE);
491         }
492     }
493 
updateStates()494     private void updateStates() {
495         final int count = mSliderGroup.getChildCount();
496         for (int i = 0; i < count; i++) {
497             StreamControl sc = (StreamControl) mSliderGroup.getChildAt(i).getTag();
498             updateSlider(sc);
499         }
500     }
501 
postVolumeChanged(int streamType, int flags)502     public void postVolumeChanged(int streamType, int flags) {
503         if (hasMessages(MSG_VOLUME_CHANGED)) return;
504         synchronized (this) {
505             if (mStreamControls == null) {
506                 createSliders();
507             }
508         }
509         removeMessages(MSG_FREE_RESOURCES);
510         obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget();
511     }
512 
postRemoteVolumeChanged(int streamType, int flags)513     public void postRemoteVolumeChanged(int streamType, int flags) {
514         if (hasMessages(MSG_REMOTE_VOLUME_CHANGED)) return;
515         synchronized (this) {
516             if (mStreamControls == null) {
517                 createSliders();
518             }
519         }
520         removeMessages(MSG_FREE_RESOURCES);
521         obtainMessage(MSG_REMOTE_VOLUME_CHANGED, streamType, flags).sendToTarget();
522     }
523 
postRemoteSliderVisibility(boolean visible)524     public void postRemoteSliderVisibility(boolean visible) {
525         obtainMessage(MSG_SLIDER_VISIBILITY_CHANGED,
526                 AudioService.STREAM_REMOTE_MUSIC, visible ? 1 : 0).sendToTarget();
527     }
528 
529     /**
530      * Called by AudioService when it has received new remote playback information that
531      * would affect the VolumePanel display (mainly volumes). The difference with
532      * {@link #postRemoteVolumeChanged(int, int)} is that the handling of the posted message
533      * (MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN) will only update the volume slider if it is being
534      * displayed.
535      * This special code path is due to the fact that remote volume updates arrive to AudioService
536      * asynchronously. So after AudioService has sent the volume update (which should be treated
537      * as a request to update the volume), the application will likely set a new volume. If the UI
538      * is still up, we need to refresh the display to show this new value.
539      */
postHasNewRemotePlaybackInfo()540     public void postHasNewRemotePlaybackInfo() {
541         if (hasMessages(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN)) return;
542         // don't create or prevent resources to be freed, if they disappear, this update came too
543         //   late and shouldn't warrant the panel to be displayed longer
544         obtainMessage(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN).sendToTarget();
545     }
546 
postMasterVolumeChanged(int flags)547     public void postMasterVolumeChanged(int flags) {
548         postVolumeChanged(STREAM_MASTER, flags);
549     }
550 
postMuteChanged(int streamType, int flags)551     public void postMuteChanged(int streamType, int flags) {
552         if (hasMessages(MSG_VOLUME_CHANGED)) return;
553         synchronized (this) {
554             if (mStreamControls == null) {
555                 createSliders();
556             }
557         }
558         removeMessages(MSG_FREE_RESOURCES);
559         obtainMessage(MSG_MUTE_CHANGED, streamType, flags).sendToTarget();
560     }
561 
postMasterMuteChanged(int flags)562     public void postMasterMuteChanged(int flags) {
563         postMuteChanged(STREAM_MASTER, flags);
564     }
565 
postDisplaySafeVolumeWarning()566     public void postDisplaySafeVolumeWarning() {
567         if (hasMessages(MSG_DISPLAY_SAFE_VOLUME_WARNING)) return;
568         obtainMessage(MSG_DISPLAY_SAFE_VOLUME_WARNING, 0, 0).sendToTarget();
569     }
570 
571     /**
572      * Override this if you have other work to do when the volume changes (for
573      * example, vibrating, playing a sound, etc.). Make sure to call through to
574      * the superclass implementation.
575      */
onVolumeChanged(int streamType, int flags)576     protected void onVolumeChanged(int streamType, int flags) {
577 
578         if (LOGD) Log.d(TAG, "onVolumeChanged(streamType: " + streamType + ", flags: " + flags + ")");
579 
580         if ((flags & AudioManager.FLAG_SHOW_UI) != 0) {
581             synchronized (this) {
582                 if (mActiveStreamType != streamType) {
583                     reorderSliders(streamType);
584                 }
585                 onShowVolumeChanged(streamType, flags);
586             }
587         }
588 
589         if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) {
590             removeMessages(MSG_PLAY_SOUND);
591             sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY);
592         }
593 
594         if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) {
595             removeMessages(MSG_PLAY_SOUND);
596             removeMessages(MSG_VIBRATE);
597             onStopSounds();
598         }
599 
600         removeMessages(MSG_FREE_RESOURCES);
601         sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY);
602 
603         resetTimeout();
604     }
605 
onMuteChanged(int streamType, int flags)606     protected void onMuteChanged(int streamType, int flags) {
607 
608         if (LOGD) Log.d(TAG, "onMuteChanged(streamType: " + streamType + ", flags: " + flags + ")");
609 
610         StreamControl sc = mStreamControls.get(streamType);
611         if (sc != null) {
612             sc.icon.setImageResource(isMuted(sc.streamType) ? sc.iconMuteRes : sc.iconRes);
613         }
614 
615         onVolumeChanged(streamType, flags);
616     }
617 
onShowVolumeChanged(int streamType, int flags)618     protected void onShowVolumeChanged(int streamType, int flags) {
619         int index = getStreamVolume(streamType);
620 
621         mRingIsSilent = false;
622 
623         if (LOGD) {
624             Log.d(TAG, "onShowVolumeChanged(streamType: " + streamType
625                     + ", flags: " + flags + "), index: " + index);
626         }
627 
628         // get max volume for progress bar
629 
630         int max = getStreamMaxVolume(streamType);
631 
632         switch (streamType) {
633 
634             case AudioManager.STREAM_RING: {
635 //                setRingerIcon();
636                 Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri(
637                         mContext, RingtoneManager.TYPE_RINGTONE);
638                 if (ringuri == null) {
639                     mRingIsSilent = true;
640                 }
641                 break;
642             }
643 
644             case AudioManager.STREAM_MUSIC: {
645                 // Special case for when Bluetooth is active for music
646                 if ((mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC) &
647                         (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP |
648                         AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
649                         AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0) {
650                     setMusicIcon(R.drawable.ic_audio_bt, R.drawable.ic_audio_bt_mute);
651                 } else {
652                     setMusicIcon(R.drawable.ic_audio_vol, R.drawable.ic_audio_vol_mute);
653                 }
654                 break;
655             }
656 
657             case AudioManager.STREAM_VOICE_CALL: {
658                 /*
659                  * For in-call voice call volume, there is no inaudible volume.
660                  * Rescale the UI control so the progress bar doesn't go all
661                  * the way to zero and don't show the mute icon.
662                  */
663                 index++;
664                 max++;
665                 break;
666             }
667 
668             case AudioManager.STREAM_ALARM: {
669                 break;
670             }
671 
672             case AudioManager.STREAM_NOTIFICATION: {
673                 Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri(
674                         mContext, RingtoneManager.TYPE_NOTIFICATION);
675                 if (ringuri == null) {
676                     mRingIsSilent = true;
677                 }
678                 break;
679             }
680 
681             case AudioManager.STREAM_BLUETOOTH_SCO: {
682                 /*
683                  * For in-call voice call volume, there is no inaudible volume.
684                  * Rescale the UI control so the progress bar doesn't go all
685                  * the way to zero and don't show the mute icon.
686                  */
687                 index++;
688                 max++;
689                 break;
690             }
691 
692             case AudioService.STREAM_REMOTE_MUSIC: {
693                 if (LOGD) { Log.d(TAG, "showing remote volume "+index+" over "+ max); }
694                 break;
695             }
696         }
697 
698         StreamControl sc = mStreamControls.get(streamType);
699         if (sc != null) {
700             if (sc.seekbarView.getMax() != max) {
701                 sc.seekbarView.setMax(max);
702             }
703 
704             sc.seekbarView.setProgress(index);
705             if (((flags & AudioManager.FLAG_FIXED_VOLUME) != 0) ||
706                     (streamType != mAudioManager.getMasterStreamType() &&
707                      streamType != AudioService.STREAM_REMOTE_MUSIC &&
708                      isMuted(streamType))) {
709                 sc.seekbarView.setEnabled(false);
710             } else {
711                 sc.seekbarView.setEnabled(true);
712             }
713         }
714 
715         if (!mDialog.isShowing()) {
716             int stream = (streamType == AudioService.STREAM_REMOTE_MUSIC) ? -1 : streamType;
717             // when the stream is for remote playback, use -1 to reset the stream type evaluation
718             mAudioManager.forceVolumeControlStream(stream);
719             mDialog.setContentView(mView);
720             // Showing dialog - use collapsed state
721             if (mShowCombinedVolumes) {
722                 collapse();
723             }
724             mDialog.show();
725         }
726 
727         // Do a little vibrate if applicable (only when going into vibrate mode)
728         if ((streamType != AudioService.STREAM_REMOTE_MUSIC) &&
729                 ((flags & AudioManager.FLAG_VIBRATE) != 0) &&
730                 mAudioService.isStreamAffectedByRingerMode(streamType) &&
731                 mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) {
732             sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY);
733         }
734     }
735 
onPlaySound(int streamType, int flags)736     protected void onPlaySound(int streamType, int flags) {
737 
738         if (hasMessages(MSG_STOP_SOUNDS)) {
739             removeMessages(MSG_STOP_SOUNDS);
740             // Force stop right now
741             onStopSounds();
742         }
743 
744         synchronized (this) {
745             ToneGenerator toneGen = getOrCreateToneGenerator(streamType);
746             if (toneGen != null) {
747                 toneGen.startTone(ToneGenerator.TONE_PROP_BEEP);
748                 sendMessageDelayed(obtainMessage(MSG_STOP_SOUNDS), BEEP_DURATION);
749             }
750         }
751     }
752 
onStopSounds()753     protected void onStopSounds() {
754 
755         synchronized (this) {
756             int numStreamTypes = AudioSystem.getNumStreamTypes();
757             for (int i = numStreamTypes - 1; i >= 0; i--) {
758                 ToneGenerator toneGen = mToneGenerators[i];
759                 if (toneGen != null) {
760                     toneGen.stopTone();
761                 }
762             }
763         }
764     }
765 
onVibrate()766     protected void onVibrate() {
767 
768         // Make sure we ended up in vibrate ringer mode
769         if (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_VIBRATE) {
770             return;
771         }
772 
773         mVibrator.vibrate(VIBRATE_DURATION);
774     }
775 
onRemoteVolumeChanged(int streamType, int flags)776     protected void onRemoteVolumeChanged(int streamType, int flags) {
777         // streamType is the real stream type being affected, but for the UI sliders, we
778         // refer to AudioService.STREAM_REMOTE_MUSIC. We still play the beeps on the real
779         // stream type.
780         if (LOGD) Log.d(TAG, "onRemoteVolumeChanged(stream:"+streamType+", flags: " + flags + ")");
781 
782         if (((flags & AudioManager.FLAG_SHOW_UI) != 0) || mDialog.isShowing()) {
783             synchronized (this) {
784                 if (mActiveStreamType != AudioService.STREAM_REMOTE_MUSIC) {
785                     reorderSliders(AudioService.STREAM_REMOTE_MUSIC);
786                 }
787                 onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, flags);
788             }
789         } else {
790             if (LOGD) Log.d(TAG, "not calling onShowVolumeChanged(), no FLAG_SHOW_UI or no UI");
791         }
792 
793         if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) {
794             removeMessages(MSG_PLAY_SOUND);
795             sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY);
796         }
797 
798         if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) {
799             removeMessages(MSG_PLAY_SOUND);
800             removeMessages(MSG_VIBRATE);
801             onStopSounds();
802         }
803 
804         removeMessages(MSG_FREE_RESOURCES);
805         sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY);
806 
807         resetTimeout();
808     }
809 
onRemoteVolumeUpdateIfShown()810     protected void onRemoteVolumeUpdateIfShown() {
811         if (LOGD) Log.d(TAG, "onRemoteVolumeUpdateIfShown()");
812         if (mDialog.isShowing()
813                 && (mActiveStreamType == AudioService.STREAM_REMOTE_MUSIC)
814                 && (mStreamControls != null)) {
815             onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, 0);
816         }
817     }
818 
819 
820     /**
821      * Handler for MSG_SLIDER_VISIBILITY_CHANGED
822      * Hide or show a slider
823      * @param streamType can be a valid stream type value, or VolumePanel.STREAM_MASTER,
824      *                   or AudioService.STREAM_REMOTE_MUSIC
825      * @param visible
826      */
onSliderVisibilityChanged(int streamType, int visible)827     synchronized protected void onSliderVisibilityChanged(int streamType, int visible) {
828         if (LOGD) Log.d(TAG, "onSliderVisibilityChanged(stream="+streamType+", visi="+visible+")");
829         boolean isVisible = (visible == 1);
830         for (int i = STREAMS.length - 1 ; i >= 0 ; i--) {
831             StreamResources streamRes = STREAMS[i];
832             if (streamRes.streamType == streamType) {
833                 streamRes.show = isVisible;
834                 if (!isVisible && (mActiveStreamType == streamType)) {
835                     mActiveStreamType = -1;
836                 }
837                 break;
838             }
839         }
840     }
841 
onDisplaySafeVolumeWarning()842     protected void onDisplaySafeVolumeWarning() {
843         synchronized (sConfirmSafeVolumeLock) {
844             if (sConfirmSafeVolumeDialog != null) {
845                 return;
846             }
847             sConfirmSafeVolumeDialog = new AlertDialog.Builder(mContext)
848                     .setMessage(com.android.internal.R.string.safe_media_volume_warning)
849                     .setPositiveButton(com.android.internal.R.string.yes,
850                                         new DialogInterface.OnClickListener() {
851                         public void onClick(DialogInterface dialog, int which) {
852                             mAudioService.disableSafeMediaVolume();
853                         }
854                     })
855                     .setNegativeButton(com.android.internal.R.string.no, null)
856                     .setIconAttribute(android.R.attr.alertDialogIcon)
857                     .create();
858             final WarningDialogReceiver warning = new WarningDialogReceiver(mContext,
859                     sConfirmSafeVolumeDialog);
860 
861             sConfirmSafeVolumeDialog.setOnDismissListener(warning);
862             sConfirmSafeVolumeDialog.getWindow().setType(
863                                                     WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
864             sConfirmSafeVolumeDialog.show();
865         }
866     }
867 
868     /**
869      * Lock on this VolumePanel instance as long as you use the returned ToneGenerator.
870      */
getOrCreateToneGenerator(int streamType)871     private ToneGenerator getOrCreateToneGenerator(int streamType) {
872         if (streamType == STREAM_MASTER) {
873             // For devices that use the master volume setting only but still want to
874             // play a volume-changed tone, direct the master volume pseudostream to
875             // the system stream's tone generator.
876             if (mPlayMasterStreamTones) {
877                 streamType = AudioManager.STREAM_SYSTEM;
878             } else {
879                 return null;
880             }
881         }
882         synchronized (this) {
883             if (mToneGenerators[streamType] == null) {
884                 try {
885                     mToneGenerators[streamType] = new ToneGenerator(streamType, MAX_VOLUME);
886                 } catch (RuntimeException e) {
887                     if (LOGD) {
888                         Log.d(TAG, "ToneGenerator constructor failed with "
889                                 + "RuntimeException: " + e);
890                     }
891                 }
892             }
893             return mToneGenerators[streamType];
894         }
895     }
896 
897 
898     /**
899      * Switch between icons because Bluetooth music is same as music volume, but with
900      * different icons.
901      */
setMusicIcon(int resId, int resMuteId)902     private void setMusicIcon(int resId, int resMuteId) {
903         StreamControl sc = mStreamControls.get(AudioManager.STREAM_MUSIC);
904         if (sc != null) {
905             sc.iconRes = resId;
906             sc.iconMuteRes = resMuteId;
907             sc.icon.setImageResource(isMuted(sc.streamType) ? sc.iconMuteRes : sc.iconRes);
908         }
909     }
910 
onFreeResources()911     protected void onFreeResources() {
912         synchronized (this) {
913             for (int i = mToneGenerators.length - 1; i >= 0; i--) {
914                 if (mToneGenerators[i] != null) {
915                     mToneGenerators[i].release();
916                 }
917                 mToneGenerators[i] = null;
918             }
919         }
920     }
921 
922     @Override
handleMessage(Message msg)923     public void handleMessage(Message msg) {
924         switch (msg.what) {
925 
926             case MSG_VOLUME_CHANGED: {
927                 onVolumeChanged(msg.arg1, msg.arg2);
928                 break;
929             }
930 
931             case MSG_MUTE_CHANGED: {
932                 onMuteChanged(msg.arg1, msg.arg2);
933                 break;
934             }
935 
936             case MSG_FREE_RESOURCES: {
937                 onFreeResources();
938                 break;
939             }
940 
941             case MSG_STOP_SOUNDS: {
942                 onStopSounds();
943                 break;
944             }
945 
946             case MSG_PLAY_SOUND: {
947                 onPlaySound(msg.arg1, msg.arg2);
948                 break;
949             }
950 
951             case MSG_VIBRATE: {
952                 onVibrate();
953                 break;
954             }
955 
956             case MSG_TIMEOUT: {
957                 if (mDialog.isShowing()) {
958                     mDialog.dismiss();
959                     mActiveStreamType = -1;
960                 }
961                 break;
962             }
963             case MSG_RINGER_MODE_CHANGED: {
964                 if (mDialog.isShowing()) {
965                     updateStates();
966                 }
967                 break;
968             }
969 
970             case MSG_REMOTE_VOLUME_CHANGED: {
971                 onRemoteVolumeChanged(msg.arg1, msg.arg2);
972                 break;
973             }
974 
975             case MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN:
976                 onRemoteVolumeUpdateIfShown();
977                 break;
978 
979             case MSG_SLIDER_VISIBILITY_CHANGED:
980                 onSliderVisibilityChanged(msg.arg1, msg.arg2);
981                 break;
982 
983             case MSG_DISPLAY_SAFE_VOLUME_WARNING:
984                 onDisplaySafeVolumeWarning();
985                 break;
986         }
987     }
988 
resetTimeout()989     private void resetTimeout() {
990         removeMessages(MSG_TIMEOUT);
991         sendMessageDelayed(obtainMessage(MSG_TIMEOUT), TIMEOUT_DELAY);
992     }
993 
forceTimeout()994     private void forceTimeout() {
995         removeMessages(MSG_TIMEOUT);
996         sendMessage(obtainMessage(MSG_TIMEOUT));
997     }
998 
onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)999     public void onProgressChanged(SeekBar seekBar, int progress,
1000             boolean fromUser) {
1001         final Object tag = seekBar.getTag();
1002         if (fromUser && tag instanceof StreamControl) {
1003             StreamControl sc = (StreamControl) tag;
1004             if (getStreamVolume(sc.streamType) != progress) {
1005                 setStreamVolume(sc.streamType, progress, 0);
1006             }
1007         }
1008         resetTimeout();
1009     }
1010 
onStartTrackingTouch(SeekBar seekBar)1011     public void onStartTrackingTouch(SeekBar seekBar) {
1012     }
1013 
onStopTrackingTouch(SeekBar seekBar)1014     public void onStopTrackingTouch(SeekBar seekBar) {
1015         final Object tag = seekBar.getTag();
1016         if (tag instanceof StreamControl) {
1017             StreamControl sc = (StreamControl) tag;
1018             // because remote volume updates are asynchronous, AudioService might have received
1019             // a new remote volume value since the finger adjusted the slider. So when the
1020             // progress of the slider isn't being tracked anymore, adjust the slider to the last
1021             // "published" remote volume value, so the UI reflects the actual volume.
1022             if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) {
1023                 seekBar.setProgress(getStreamVolume(AudioService.STREAM_REMOTE_MUSIC));
1024             }
1025         }
1026     }
1027 
onClick(View v)1028     public void onClick(View v) {
1029         if (v == mMoreButton) {
1030             expand();
1031         }
1032         resetTimeout();
1033     }
1034 }
1035