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