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