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