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