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