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