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