Home | History | Annotate | Download | only in view
      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 
    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 
    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
    234         public void onReceive(Context context, Intent intent) {
    235             mDialog.cancel();
    236             cleanUp();
    237         }
    238 
    239         public void onDismiss(DialogInterface unused) {
    240             mContext.unregisterReceiver(this);
    241             cleanUp();
    242         }
    243 
    244         private void cleanUp() {
    245             synchronized (sConfirmSafeVolumeLock) {
    246                 sConfirmSafeVolumeDialog = null;
    247             }
    248             mVolumePanel.forceTimeout();
    249             mVolumePanel.updateStates();
    250         }
    251     }
    252 
    253 
    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 
    340     public void setLayoutDirection(int layoutDirection) {
    341         mPanel.setLayoutDirection(layoutDirection);
    342         updateStates();
    343     }
    344 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 */
    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 
    488     private boolean isExpanded() {
    489         return mMoreButton.getVisibility() != View.VISIBLE;
    490     }
    491 
    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 
    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 
    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 
    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 
    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 
    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      */
    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 
    563     public void postMasterVolumeChanged(int flags) {
    564         postVolumeChanged(STREAM_MASTER, flags);
    565     }
    566 
    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 
    578     public void postMasterMuteChanged(int flags) {
    579         postMuteChanged(STREAM_MASTER, flags);
    580     }
    581 
    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      */
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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      */
    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 
    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      */
    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      */
    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 
    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
    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 
   1013     private void resetTimeout() {
   1014         removeMessages(MSG_TIMEOUT);
   1015         sendMessageDelayed(obtainMessage(MSG_TIMEOUT), TIMEOUT_DELAY);
   1016     }
   1017 
   1018     private void forceTimeout() {
   1019         removeMessages(MSG_TIMEOUT);
   1020         sendMessage(obtainMessage(MSG_TIMEOUT));
   1021     }
   1022 
   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 
   1035     public void onStartTrackingTouch(SeekBar seekBar) {
   1036     }
   1037 
   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 
   1052     public void onClick(View v) {
   1053         if (v == mMoreButton) {
   1054             expand();
   1055         }
   1056         resetTimeout();
   1057     }
   1058 }
   1059