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