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