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