Home | History | Annotate | Download | only in volume
      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 com.android.systemui.volume;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.ValueAnimator;
     22 import android.app.AlertDialog;
     23 import android.app.Dialog;
     24 import android.content.BroadcastReceiver;
     25 import android.content.ComponentName;
     26 import android.content.Context;
     27 import android.content.DialogInterface;
     28 import android.content.DialogInterface.OnDismissListener;
     29 import android.content.Intent;
     30 import android.content.IntentFilter;
     31 import android.content.pm.PackageManager;
     32 import android.content.pm.ServiceInfo;
     33 import android.content.res.Configuration;
     34 import android.content.res.Resources;
     35 import android.content.res.TypedArray;
     36 import android.graphics.PixelFormat;
     37 import android.graphics.drawable.ColorDrawable;
     38 import android.media.AudioAttributes;
     39 import android.media.AudioManager;
     40 import android.media.AudioService;
     41 import android.media.AudioSystem;
     42 import android.media.RingtoneManager;
     43 import android.media.ToneGenerator;
     44 import android.media.VolumeProvider;
     45 import android.media.session.MediaController;
     46 import android.media.session.MediaController.PlaybackInfo;
     47 import android.net.Uri;
     48 import android.os.AsyncTask;
     49 import android.os.Bundle;
     50 import android.os.Debug;
     51 import android.os.Handler;
     52 import android.os.Message;
     53 import android.os.Vibrator;
     54 import android.util.Log;
     55 import android.util.SparseArray;
     56 import android.view.KeyEvent;
     57 import android.view.LayoutInflater;
     58 import android.view.MotionEvent;
     59 import android.view.View;
     60 import android.view.View.OnClickListener;
     61 import android.view.ViewGroup;
     62 import android.view.Window;
     63 import android.view.WindowManager;
     64 import android.view.WindowManager.LayoutParams;
     65 import android.view.accessibility.AccessibilityEvent;
     66 import android.view.accessibility.AccessibilityManager;
     67 import android.view.animation.AnimationUtils;
     68 import android.view.animation.Interpolator;
     69 import android.widget.ImageView;
     70 import android.widget.SeekBar;
     71 import android.widget.SeekBar.OnSeekBarChangeListener;
     72 import android.widget.TextView;
     73 
     74 import com.android.internal.R;
     75 import com.android.systemui.DemoMode;
     76 import com.android.systemui.statusbar.phone.SystemUIDialog;
     77 import com.android.systemui.statusbar.policy.ZenModeController;
     78 
     79 import java.io.FileDescriptor;
     80 import java.io.PrintWriter;
     81 
     82 /**
     83  * Handles the user interface for the volume keys.
     84  *
     85  * @hide
     86  */
     87 public class VolumePanel extends Handler implements DemoMode {
     88     private static final String TAG = "VolumePanel";
     89     private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
     90 
     91     private static final int PLAY_SOUND_DELAY = AudioService.PLAY_SOUND_DELAY;
     92 
     93     /**
     94      * The delay before vibrating. This small period exists so if the user is
     95      * moving to silent mode, it will not emit a short vibrate (it normally
     96      * would since vibrate is between normal mode and silent mode using hardware
     97      * keys).
     98      */
     99     public static final int VIBRATE_DELAY = 300;
    100 
    101     private static final int VIBRATE_DURATION = 300;
    102     private static final int BEEP_DURATION = 150;
    103     private static final int MAX_VOLUME = 100;
    104     private static final int FREE_DELAY = 10000;
    105     private static final int TIMEOUT_DELAY = 3000;
    106     private static final int TIMEOUT_DELAY_SHORT = 1500;
    107     private static final int TIMEOUT_DELAY_COLLAPSED = 4500;
    108     private static final int TIMEOUT_DELAY_SAFETY_WARNING = 5000;
    109     private static final int TIMEOUT_DELAY_EXPANDED = 10000;
    110 
    111     private static final int MSG_VOLUME_CHANGED = 0;
    112     private static final int MSG_FREE_RESOURCES = 1;
    113     private static final int MSG_PLAY_SOUND = 2;
    114     private static final int MSG_STOP_SOUNDS = 3;
    115     private static final int MSG_VIBRATE = 4;
    116     private static final int MSG_TIMEOUT = 5;
    117     private static final int MSG_RINGER_MODE_CHANGED = 6;
    118     private static final int MSG_MUTE_CHANGED = 7;
    119     private static final int MSG_REMOTE_VOLUME_CHANGED = 8;
    120     private static final int MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN = 9;
    121     private static final int MSG_SLIDER_VISIBILITY_CHANGED = 10;
    122     private static final int MSG_DISPLAY_SAFE_VOLUME_WARNING = 11;
    123     private static final int MSG_LAYOUT_DIRECTION = 12;
    124     private static final int MSG_ZEN_MODE_AVAILABLE_CHANGED = 13;
    125     private static final int MSG_USER_ACTIVITY = 14;
    126     private static final int MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED = 15;
    127     private static final int MSG_INTERNAL_RINGER_MODE_CHANGED = 16;
    128 
    129     // Pseudo stream type for master volume
    130     private static final int STREAM_MASTER = -100;
    131     // Pseudo stream type for remote volume
    132     private static final int STREAM_REMOTE_MUSIC = -200;
    133 
    134     private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
    135             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
    136             .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
    137             .build();
    138 
    139     private static final int IC_AUDIO_VOL = com.android.systemui.R.drawable.ic_audio_vol;
    140     private static final int IC_AUDIO_VOL_MUTE = com.android.systemui.R.drawable.ic_audio_vol_mute;
    141     private static final int IC_AUDIO_BT = com.android.systemui.R.drawable.ic_audio_bt;
    142     private static final int IC_AUDIO_BT_MUTE = com.android.systemui.R.drawable.ic_audio_bt_mute;
    143 
    144     private final String mTag;
    145     protected final Context mContext;
    146     private final AudioManager mAudioManager;
    147     private final ZenModeController mZenController;
    148     private boolean mRingIsSilent;
    149     private boolean mVoiceCapable;
    150     private boolean mZenModeAvailable;
    151     private boolean mZenPanelExpanded;
    152     private int mTimeoutDelay = TIMEOUT_DELAY;
    153     private float mDisabledAlpha;
    154     private int mLastRingerMode = AudioManager.RINGER_MODE_NORMAL;
    155     private int mLastRingerProgress = 0;
    156     private int mDemoIcon;
    157 
    158     // True if we want to play tones on the system stream when the master stream is specified.
    159     private final boolean mPlayMasterStreamTones;
    160 
    161 
    162     /** Volume panel content view */
    163     private final View mView;
    164     /** Dialog hosting the panel */
    165     private final Dialog mDialog;
    166 
    167     /** The visible portion of the volume overlay */
    168     private final ViewGroup mPanel;
    169     /** Contains the slider and its touchable icons */
    170     private final ViewGroup mSliderPanel;
    171     /** The zen mode configuration panel view */
    172     private ZenModePanel mZenPanel;
    173     /** The component currently suppressing notification stream effects */
    174     private ComponentName mNotificationEffectsSuppressor;
    175 
    176     private Callback mCallback;
    177 
    178     /** Currently active stream that shows up at the top of the list of sliders */
    179     private int mActiveStreamType = -1;
    180     /** All the slider controls mapped by stream type */
    181     private SparseArray<StreamControl> mStreamControls;
    182     private final AccessibilityManager mAccessibilityManager;
    183     private final SecondaryIconTransition mSecondaryIconTransition;
    184     private final IconPulser mIconPulser;
    185 
    186     private enum StreamResources {
    187         BluetoothSCOStream(AudioManager.STREAM_BLUETOOTH_SCO,
    188                 R.string.volume_icon_description_bluetooth,
    189                 IC_AUDIO_BT,
    190                 IC_AUDIO_BT_MUTE,
    191                 false),
    192         RingerStream(AudioManager.STREAM_RING,
    193                 R.string.volume_icon_description_ringer,
    194                 com.android.systemui.R.drawable.ic_ringer_audible,
    195                 com.android.systemui.R.drawable.ic_ringer_mute,
    196                 false),
    197         VoiceStream(AudioManager.STREAM_VOICE_CALL,
    198                 R.string.volume_icon_description_incall,
    199                 com.android.systemui.R.drawable.ic_audio_phone,
    200                 com.android.systemui.R.drawable.ic_audio_phone,
    201                 false),
    202         AlarmStream(AudioManager.STREAM_ALARM,
    203                 R.string.volume_alarm,
    204                 com.android.systemui.R.drawable.ic_audio_alarm,
    205                 com.android.systemui.R.drawable.ic_audio_alarm_mute,
    206                 false),
    207         MediaStream(AudioManager.STREAM_MUSIC,
    208                 R.string.volume_icon_description_media,
    209                 IC_AUDIO_VOL,
    210                 IC_AUDIO_VOL_MUTE,
    211                 true),
    212         NotificationStream(AudioManager.STREAM_NOTIFICATION,
    213                 R.string.volume_icon_description_notification,
    214                 com.android.systemui.R.drawable.ic_ringer_audible,
    215                 com.android.systemui.R.drawable.ic_ringer_mute,
    216                 true),
    217         // for now, use media resources for master volume
    218         MasterStream(STREAM_MASTER,
    219                 R.string.volume_icon_description_media, //FIXME should have its own description
    220                 IC_AUDIO_VOL,
    221                 IC_AUDIO_VOL_MUTE,
    222                 false),
    223         RemoteStream(STREAM_REMOTE_MUSIC,
    224                 R.string.volume_icon_description_media, //FIXME should have its own description
    225                 com.android.systemui.R.drawable.ic_audio_remote,
    226                 com.android.systemui.R.drawable.ic_audio_remote,
    227                 false);// will be dynamically updated
    228 
    229         int streamType;
    230         int descRes;
    231         int iconRes;
    232         int iconMuteRes;
    233         // RING, VOICE_CALL & BLUETOOTH_SCO are hidden unless explicitly requested
    234         boolean show;
    235 
    236         StreamResources(int streamType, int descRes, int iconRes, int iconMuteRes, boolean show) {
    237             this.streamType = streamType;
    238             this.descRes = descRes;
    239             this.iconRes = iconRes;
    240             this.iconMuteRes = iconMuteRes;
    241             this.show = show;
    242         }
    243     }
    244 
    245     // List of stream types and their order
    246     private static final StreamResources[] STREAMS = {
    247         StreamResources.BluetoothSCOStream,
    248         StreamResources.RingerStream,
    249         StreamResources.VoiceStream,
    250         StreamResources.MediaStream,
    251         StreamResources.NotificationStream,
    252         StreamResources.AlarmStream,
    253         StreamResources.MasterStream,
    254         StreamResources.RemoteStream
    255     };
    256 
    257     /** Object that contains data for each slider */
    258     private class StreamControl {
    259         int streamType;
    260         MediaController controller;
    261         ViewGroup group;
    262         ImageView icon;
    263         SeekBar seekbarView;
    264         TextView suppressorView;
    265         View divider;
    266         ImageView secondaryIcon;
    267         int iconRes;
    268         int iconMuteRes;
    269         int iconSuppressedRes;
    270     }
    271 
    272     // Synchronize when accessing this
    273     private ToneGenerator mToneGenerators[];
    274     private Vibrator mVibrator;
    275     private boolean mHasVibrator;
    276 
    277     private static AlertDialog sSafetyWarning;
    278     private static Object sSafetyWarningLock = new Object();
    279 
    280     private static class SafetyWarning extends SystemUIDialog
    281             implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener {
    282         private final Context mContext;
    283         private final VolumePanel mVolumePanel;
    284         private final AudioManager mAudioManager;
    285 
    286         private boolean mNewVolumeUp;
    287 
    288         SafetyWarning(Context context, VolumePanel volumePanel, AudioManager audioManager) {
    289             super(context);
    290             mContext = context;
    291             mVolumePanel = volumePanel;
    292             mAudioManager = audioManager;
    293 
    294             setMessage(mContext.getString(com.android.internal.R.string.safe_media_volume_warning));
    295             setButton(DialogInterface.BUTTON_POSITIVE,
    296                     mContext.getString(com.android.internal.R.string.yes), this);
    297             setButton(DialogInterface.BUTTON_NEGATIVE,
    298                     mContext.getString(com.android.internal.R.string.no), (OnClickListener) null);
    299             setOnDismissListener(this);
    300 
    301             IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
    302             context.registerReceiver(mReceiver, filter);
    303         }
    304 
    305         @Override
    306         public boolean onKeyDown(int keyCode, KeyEvent event) {
    307             if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && event.getRepeatCount() == 0) {
    308                 mNewVolumeUp = true;
    309             }
    310             return super.onKeyDown(keyCode, event);
    311         }
    312 
    313         @Override
    314         public boolean onKeyUp(int keyCode, KeyEvent event) {
    315             if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && mNewVolumeUp) {
    316                 if (LOGD) Log.d(TAG, "Confirmed warning via VOLUME_UP");
    317                 mAudioManager.disableSafeMediaVolume();
    318                 dismiss();
    319             }
    320             return super.onKeyUp(keyCode, event);
    321         }
    322 
    323         @Override
    324         public void onClick(DialogInterface dialog, int which) {
    325             mAudioManager.disableSafeMediaVolume();
    326         }
    327 
    328         @Override
    329         public void onDismiss(DialogInterface unused) {
    330             mContext.unregisterReceiver(mReceiver);
    331             cleanUp();
    332         }
    333 
    334         private void cleanUp() {
    335             synchronized (sSafetyWarningLock) {
    336                 sSafetyWarning = null;
    337             }
    338             mVolumePanel.forceTimeout(0);
    339             mVolumePanel.updateStates();
    340         }
    341 
    342         private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    343             @Override
    344             public void onReceive(Context context, Intent intent) {
    345                 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
    346                     if (LOGD) Log.d(TAG, "Received ACTION_CLOSE_SYSTEM_DIALOGS");
    347                     cancel();
    348                     cleanUp();
    349                 }
    350             }
    351         };
    352     }
    353 
    354     public VolumePanel(Context context, ZenModeController zenController) {
    355         mTag = String.format("%s.%08x", TAG, hashCode());
    356         mContext = context;
    357         mZenController = zenController;
    358         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    359         mAccessibilityManager = (AccessibilityManager) context.getSystemService(
    360                 Context.ACCESSIBILITY_SERVICE);
    361         mSecondaryIconTransition = new SecondaryIconTransition();
    362         mIconPulser = new IconPulser(context);
    363 
    364         // For now, only show master volume if master volume is supported
    365         final Resources res = context.getResources();
    366         final boolean useMasterVolume = res.getBoolean(R.bool.config_useMasterVolume);
    367         if (useMasterVolume) {
    368             for (int i = 0; i < STREAMS.length; i++) {
    369                 StreamResources streamRes = STREAMS[i];
    370                 streamRes.show = (streamRes.streamType == STREAM_MASTER);
    371             }
    372         }
    373         if (LOGD) Log.d(mTag, "new VolumePanel");
    374 
    375         mDisabledAlpha = 0.5f;
    376         if (mContext.getTheme() != null) {
    377             final TypedArray arr = mContext.getTheme().obtainStyledAttributes(
    378                     new int[] { android.R.attr.disabledAlpha });
    379             mDisabledAlpha = arr.getFloat(0, mDisabledAlpha);
    380             arr.recycle();
    381         }
    382 
    383         mDialog = new Dialog(context) {
    384             @Override
    385             public boolean onTouchEvent(MotionEvent event) {
    386                 if (isShowing() && event.getAction() == MotionEvent.ACTION_OUTSIDE &&
    387                         sSafetyWarning == null) {
    388                     forceTimeout(0);
    389                     return true;
    390                 }
    391                 return false;
    392             }
    393         };
    394 
    395         final Window window = mDialog.getWindow();
    396         window.requestFeature(Window.FEATURE_NO_TITLE);
    397         mDialog.setCanceledOnTouchOutside(true);
    398         mDialog.setContentView(com.android.systemui.R.layout.volume_dialog);
    399         mDialog.setOnDismissListener(new OnDismissListener() {
    400             @Override
    401             public void onDismiss(DialogInterface dialog) {
    402                 mActiveStreamType = -1;
    403                 mAudioManager.forceVolumeControlStream(mActiveStreamType);
    404                 setZenPanelVisible(false);
    405                 mDemoIcon = 0;
    406                 mSecondaryIconTransition.cancel();
    407             }
    408         });
    409 
    410         mDialog.create();
    411 
    412         final LayoutParams lp = window.getAttributes();
    413         lp.token = null;
    414         lp.y = res.getDimensionPixelOffset(com.android.systemui.R.dimen.volume_panel_top);
    415         lp.type = LayoutParams.TYPE_STATUS_BAR_PANEL;
    416         lp.format = PixelFormat.TRANSLUCENT;
    417         lp.windowAnimations = com.android.systemui.R.style.VolumePanelAnimation;
    418         lp.setTitle(TAG);
    419         window.setAttributes(lp);
    420 
    421         updateWidth();
    422 
    423         window.setBackgroundDrawable(new ColorDrawable(0x00000000));
    424         window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
    425         window.addFlags(LayoutParams.FLAG_NOT_FOCUSABLE
    426                 | LayoutParams.FLAG_NOT_TOUCH_MODAL
    427                 | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
    428                 | LayoutParams.FLAG_HARDWARE_ACCELERATED);
    429         mView = window.findViewById(R.id.content);
    430         Interaction.register(mView, new Interaction.Callback() {
    431             @Override
    432             public void onInteraction() {
    433                 resetTimeout();
    434             }
    435         });
    436 
    437         mPanel = (ViewGroup) mView.findViewById(com.android.systemui.R.id.visible_panel);
    438         mSliderPanel = (ViewGroup) mView.findViewById(com.android.systemui.R.id.slider_panel);
    439         mZenPanel = (ZenModePanel) mView.findViewById(com.android.systemui.R.id.zen_mode_panel);
    440         initZenModePanel();
    441 
    442         mToneGenerators = new ToneGenerator[AudioSystem.getNumStreamTypes()];
    443         mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
    444         mHasVibrator = mVibrator != null && mVibrator.hasVibrator();
    445         mVoiceCapable = context.getResources().getBoolean(R.bool.config_voice_capable);
    446 
    447         if (mZenController != null && !useMasterVolume) {
    448             mZenModeAvailable = mZenController.isZenAvailable();
    449             mNotificationEffectsSuppressor = mZenController.getEffectsSuppressor();
    450             mZenController.addCallback(mZenCallback);
    451         }
    452 
    453         final boolean masterVolumeOnly = res.getBoolean(R.bool.config_useMasterVolume);
    454         final boolean masterVolumeKeySounds = res.getBoolean(R.bool.config_useVolumeKeySounds);
    455         mPlayMasterStreamTones = masterVolumeOnly && masterVolumeKeySounds;
    456 
    457         registerReceiver();
    458     }
    459 
    460     public void onConfigurationChanged(Configuration newConfig) {
    461         updateWidth();
    462         if (mZenPanel != null) {
    463             mZenPanel.updateLocale();
    464         }
    465     }
    466 
    467     private void updateWidth() {
    468         final Resources res = mContext.getResources();
    469         final LayoutParams lp = mDialog.getWindow().getAttributes();
    470         lp.width = res.getDimensionPixelSize(com.android.systemui.R.dimen.notification_panel_width);
    471         lp.gravity =
    472                 res.getInteger(com.android.systemui.R.integer.notification_panel_layout_gravity);
    473         mDialog.getWindow().setAttributes(lp);
    474     }
    475 
    476     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    477         pw.println("VolumePanel state:");
    478         pw.print("  mTag="); pw.println(mTag);
    479         pw.print("  mRingIsSilent="); pw.println(mRingIsSilent);
    480         pw.print("  mVoiceCapable="); pw.println(mVoiceCapable);
    481         pw.print("  mHasVibrator="); pw.println(mHasVibrator);
    482         pw.print("  mZenModeAvailable="); pw.println(mZenModeAvailable);
    483         pw.print("  mZenPanelExpanded="); pw.println(mZenPanelExpanded);
    484         pw.print("  mNotificationEffectsSuppressor="); pw.println(mNotificationEffectsSuppressor);
    485         pw.print("  mTimeoutDelay="); pw.println(mTimeoutDelay);
    486         pw.print("  mDisabledAlpha="); pw.println(mDisabledAlpha);
    487         pw.print("  mLastRingerMode="); pw.println(mLastRingerMode);
    488         pw.print("  mLastRingerProgress="); pw.println(mLastRingerProgress);
    489         pw.print("  mPlayMasterStreamTones="); pw.println(mPlayMasterStreamTones);
    490         pw.print("  isShowing()="); pw.println(isShowing());
    491         pw.print("  mCallback="); pw.println(mCallback);
    492         pw.print("  sConfirmSafeVolumeDialog=");
    493         pw.println(sSafetyWarning != null ? "<not null>" : null);
    494         pw.print("  mActiveStreamType="); pw.println(mActiveStreamType);
    495         pw.print("  mStreamControls=");
    496         if (mStreamControls == null) {
    497             pw.println("null");
    498         } else {
    499             final int N = mStreamControls.size();
    500             pw.print("<size "); pw.print(N); pw.println('>');
    501             for (int i = 0; i < N; i++) {
    502                 final StreamControl sc = mStreamControls.valueAt(i);
    503                 pw.print("    stream "); pw.print(sc.streamType); pw.print(":");
    504                 if (sc.seekbarView != null) {
    505                     pw.print(" progress="); pw.print(sc.seekbarView.getProgress());
    506                     pw.print(" of "); pw.print(sc.seekbarView.getMax());
    507                     if (!sc.seekbarView.isEnabled()) pw.print(" (disabled)");
    508                 }
    509                 if (sc.icon != null && sc.icon.isClickable()) pw.print(" (clickable)");
    510                 pw.println();
    511             }
    512         }
    513         if (mZenPanel != null) {
    514             mZenPanel.dump(fd, pw, args);
    515         }
    516     }
    517 
    518     private void initZenModePanel() {
    519         mZenPanel.init(mZenController);
    520         mZenPanel.setCallback(new ZenModePanel.Callback() {
    521             @Override
    522             public void onMoreSettings() {
    523                 if (mCallback != null) {
    524                     mCallback.onZenSettings();
    525                 }
    526             }
    527 
    528             @Override
    529             public void onInteraction() {
    530                 resetTimeout();
    531             }
    532 
    533             @Override
    534             public void onExpanded(boolean expanded) {
    535                 if (mZenPanelExpanded == expanded) return;
    536                 mZenPanelExpanded = expanded;
    537                 updateTimeoutDelay();
    538                 resetTimeout();
    539             }
    540         });
    541     }
    542 
    543     private void setLayoutDirection(int layoutDirection) {
    544         mPanel.setLayoutDirection(layoutDirection);
    545         updateStates();
    546     }
    547 
    548     private void registerReceiver() {
    549         final IntentFilter filter = new IntentFilter();
    550         filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
    551         filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
    552         filter.addAction(Intent.ACTION_SCREEN_OFF);
    553         mContext.registerReceiver(new BroadcastReceiver() {
    554             @Override
    555             public void onReceive(Context context, Intent intent) {
    556                 final String action = intent.getAction();
    557 
    558                 if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(action)) {
    559                     removeMessages(MSG_RINGER_MODE_CHANGED);
    560                     sendEmptyMessage(MSG_RINGER_MODE_CHANGED);
    561                 }
    562 
    563                 if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) {
    564                     removeMessages(MSG_INTERNAL_RINGER_MODE_CHANGED);
    565                     sendEmptyMessage(MSG_INTERNAL_RINGER_MODE_CHANGED);
    566                 }
    567 
    568                 if (Intent.ACTION_SCREEN_OFF.equals(action)) {
    569                     postDismiss(0);
    570                 }
    571             }
    572         }, filter);
    573     }
    574 
    575     private boolean isMuted(int streamType) {
    576         if (streamType == STREAM_MASTER) {
    577             return mAudioManager.isMasterMute();
    578         } else if (streamType == STREAM_REMOTE_MUSIC) {
    579             // TODO do we need to support a distinct mute property for remote?
    580             return false;
    581         } else {
    582             return mAudioManager.isStreamMute(streamType);
    583         }
    584     }
    585 
    586     private int getStreamMaxVolume(int streamType) {
    587         if (streamType == STREAM_MASTER) {
    588             return mAudioManager.getMasterMaxVolume();
    589         } else if (streamType == STREAM_REMOTE_MUSIC) {
    590             if (mStreamControls != null) {
    591                 StreamControl sc = mStreamControls.get(streamType);
    592                 if (sc != null && sc.controller != null) {
    593                     PlaybackInfo ai = sc.controller.getPlaybackInfo();
    594                     return ai.getMaxVolume();
    595                 }
    596             }
    597             return -1;
    598         } else {
    599             return mAudioManager.getStreamMaxVolume(streamType);
    600         }
    601     }
    602 
    603     private int getStreamVolume(int streamType) {
    604         if (streamType == STREAM_MASTER) {
    605             return mAudioManager.getMasterVolume();
    606         } else if (streamType == STREAM_REMOTE_MUSIC) {
    607             if (mStreamControls != null) {
    608                 StreamControl sc = mStreamControls.get(streamType);
    609                 if (sc != null && sc.controller != null) {
    610                     PlaybackInfo ai = sc.controller.getPlaybackInfo();
    611                     return ai.getCurrentVolume();
    612                 }
    613             }
    614             return -1;
    615         } else {
    616             return mAudioManager.getStreamVolume(streamType);
    617         }
    618     }
    619 
    620     private void setStreamVolume(StreamControl sc, int index, int flags) {
    621         if (sc.streamType == STREAM_REMOTE_MUSIC) {
    622             if (sc.controller != null) {
    623                 sc.controller.setVolumeTo(index, flags);
    624             } else {
    625                 Log.w(mTag, "Adjusting remote volume without a controller!");
    626             }
    627         } else if (getStreamVolume(sc.streamType) != index) {
    628             if (sc.streamType == STREAM_MASTER) {
    629                 mAudioManager.setMasterVolume(index, flags);
    630             } else {
    631                 mAudioManager.setStreamVolume(sc.streamType, index, flags);
    632             }
    633         }
    634     }
    635 
    636     private void createSliders() {
    637         final Resources res = mContext.getResources();
    638         final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
    639                 Context.LAYOUT_INFLATER_SERVICE);
    640 
    641         mStreamControls = new SparseArray<StreamControl>(STREAMS.length);
    642 
    643         final StreamResources notificationStream = StreamResources.NotificationStream;
    644         for (int i = 0; i < STREAMS.length; i++) {
    645             StreamResources streamRes = STREAMS[i];
    646 
    647             final int streamType = streamRes.streamType;
    648             final boolean isNotification = isNotificationOrRing(streamType);
    649 
    650             final StreamControl sc = new StreamControl();
    651             sc.streamType = streamType;
    652             sc.group = (ViewGroup) inflater.inflate(
    653                     com.android.systemui.R.layout.volume_panel_item, null);
    654             sc.group.setTag(sc);
    655             sc.icon = (ImageView) sc.group.findViewById(com.android.systemui.R.id.stream_icon);
    656             sc.icon.setTag(sc);
    657             sc.icon.setContentDescription(res.getString(streamRes.descRes));
    658             sc.iconRes = streamRes.iconRes;
    659             sc.iconMuteRes = streamRes.iconMuteRes;
    660             sc.icon.setImageResource(sc.iconRes);
    661             sc.icon.setClickable(isNotification && mHasVibrator);
    662             if (isNotification) {
    663                 if (mHasVibrator) {
    664                     sc.icon.setSoundEffectsEnabled(false);
    665                     sc.iconMuteRes = com.android.systemui.R.drawable.ic_ringer_vibrate;
    666                     sc.icon.setOnClickListener(new OnClickListener() {
    667                         @Override
    668                         public void onClick(View v) {
    669                             resetTimeout();
    670                             toggleRinger(sc);
    671                         }
    672                     });
    673                 }
    674                 sc.iconSuppressedRes = com.android.systemui.R.drawable.ic_ringer_mute;
    675             }
    676             sc.seekbarView = (SeekBar) sc.group.findViewById(com.android.systemui.R.id.seekbar);
    677             sc.suppressorView =
    678                     (TextView) sc.group.findViewById(com.android.systemui.R.id.suppressor);
    679             sc.suppressorView.setVisibility(View.GONE);
    680             final boolean showSecondary = !isNotification && notificationStream.show;
    681             sc.divider = sc.group.findViewById(com.android.systemui.R.id.divider);
    682             sc.secondaryIcon = (ImageView) sc.group
    683                     .findViewById(com.android.systemui.R.id.secondary_icon);
    684             sc.secondaryIcon.setImageResource(com.android.systemui.R.drawable.ic_ringer_audible);
    685             sc.secondaryIcon.setContentDescription(res.getString(notificationStream.descRes));
    686             sc.secondaryIcon.setClickable(showSecondary);
    687             sc.divider.setVisibility(showSecondary ? View.VISIBLE : View.GONE);
    688             sc.secondaryIcon.setVisibility(showSecondary ? View.VISIBLE : View.GONE);
    689             if (showSecondary) {
    690                 sc.secondaryIcon.setOnClickListener(new OnClickListener() {
    691                     @Override
    692                     public void onClick(View v) {
    693                         mSecondaryIconTransition.start(sc);
    694                     }
    695                 });
    696             }
    697             final int plusOne = (streamType == AudioSystem.STREAM_BLUETOOTH_SCO ||
    698                     streamType == AudioSystem.STREAM_VOICE_CALL) ? 1 : 0;
    699             sc.seekbarView.setMax(getStreamMaxVolume(streamType) + plusOne);
    700             sc.seekbarView.setOnSeekBarChangeListener(mSeekListener);
    701             sc.seekbarView.setTag(sc);
    702             mStreamControls.put(streamType, sc);
    703         }
    704     }
    705 
    706     private void toggleRinger(StreamControl sc) {
    707         if (!mHasVibrator) return;
    708         if (mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_NORMAL) {
    709             mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_VIBRATE);
    710             postVolumeChanged(sc.streamType, AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE);
    711         } else {
    712             mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL);
    713             postVolumeChanged(sc.streamType, AudioManager.FLAG_PLAY_SOUND);
    714         }
    715     }
    716 
    717     private void reorderSliders(int activeStreamType) {
    718         mSliderPanel.removeAllViews();
    719 
    720         final StreamControl active = mStreamControls.get(activeStreamType);
    721         if (active == null) {
    722             Log.e(TAG, "Missing stream type! - " + activeStreamType);
    723             mActiveStreamType = -1;
    724         } else {
    725             mSliderPanel.addView(active.group);
    726             mActiveStreamType = activeStreamType;
    727             active.group.setVisibility(View.VISIBLE);
    728             updateSlider(active, true /*forceReloadIcon*/);
    729             updateTimeoutDelay();
    730             updateZenPanelVisible();
    731         }
    732     }
    733 
    734     private void updateSliderProgress(StreamControl sc, int progress) {
    735         final boolean isRinger = isNotificationOrRing(sc.streamType);
    736         if (isRinger && mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT) {
    737             progress = mLastRingerProgress;
    738         }
    739         if (progress < 0) {
    740             progress = getStreamVolume(sc.streamType);
    741         }
    742         sc.seekbarView.setProgress(progress);
    743         if (isRinger) {
    744             mLastRingerProgress = progress;
    745         }
    746     }
    747 
    748     private void updateSliderIcon(StreamControl sc, boolean muted) {
    749         ComponentName suppressor = null;
    750         if (isNotificationOrRing(sc.streamType)) {
    751             suppressor = mNotificationEffectsSuppressor;
    752             int ringerMode = mAudioManager.getRingerModeInternal();
    753             if (ringerMode == AudioManager.RINGER_MODE_SILENT) {
    754                 ringerMode = mLastRingerMode;
    755             } else {
    756                 mLastRingerMode = ringerMode;
    757             }
    758             if (mHasVibrator) {
    759                 muted = ringerMode == AudioManager.RINGER_MODE_VIBRATE;
    760             } else {
    761                 muted = false;
    762             }
    763         }
    764         sc.icon.setImageResource(mDemoIcon != 0 ? mDemoIcon
    765                 : suppressor != null ? sc.iconSuppressedRes
    766                 : muted ? sc.iconMuteRes
    767                 : sc.iconRes);
    768     }
    769 
    770     private void updateSliderSuppressor(StreamControl sc) {
    771         final ComponentName suppressor = isNotificationOrRing(sc.streamType)
    772                 ? mNotificationEffectsSuppressor : null;
    773         if (suppressor == null) {
    774             sc.seekbarView.setVisibility(View.VISIBLE);
    775             sc.suppressorView.setVisibility(View.GONE);
    776         } else {
    777             sc.seekbarView.setVisibility(View.GONE);
    778             sc.suppressorView.setVisibility(View.VISIBLE);
    779             sc.suppressorView.setText(mContext.getString(R.string.muted_by,
    780                     getSuppressorCaption(suppressor)));
    781         }
    782     }
    783 
    784     private String getSuppressorCaption(ComponentName suppressor) {
    785         final PackageManager pm = mContext.getPackageManager();
    786         try {
    787             final ServiceInfo info = pm.getServiceInfo(suppressor, 0);
    788             if (info != null) {
    789                 final CharSequence seq = info.loadLabel(pm);
    790                 if (seq != null) {
    791                     final String str = seq.toString().trim();
    792                     if (str.length() > 0) {
    793                         return str;
    794                     }
    795                 }
    796             }
    797         } catch (Throwable e) {
    798             Log.w(TAG, "Error loading suppressor caption", e);
    799         }
    800         return suppressor.getPackageName();
    801     }
    802 
    803     /** Update the mute and progress state of a slider */
    804     private void updateSlider(StreamControl sc, boolean forceReloadIcon) {
    805         updateSliderProgress(sc, -1);
    806         final boolean muted = isMuted(sc.streamType);
    807         if (forceReloadIcon) {
    808             sc.icon.setImageDrawable(null);
    809         }
    810         updateSliderIcon(sc, muted);
    811         updateSliderEnabled(sc, muted, false);
    812         updateSliderSuppressor(sc);
    813     }
    814 
    815     private void updateSliderEnabled(final StreamControl sc, boolean muted, boolean fixedVolume) {
    816         final boolean wasEnabled = sc.seekbarView.isEnabled();
    817         final boolean isRinger = isNotificationOrRing(sc.streamType);
    818         if (sc.streamType == STREAM_REMOTE_MUSIC) {
    819             // never disable touch interactions for remote playback, the muting is not tied to
    820             // the state of the phone.
    821             sc.seekbarView.setEnabled(!fixedVolume);
    822         } else if (isRinger && mNotificationEffectsSuppressor != null) {
    823             sc.icon.setEnabled(true);
    824             sc.icon.setAlpha(1f);
    825             sc.icon.setClickable(false);
    826         } else if (isRinger
    827                 && mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT) {
    828             sc.seekbarView.setEnabled(false);
    829             sc.icon.setEnabled(false);
    830             sc.icon.setAlpha(mDisabledAlpha);
    831             sc.icon.setClickable(false);
    832         } else if (fixedVolume ||
    833                 (sc.streamType != mAudioManager.getMasterStreamType() && !isRinger && muted) ||
    834                 (sSafetyWarning != null)) {
    835             sc.seekbarView.setEnabled(false);
    836         } else {
    837             sc.seekbarView.setEnabled(true);
    838             sc.icon.setEnabled(true);
    839             sc.icon.setAlpha(1f);
    840         }
    841         // show the silent hint when the disabled slider is touched in silent mode
    842         if (isRinger && wasEnabled != sc.seekbarView.isEnabled()) {
    843             if (sc.seekbarView.isEnabled()) {
    844                 sc.group.setOnTouchListener(null);
    845                 sc.icon.setClickable(mHasVibrator);
    846             } else {
    847                 final View.OnTouchListener showHintOnTouch = new View.OnTouchListener() {
    848                     @Override
    849                     public boolean onTouch(View v, MotionEvent event) {
    850                         resetTimeout();
    851                         showSilentHint();
    852                         return false;
    853                     }
    854                 };
    855                 sc.group.setOnTouchListener(showHintOnTouch);
    856             }
    857         }
    858     }
    859 
    860     private void showSilentHint() {
    861         if (mZenPanel != null) {
    862             mZenPanel.showSilentHint();
    863         }
    864     }
    865 
    866     private void showVibrateHint() {
    867         final StreamControl active = mStreamControls.get(mActiveStreamType);
    868         if (active != null) {
    869             mIconPulser.start(active.icon);
    870             if (!hasMessages(MSG_VIBRATE)) {
    871                 sendEmptyMessageDelayed(MSG_VIBRATE, VIBRATE_DELAY);
    872             }
    873         }
    874     }
    875 
    876     private static boolean isNotificationOrRing(int streamType) {
    877         return streamType == AudioManager.STREAM_RING
    878                 || streamType == AudioManager.STREAM_NOTIFICATION;
    879     }
    880 
    881     public void setCallback(Callback callback) {
    882         mCallback = callback;
    883     }
    884 
    885     private void updateTimeoutDelay() {
    886         mTimeoutDelay = mDemoIcon != 0 ? TIMEOUT_DELAY_EXPANDED
    887                 : sSafetyWarning != null ? TIMEOUT_DELAY_SAFETY_WARNING
    888                 : mActiveStreamType == AudioManager.STREAM_MUSIC ? TIMEOUT_DELAY_SHORT
    889                 : mZenPanelExpanded ? TIMEOUT_DELAY_EXPANDED
    890                 : isZenPanelVisible() ? TIMEOUT_DELAY_COLLAPSED
    891                 : TIMEOUT_DELAY;
    892     }
    893 
    894     private boolean isZenPanelVisible() {
    895         return mZenPanel != null && mZenPanel.getVisibility() == View.VISIBLE;
    896     }
    897 
    898     private void setZenPanelVisible(boolean visible) {
    899         if (LOGD) Log.d(mTag, "setZenPanelVisible " + visible + " mZenPanel=" + mZenPanel);
    900         final boolean changing = visible != isZenPanelVisible();
    901         if (visible) {
    902             mZenPanel.setHidden(false);
    903             resetTimeout();
    904         } else {
    905             mZenPanel.setHidden(true);
    906         }
    907         if (changing) {
    908             updateTimeoutDelay();
    909             resetTimeout();
    910         }
    911     }
    912 
    913     private void updateStates() {
    914         final int count = mSliderPanel.getChildCount();
    915         for (int i = 0; i < count; i++) {
    916             StreamControl sc = (StreamControl) mSliderPanel.getChildAt(i).getTag();
    917             updateSlider(sc, true /*forceReloadIcon*/);
    918         }
    919     }
    920 
    921     private void updateActiveSlider() {
    922         final StreamControl active = mStreamControls.get(mActiveStreamType);
    923         if (active != null) {
    924             updateSlider(active, false /*forceReloadIcon*/);
    925         }
    926     }
    927 
    928     private void updateZenPanelVisible() {
    929         setZenPanelVisible(mZenModeAvailable && isNotificationOrRing(mActiveStreamType));
    930     }
    931 
    932     public void postVolumeChanged(int streamType, int flags) {
    933         if (hasMessages(MSG_VOLUME_CHANGED)) return;
    934         synchronized (this) {
    935             if (mStreamControls == null) {
    936                 createSliders();
    937             }
    938         }
    939         removeMessages(MSG_FREE_RESOURCES);
    940         obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget();
    941     }
    942 
    943     public void postRemoteVolumeChanged(MediaController controller, int flags) {
    944         if (hasMessages(MSG_REMOTE_VOLUME_CHANGED)) return;
    945         synchronized (this) {
    946             if (mStreamControls == null) {
    947                 createSliders();
    948             }
    949         }
    950         removeMessages(MSG_FREE_RESOURCES);
    951         obtainMessage(MSG_REMOTE_VOLUME_CHANGED, flags, 0, controller).sendToTarget();
    952     }
    953 
    954     public void postRemoteSliderVisibility(boolean visible) {
    955         obtainMessage(MSG_SLIDER_VISIBILITY_CHANGED,
    956                 STREAM_REMOTE_MUSIC, visible ? 1 : 0).sendToTarget();
    957     }
    958 
    959     /**
    960      * Called by AudioService when it has received new remote playback information that
    961      * would affect the VolumePanel display (mainly volumes). The difference with
    962      * {@link #postRemoteVolumeChanged(int, int)} is that the handling of the posted message
    963      * (MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN) will only update the volume slider if it is being
    964      * displayed.
    965      * This special code path is due to the fact that remote volume updates arrive to AudioService
    966      * asynchronously. So after AudioService has sent the volume update (which should be treated
    967      * as a request to update the volume), the application will likely set a new volume. If the UI
    968      * is still up, we need to refresh the display to show this new value.
    969      */
    970     public void postHasNewRemotePlaybackInfo() {
    971         if (hasMessages(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN)) return;
    972         // don't create or prevent resources to be freed, if they disappear, this update came too
    973         //   late and shouldn't warrant the panel to be displayed longer
    974         obtainMessage(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN).sendToTarget();
    975     }
    976 
    977     public void postMasterVolumeChanged(int flags) {
    978         postVolumeChanged(STREAM_MASTER, flags);
    979     }
    980 
    981     public void postMuteChanged(int streamType, int flags) {
    982         if (hasMessages(MSG_VOLUME_CHANGED)) return;
    983         synchronized (this) {
    984             if (mStreamControls == null) {
    985                 createSliders();
    986             }
    987         }
    988         removeMessages(MSG_FREE_RESOURCES);
    989         obtainMessage(MSG_MUTE_CHANGED, streamType, flags).sendToTarget();
    990     }
    991 
    992     public void postMasterMuteChanged(int flags) {
    993         postMuteChanged(STREAM_MASTER, flags);
    994     }
    995 
    996     public void postDisplaySafeVolumeWarning(int flags) {
    997         if (hasMessages(MSG_DISPLAY_SAFE_VOLUME_WARNING)) return;
    998         obtainMessage(MSG_DISPLAY_SAFE_VOLUME_WARNING, flags, 0).sendToTarget();
    999     }
   1000 
   1001     public void postDismiss(long delay) {
   1002         forceTimeout(delay);
   1003     }
   1004 
   1005     public void postLayoutDirection(int layoutDirection) {
   1006         removeMessages(MSG_LAYOUT_DIRECTION);
   1007         obtainMessage(MSG_LAYOUT_DIRECTION, layoutDirection, 0).sendToTarget();
   1008     }
   1009 
   1010     private static String flagsToString(int flags) {
   1011         return flags == 0 ? "0" : (flags + "=" + AudioManager.flagsToString(flags));
   1012     }
   1013 
   1014     private static String streamToString(int stream) {
   1015         return AudioService.streamToString(stream);
   1016     }
   1017 
   1018     /**
   1019      * Override this if you have other work to do when the volume changes (for
   1020      * example, vibrating, playing a sound, etc.). Make sure to call through to
   1021      * the superclass implementation.
   1022      */
   1023     protected void onVolumeChanged(int streamType, int flags) {
   1024 
   1025         if (LOGD) Log.d(mTag, "onVolumeChanged(streamType: " + streamToString(streamType)
   1026                 + ", flags: " + flagsToString(flags) + ")");
   1027 
   1028         if ((flags & AudioManager.FLAG_SHOW_UI) != 0) {
   1029             synchronized (this) {
   1030                 if (mActiveStreamType != streamType) {
   1031                     reorderSliders(streamType);
   1032                 }
   1033                 onShowVolumeChanged(streamType, flags, null);
   1034             }
   1035         }
   1036 
   1037         if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) {
   1038             removeMessages(MSG_PLAY_SOUND);
   1039             sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY);
   1040         }
   1041 
   1042         if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) {
   1043             removeMessages(MSG_PLAY_SOUND);
   1044             removeMessages(MSG_VIBRATE);
   1045             onStopSounds();
   1046         }
   1047 
   1048         removeMessages(MSG_FREE_RESOURCES);
   1049         sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY);
   1050         resetTimeout();
   1051     }
   1052 
   1053     protected void onMuteChanged(int streamType, int flags) {
   1054 
   1055         if (LOGD) Log.d(mTag, "onMuteChanged(streamType: " + streamToString(streamType)
   1056                 + ", flags: " + flagsToString(flags) + ")");
   1057 
   1058         StreamControl sc = mStreamControls.get(streamType);
   1059         if (sc != null) {
   1060             updateSliderIcon(sc, isMuted(sc.streamType));
   1061         }
   1062 
   1063         onVolumeChanged(streamType, flags);
   1064     }
   1065 
   1066     protected void onShowVolumeChanged(int streamType, int flags, MediaController controller) {
   1067         int index = getStreamVolume(streamType);
   1068 
   1069         mRingIsSilent = false;
   1070 
   1071         if (LOGD) {
   1072             Log.d(mTag, "onShowVolumeChanged(streamType: " + streamToString(streamType)
   1073                     + ", flags: " + flagsToString(flags) + "), index: " + index);
   1074         }
   1075 
   1076         // get max volume for progress bar
   1077 
   1078         int max = getStreamMaxVolume(streamType);
   1079         StreamControl sc = mStreamControls.get(streamType);
   1080 
   1081         switch (streamType) {
   1082 
   1083             case AudioManager.STREAM_RING: {
   1084                 Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri(
   1085                         mContext, RingtoneManager.TYPE_RINGTONE);
   1086                 if (ringuri == null) {
   1087                     mRingIsSilent = true;
   1088                 }
   1089                 break;
   1090             }
   1091 
   1092             case AudioManager.STREAM_MUSIC: {
   1093                 // Special case for when Bluetooth is active for music
   1094                 if ((mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC) &
   1095                         (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP |
   1096                         AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
   1097                         AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0) {
   1098                     setMusicIcon(IC_AUDIO_BT, IC_AUDIO_BT_MUTE);
   1099                 } else {
   1100                     setMusicIcon(IC_AUDIO_VOL, IC_AUDIO_VOL_MUTE);
   1101                 }
   1102                 break;
   1103             }
   1104 
   1105             case AudioManager.STREAM_VOICE_CALL: {
   1106                 /*
   1107                  * For in-call voice call volume, there is no inaudible volume.
   1108                  * Rescale the UI control so the progress bar doesn't go all
   1109                  * the way to zero and don't show the mute icon.
   1110                  */
   1111                 index++;
   1112                 max++;
   1113                 break;
   1114             }
   1115 
   1116             case AudioManager.STREAM_ALARM: {
   1117                 break;
   1118             }
   1119 
   1120             case AudioManager.STREAM_NOTIFICATION: {
   1121                 Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri(
   1122                         mContext, RingtoneManager.TYPE_NOTIFICATION);
   1123                 if (ringuri == null) {
   1124                     mRingIsSilent = true;
   1125                 }
   1126                 break;
   1127             }
   1128 
   1129             case AudioManager.STREAM_BLUETOOTH_SCO: {
   1130                 /*
   1131                  * For in-call voice call volume, there is no inaudible volume.
   1132                  * Rescale the UI control so the progress bar doesn't go all
   1133                  * the way to zero and don't show the mute icon.
   1134                  */
   1135                 index++;
   1136                 max++;
   1137                 break;
   1138             }
   1139 
   1140             case STREAM_REMOTE_MUSIC: {
   1141                 if (controller == null && sc != null) {
   1142                     // If we weren't passed one try using the last one set.
   1143                     controller = sc.controller;
   1144                 }
   1145                 if (controller == null) {
   1146                     // We still don't have one, ignore the command.
   1147                     Log.w(mTag, "sent remote volume change without a controller!");
   1148                 } else {
   1149                     PlaybackInfo vi = controller.getPlaybackInfo();
   1150                     index = vi.getCurrentVolume();
   1151                     max = vi.getMaxVolume();
   1152                     if ((vi.getVolumeControl() & VolumeProvider.VOLUME_CONTROL_FIXED) != 0) {
   1153                         // if the remote volume is fixed add the flag for the UI
   1154                         flags |= AudioManager.FLAG_FIXED_VOLUME;
   1155                     }
   1156                 }
   1157                 if (LOGD) { Log.d(mTag, "showing remote volume "+index+" over "+ max); }
   1158                 break;
   1159             }
   1160         }
   1161 
   1162         if (sc != null) {
   1163             if (streamType == STREAM_REMOTE_MUSIC && controller != sc.controller) {
   1164                 if (sc.controller != null) {
   1165                     sc.controller.unregisterCallback(mMediaControllerCb);
   1166                 }
   1167                 sc.controller = controller;
   1168                 if (controller != null) {
   1169                     sc.controller.registerCallback(mMediaControllerCb);
   1170                 }
   1171             }
   1172             if (sc.seekbarView.getMax() != max) {
   1173                 sc.seekbarView.setMax(max);
   1174             }
   1175             updateSliderProgress(sc, index);
   1176             final boolean muted = isMuted(streamType);
   1177             updateSliderEnabled(sc, muted, (flags & AudioManager.FLAG_FIXED_VOLUME) != 0);
   1178             if (isNotificationOrRing(streamType)) {
   1179                 // check for secondary-icon transition completion
   1180                 if (mSecondaryIconTransition.isRunning()) {
   1181                     mSecondaryIconTransition.cancel();  // safe to reset
   1182                     sc.seekbarView.setAlpha(0); sc.seekbarView.animate().alpha(1);
   1183                     mZenPanel.setAlpha(0); mZenPanel.animate().alpha(1);
   1184                 }
   1185                 updateSliderIcon(sc, muted);
   1186             }
   1187         }
   1188 
   1189         if (!isShowing()) {
   1190             int stream = (streamType == STREAM_REMOTE_MUSIC) ? -1 : streamType;
   1191             // when the stream is for remote playback, use -1 to reset the stream type evaluation
   1192             if (stream != STREAM_MASTER) {
   1193                 mAudioManager.forceVolumeControlStream(stream);
   1194             }
   1195             mDialog.show();
   1196             if (mCallback != null) {
   1197                 mCallback.onVisible(true);
   1198             }
   1199             announceDialogShown();
   1200         }
   1201 
   1202         // Do a little vibrate if applicable (only when going into vibrate mode)
   1203         if ((streamType != STREAM_REMOTE_MUSIC) &&
   1204                 ((flags & AudioManager.FLAG_VIBRATE) != 0) &&
   1205                 isNotificationOrRing(streamType) &&
   1206                 mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE) {
   1207             sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY);
   1208         }
   1209 
   1210         // Pulse the zen icon if an adjustment was suppressed due to silent mode.
   1211         if ((flags & AudioManager.FLAG_SHOW_SILENT_HINT) != 0) {
   1212             showSilentHint();
   1213         }
   1214 
   1215         // Pulse the slider icon & vibrate if an adjustment down was suppressed due to vibrate mode.
   1216         if ((flags & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0) {
   1217             showVibrateHint();
   1218         }
   1219     }
   1220 
   1221     private void announceDialogShown() {
   1222         mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
   1223     }
   1224 
   1225     private boolean isShowing() {
   1226         return mDialog.isShowing();
   1227     }
   1228 
   1229     protected void onPlaySound(int streamType, int flags) {
   1230 
   1231         if (hasMessages(MSG_STOP_SOUNDS)) {
   1232             removeMessages(MSG_STOP_SOUNDS);
   1233             // Force stop right now
   1234             onStopSounds();
   1235         }
   1236 
   1237         synchronized (this) {
   1238             ToneGenerator toneGen = getOrCreateToneGenerator(streamType);
   1239             if (toneGen != null) {
   1240                 toneGen.startTone(ToneGenerator.TONE_PROP_BEEP);
   1241                 sendMessageDelayed(obtainMessage(MSG_STOP_SOUNDS), BEEP_DURATION);
   1242             }
   1243         }
   1244     }
   1245 
   1246     protected void onStopSounds() {
   1247 
   1248         synchronized (this) {
   1249             int numStreamTypes = AudioSystem.getNumStreamTypes();
   1250             for (int i = numStreamTypes - 1; i >= 0; i--) {
   1251                 ToneGenerator toneGen = mToneGenerators[i];
   1252                 if (toneGen != null) {
   1253                     toneGen.stopTone();
   1254                 }
   1255             }
   1256         }
   1257     }
   1258 
   1259     protected void onVibrate() {
   1260 
   1261         // Make sure we ended up in vibrate ringer mode
   1262         if (mAudioManager.getRingerModeInternal() != AudioManager.RINGER_MODE_VIBRATE) {
   1263             return;
   1264         }
   1265         if (mVibrator != null) {
   1266             mVibrator.vibrate(VIBRATE_DURATION, VIBRATION_ATTRIBUTES);
   1267         }
   1268     }
   1269 
   1270     protected void onRemoteVolumeChanged(MediaController controller, int flags) {
   1271         if (LOGD) Log.d(mTag, "onRemoteVolumeChanged(controller:" + controller + ", flags: "
   1272                 + flagsToString(flags) + ")");
   1273 
   1274         if (((flags & AudioManager.FLAG_SHOW_UI) != 0) || isShowing()) {
   1275             synchronized (this) {
   1276                 if (mActiveStreamType != STREAM_REMOTE_MUSIC) {
   1277                     reorderSliders(STREAM_REMOTE_MUSIC);
   1278                 }
   1279                 onShowVolumeChanged(STREAM_REMOTE_MUSIC, flags, controller);
   1280             }
   1281         } else {
   1282             if (LOGD) Log.d(mTag, "not calling onShowVolumeChanged(), no FLAG_SHOW_UI or no UI");
   1283         }
   1284 
   1285         removeMessages(MSG_FREE_RESOURCES);
   1286         sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY);
   1287         resetTimeout();
   1288     }
   1289 
   1290     protected void onRemoteVolumeUpdateIfShown() {
   1291         if (LOGD) Log.d(mTag, "onRemoteVolumeUpdateIfShown()");
   1292         if (isShowing()
   1293                 && (mActiveStreamType == STREAM_REMOTE_MUSIC)
   1294                 && (mStreamControls != null)) {
   1295             onShowVolumeChanged(STREAM_REMOTE_MUSIC, 0, null);
   1296         }
   1297     }
   1298 
   1299     /**
   1300      * Clear the current remote stream controller.
   1301      */
   1302     private void clearRemoteStreamController() {
   1303         if (mStreamControls != null) {
   1304             StreamControl sc = mStreamControls.get(STREAM_REMOTE_MUSIC);
   1305             if (sc != null) {
   1306                 if (sc.controller != null) {
   1307                     sc.controller.unregisterCallback(mMediaControllerCb);
   1308                     sc.controller = null;
   1309                 }
   1310             }
   1311         }
   1312     }
   1313 
   1314     /**
   1315      * Handler for MSG_SLIDER_VISIBILITY_CHANGED Hide or show a slider
   1316      *
   1317      * @param streamType can be a valid stream type value, or
   1318      *            VolumePanel.STREAM_MASTER, or VolumePanel.STREAM_REMOTE_MUSIC
   1319      * @param visible
   1320      */
   1321     synchronized protected void onSliderVisibilityChanged(int streamType, int visible) {
   1322         if (LOGD) Log.d(mTag, "onSliderVisibilityChanged(stream="+streamType+", visi="+visible+")");
   1323         boolean isVisible = (visible == 1);
   1324         for (int i = STREAMS.length - 1 ; i >= 0 ; i--) {
   1325             StreamResources streamRes = STREAMS[i];
   1326             if (streamRes.streamType == streamType) {
   1327                 streamRes.show = isVisible;
   1328                 if (!isVisible && (mActiveStreamType == streamType)) {
   1329                     mActiveStreamType = -1;
   1330                 }
   1331                 break;
   1332             }
   1333         }
   1334     }
   1335 
   1336     protected void onDisplaySafeVolumeWarning(int flags) {
   1337         if ((flags & (AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_SHOW_UI_WARNINGS)) != 0
   1338                 || isShowing()) {
   1339             synchronized (sSafetyWarningLock) {
   1340                 if (sSafetyWarning != null) {
   1341                     return;
   1342                 }
   1343                 sSafetyWarning = new SafetyWarning(mContext, this, mAudioManager);
   1344                 sSafetyWarning.show();
   1345             }
   1346             updateStates();
   1347         }
   1348         if (mAccessibilityManager.isTouchExplorationEnabled()) {
   1349             removeMessages(MSG_TIMEOUT);
   1350         } else {
   1351             updateTimeoutDelay();
   1352             resetTimeout();
   1353         }
   1354     }
   1355 
   1356     /**
   1357      * Lock on this VolumePanel instance as long as you use the returned ToneGenerator.
   1358      */
   1359     private ToneGenerator getOrCreateToneGenerator(int streamType) {
   1360         if (streamType == STREAM_MASTER) {
   1361             // For devices that use the master volume setting only but still want to
   1362             // play a volume-changed tone, direct the master volume pseudostream to
   1363             // the system stream's tone generator.
   1364             if (mPlayMasterStreamTones) {
   1365                 streamType = AudioManager.STREAM_SYSTEM;
   1366             } else {
   1367                 return null;
   1368             }
   1369         }
   1370         synchronized (this) {
   1371             if (mToneGenerators[streamType] == null) {
   1372                 try {
   1373                     mToneGenerators[streamType] = new ToneGenerator(streamType, MAX_VOLUME);
   1374                 } catch (RuntimeException e) {
   1375                     if (LOGD) {
   1376                         Log.d(mTag, "ToneGenerator constructor failed with "
   1377                                 + "RuntimeException: " + e);
   1378                     }
   1379                 }
   1380             }
   1381             return mToneGenerators[streamType];
   1382         }
   1383     }
   1384 
   1385 
   1386     /**
   1387      * Switch between icons because Bluetooth music is same as music volume, but with
   1388      * different icons.
   1389      */
   1390     private void setMusicIcon(int resId, int resMuteId) {
   1391         StreamControl sc = mStreamControls.get(AudioManager.STREAM_MUSIC);
   1392         if (sc != null) {
   1393             sc.iconRes = resId;
   1394             sc.iconMuteRes = resMuteId;
   1395             updateSliderIcon(sc, isMuted(sc.streamType));
   1396         }
   1397     }
   1398 
   1399     protected void onFreeResources() {
   1400         synchronized (this) {
   1401             for (int i = mToneGenerators.length - 1; i >= 0; i--) {
   1402                 if (mToneGenerators[i] != null) {
   1403                     mToneGenerators[i].release();
   1404                 }
   1405                 mToneGenerators[i] = null;
   1406             }
   1407         }
   1408     }
   1409 
   1410     @Override
   1411     public void handleMessage(Message msg) {
   1412         switch (msg.what) {
   1413 
   1414             case MSG_VOLUME_CHANGED: {
   1415                 onVolumeChanged(msg.arg1, msg.arg2);
   1416                 break;
   1417             }
   1418 
   1419             case MSG_MUTE_CHANGED: {
   1420                 onMuteChanged(msg.arg1, msg.arg2);
   1421                 break;
   1422             }
   1423 
   1424             case MSG_FREE_RESOURCES: {
   1425                 onFreeResources();
   1426                 break;
   1427             }
   1428 
   1429             case MSG_STOP_SOUNDS: {
   1430                 onStopSounds();
   1431                 break;
   1432             }
   1433 
   1434             case MSG_PLAY_SOUND: {
   1435                 onPlaySound(msg.arg1, msg.arg2);
   1436                 break;
   1437             }
   1438 
   1439             case MSG_VIBRATE: {
   1440                 onVibrate();
   1441                 break;
   1442             }
   1443 
   1444             case MSG_TIMEOUT: {
   1445                 if (isShowing()) {
   1446                     mDialog.dismiss();
   1447                     clearRemoteStreamController();
   1448                     mActiveStreamType = -1;
   1449                     if (mCallback != null) {
   1450                         mCallback.onVisible(false);
   1451                     }
   1452                 }
   1453                 synchronized (sSafetyWarningLock) {
   1454                     if (sSafetyWarning != null) {
   1455                         if (LOGD) Log.d(mTag, "SafetyWarning timeout");
   1456                         sSafetyWarning.dismiss();
   1457                     }
   1458                 }
   1459                 break;
   1460             }
   1461 
   1462             case MSG_RINGER_MODE_CHANGED:
   1463             case MSG_INTERNAL_RINGER_MODE_CHANGED:
   1464             case MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED: {
   1465                 if (isShowing()) {
   1466                     updateActiveSlider();
   1467                 }
   1468                 break;
   1469             }
   1470 
   1471             case MSG_REMOTE_VOLUME_CHANGED: {
   1472                 onRemoteVolumeChanged((MediaController) msg.obj, msg.arg1);
   1473                 break;
   1474             }
   1475 
   1476             case MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN:
   1477                 onRemoteVolumeUpdateIfShown();
   1478                 break;
   1479 
   1480             case MSG_SLIDER_VISIBILITY_CHANGED:
   1481                 onSliderVisibilityChanged(msg.arg1, msg.arg2);
   1482                 break;
   1483 
   1484             case MSG_DISPLAY_SAFE_VOLUME_WARNING:
   1485                 onDisplaySafeVolumeWarning(msg.arg1);
   1486                 break;
   1487 
   1488             case MSG_LAYOUT_DIRECTION:
   1489                 setLayoutDirection(msg.arg1);
   1490                 break;
   1491 
   1492             case MSG_ZEN_MODE_AVAILABLE_CHANGED:
   1493                 mZenModeAvailable = msg.arg1 != 0;
   1494                 updateZenPanelVisible();
   1495                 break;
   1496 
   1497             case MSG_USER_ACTIVITY:
   1498                 if (mCallback != null) {
   1499                     mCallback.onInteraction();
   1500                 }
   1501                 break;
   1502         }
   1503     }
   1504 
   1505     private void resetTimeout() {
   1506         final boolean touchExploration = mAccessibilityManager.isTouchExplorationEnabled();
   1507         if (LOGD) Log.d(mTag, "resetTimeout at " + System.currentTimeMillis()
   1508                 + " delay=" + mTimeoutDelay + " touchExploration=" + touchExploration);
   1509         if (sSafetyWarning == null || !touchExploration) {
   1510             removeMessages(MSG_TIMEOUT);
   1511             sendEmptyMessageDelayed(MSG_TIMEOUT, mTimeoutDelay);
   1512             removeMessages(MSG_USER_ACTIVITY);
   1513             sendEmptyMessage(MSG_USER_ACTIVITY);
   1514         }
   1515     }
   1516 
   1517     private void forceTimeout(long delay) {
   1518         if (LOGD) Log.d(mTag, "forceTimeout delay=" + delay + " callers=" + Debug.getCallers(3));
   1519         removeMessages(MSG_TIMEOUT);
   1520         sendEmptyMessageDelayed(MSG_TIMEOUT, delay);
   1521     }
   1522 
   1523     public ZenModeController getZenController() {
   1524         return mZenController;
   1525     }
   1526 
   1527     @Override
   1528     public void dispatchDemoCommand(String command, Bundle args) {
   1529         if (!COMMAND_VOLUME.equals(command)) return;
   1530         String icon = args.getString("icon");
   1531         final String iconMute = args.getString("iconmute");
   1532         final boolean mute = iconMute != null;
   1533         icon = mute ? iconMute : icon;
   1534         icon = icon.endsWith("Stream") ? icon : (icon + "Stream");
   1535         final StreamResources sr = StreamResources.valueOf(icon);
   1536         mDemoIcon = mute ? sr.iconMuteRes : sr.iconRes;
   1537         final int forcedStreamType = StreamResources.MediaStream.streamType;
   1538         mAudioManager.forceVolumeControlStream(forcedStreamType);
   1539         mAudioManager.adjustStreamVolume(forcedStreamType, AudioManager.ADJUST_SAME,
   1540                 AudioManager.FLAG_SHOW_UI);
   1541     }
   1542 
   1543     private final OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
   1544         @Override
   1545         public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
   1546             final Object tag = seekBar.getTag();
   1547             if (fromUser && tag instanceof StreamControl) {
   1548                 StreamControl sc = (StreamControl) tag;
   1549                 setStreamVolume(sc, progress,
   1550                         AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE);
   1551             }
   1552             resetTimeout();
   1553         }
   1554 
   1555         @Override
   1556         public void onStartTrackingTouch(SeekBar seekBar) {
   1557         }
   1558 
   1559         @Override
   1560         public void onStopTrackingTouch(SeekBar seekBar) {
   1561         }
   1562     };
   1563 
   1564     private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
   1565         @Override
   1566         public void onZenAvailableChanged(boolean available) {
   1567             obtainMessage(MSG_ZEN_MODE_AVAILABLE_CHANGED, available ? 1 : 0, 0).sendToTarget();
   1568         }
   1569 
   1570         @Override
   1571         public void onEffectsSupressorChanged() {
   1572             mNotificationEffectsSuppressor = mZenController.getEffectsSuppressor();
   1573             sendEmptyMessage(MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED);
   1574         }
   1575     };
   1576 
   1577     private final MediaController.Callback mMediaControllerCb = new MediaController.Callback() {
   1578         public void onAudioInfoChanged(PlaybackInfo info) {
   1579             onRemoteVolumeUpdateIfShown();
   1580         }
   1581     };
   1582 
   1583     private final class SecondaryIconTransition extends AnimatorListenerAdapter
   1584             implements Runnable {
   1585         private static final int ANIMATION_TIME = 400;
   1586         private static final int WAIT_FOR_SWITCH_TIME = 1000;
   1587 
   1588         private final int mAnimationTime = (int)(ANIMATION_TIME * ValueAnimator.getDurationScale());
   1589         private final int mFadeOutTime = mAnimationTime / 2;
   1590         private final int mDelayTime = mAnimationTime / 3;
   1591 
   1592         private final Interpolator mIconInterpolator =
   1593                 AnimationUtils.loadInterpolator(mContext, android.R.interpolator.fast_out_slow_in);
   1594 
   1595         private StreamControl mTarget;
   1596 
   1597         public void start(StreamControl sc) {
   1598             if (sc == null) throw new IllegalArgumentException();
   1599             if (LOGD) Log.d(mTag, "Secondary icon animation start");
   1600             if (mTarget != null) {
   1601                 cancel();
   1602             }
   1603             mTarget = sc;
   1604             mTimeoutDelay = mAnimationTime + WAIT_FOR_SWITCH_TIME;
   1605             resetTimeout();
   1606             mTarget.secondaryIcon.setClickable(false);
   1607             final int N = mTarget.group.getChildCount();
   1608             for (int i = 0; i < N; i++) {
   1609                 final View child = mTarget.group.getChildAt(i);
   1610                 if (child != mTarget.secondaryIcon) {
   1611                     child.animate().alpha(0).setDuration(mFadeOutTime).start();
   1612                 }
   1613             }
   1614             mTarget.secondaryIcon.animate()
   1615                     .translationXBy(mTarget.icon.getX() - mTarget.secondaryIcon.getX())
   1616                     .setInterpolator(mIconInterpolator)
   1617                     .setStartDelay(mDelayTime)
   1618                     .setDuration(mAnimationTime - mDelayTime)
   1619                     .setListener(this)
   1620                     .start();
   1621         }
   1622 
   1623         public boolean isRunning() {
   1624             return mTarget != null;
   1625         }
   1626 
   1627         public void cancel() {
   1628             if (mTarget == null) return;
   1629             mTarget.secondaryIcon.setClickable(true);
   1630             final int N = mTarget.group.getChildCount();
   1631             for (int i = 0; i < N; i++) {
   1632                 final View child = mTarget.group.getChildAt(i);
   1633                 if (child != mTarget.secondaryIcon) {
   1634                     child.animate().cancel();
   1635                     child.setAlpha(1);
   1636                 }
   1637             }
   1638             mTarget.secondaryIcon.animate().cancel();
   1639             mTarget.secondaryIcon.setTranslationX(0);
   1640             mTarget = null;
   1641         }
   1642 
   1643         @Override
   1644         public void onAnimationEnd(Animator animation) {
   1645             if (mTarget == null) return;
   1646             AsyncTask.execute(this);
   1647         }
   1648 
   1649         @Override
   1650         public void run() {
   1651             if (mTarget == null) return;
   1652             if (LOGD) Log.d(mTag, "Secondary icon animation complete, show notification slider");
   1653             mAudioManager.forceVolumeControlStream(StreamResources.NotificationStream.streamType);
   1654             mAudioManager.adjustStreamVolume(StreamResources.NotificationStream.streamType,
   1655                     AudioManager.ADJUST_SAME, AudioManager.FLAG_SHOW_UI);
   1656         }
   1657     }
   1658 
   1659     public interface Callback {
   1660         void onZenSettings();
   1661         void onInteraction();
   1662         void onVisible(boolean visible);
   1663     }
   1664 }
   1665