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