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