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