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