1 /*
2  * Copyright (C) 2015 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 
17 package com.android.systemui.volume;
18 
19 import android.accessibilityservice.AccessibilityServiceInfo;
20 import android.animation.LayoutTransition;
21 import android.animation.ObjectAnimator;
22 import android.animation.ValueAnimator;
23 import android.annotation.NonNull;
24 import android.annotation.SuppressLint;
25 import android.app.Dialog;
26 import android.app.KeyguardManager;
27 import android.content.Context;
28 import android.content.pm.PackageManager;
29 import android.content.res.ColorStateList;
30 import android.content.res.Configuration;
31 import android.content.res.Resources;
32 import android.graphics.Color;
33 import android.graphics.PixelFormat;
34 import android.graphics.Rect;
35 import android.graphics.drawable.AnimatedVectorDrawable;
36 import android.graphics.drawable.ColorDrawable;
37 import android.graphics.drawable.Drawable;
38 import android.media.AudioManager;
39 import android.media.AudioSystem;
40 import android.os.Debug;
41 import android.os.Handler;
42 import android.os.Looper;
43 import android.os.Message;
44 import android.os.SystemClock;
45 import android.provider.Settings.Global;
46 import android.util.DisplayMetrics;
47 import android.util.Log;
48 import android.util.Slog;
49 import android.util.SparseBooleanArray;
50 import android.view.Gravity;
51 import android.view.MotionEvent;
52 import android.view.View;
53 import android.view.View.AccessibilityDelegate;
54 import android.view.View.OnAttachStateChangeListener;
55 import android.view.View.OnClickListener;
56 import android.view.View.OnTouchListener;
57 import android.view.ViewGroup;
58 import android.view.ViewGroup.MarginLayoutParams;
59 import android.view.Window;
60 import android.view.WindowManager;
61 import android.view.accessibility.AccessibilityEvent;
62 import android.view.accessibility.AccessibilityManager;
63 import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
64 import android.view.animation.DecelerateInterpolator;
65 import android.widget.ImageButton;
66 import android.widget.LinearLayout;
67 import android.widget.SeekBar;
68 import android.widget.SeekBar.OnSeekBarChangeListener;
69 import android.widget.TextView;
70 
71 import com.android.systemui.R;
72 import com.android.systemui.statusbar.policy.ZenModeController;
73 import com.android.systemui.tuner.TunerService;
74 import com.android.systemui.tuner.TunerZenModePanel;
75 import com.android.systemui.volume.VolumeDialogController.State;
76 import com.android.systemui.volume.VolumeDialogController.StreamState;
77 
78 import java.io.PrintWriter;
79 import java.util.ArrayList;
80 import java.util.List;
81 
82 import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
83 import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_GENERIC;
84 
85 /**
86  * Visual presentation of the volume dialog.
87  *
88  * A client of VolumeDialogController and its state model.
89  *
90  * Methods ending in "H" must be called on the (ui) handler.
91  */
92 public class VolumeDialog implements TunerService.Tunable {
93     private static final String TAG = Util.logTag(VolumeDialog.class);
94 
95     public static final String SHOW_FULL_ZEN = "sysui_show_full_zen";
96 
97     private static final long USER_ATTEMPT_GRACE_PERIOD = 1000;
98     private static final int UPDATE_ANIMATION_DURATION = 80;
99 
100     private final Context mContext;
101     private final H mHandler = new H();
102     private final VolumeDialogController mController;
103 
104     private CustomDialog mDialog;
105     private ViewGroup mDialogView;
106     private ViewGroup mDialogContentView;
107     private ImageButton mExpandButton;
108     private final List<VolumeRow> mRows = new ArrayList<>();
109     private SpTexts mSpTexts;
110     private final SparseBooleanArray mDynamic = new SparseBooleanArray();
111     private final KeyguardManager mKeyguard;
112     private final AudioManager mAudioManager;
113     private final AccessibilityManager mAccessibilityMgr;
114     private int mExpandButtonAnimationDuration;
115     private ZenFooter mZenFooter;
116     private LayoutTransition mLayoutTransition;
117     private final Object mSafetyWarningLock = new Object();
118     private final Accessibility mAccessibility = new Accessibility();
119     private final ColorStateList mActiveSliderTint;
120     private final ColorStateList mInactiveSliderTint;
121     private VolumeDialogMotion mMotion;
122     private final int mWindowType;
123     private final ZenModeController mZenModeController;
124 
125     private boolean mShowing;
126     private boolean mExpanded;
127 
128     private int mActiveStream;
129     private boolean mShowHeaders = VolumePrefs.DEFAULT_SHOW_HEADERS;
130     private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE;
131     private boolean mSilentMode = VolumePrefs.DEFAULT_ENABLE_SILENT_MODE;
132     private State mState;
133     private boolean mExpandButtonAnimationRunning;
134     private SafetyWarningDialog mSafetyWarning;
135     private Callback mCallback;
136     private boolean mPendingStateChanged;
137     private boolean mPendingRecheckAll;
138     private long mCollapseTime;
139     private boolean mHovering = false;
140     private int mDensity;
141 
142     private boolean mShowFullZen;
143     private TunerZenModePanel mZenPanel;
144 
VolumeDialog(Context context, int windowType, VolumeDialogController controller, ZenModeController zenModeController, Callback callback)145     public VolumeDialog(Context context, int windowType, VolumeDialogController controller,
146             ZenModeController zenModeController, Callback callback) {
147         mContext = context;
148         mController = controller;
149         mCallback = callback;
150         mWindowType = windowType;
151         mZenModeController = zenModeController;
152         mKeyguard = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
153         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
154         mAccessibilityMgr = (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
155         mActiveSliderTint = loadColorStateList(R.color.system_accent_color);
156         mInactiveSliderTint = loadColorStateList(R.color.volume_slider_inactive);
157 
158         initDialog();
159 
160         mAccessibility.init();
161 
162         controller.addCallback(mControllerCallbackH, mHandler);
163         controller.getState();
164         TunerService.get(mContext).addTunable(this, SHOW_FULL_ZEN);
165 
166         final Configuration currentConfig = mContext.getResources().getConfiguration();
167         mDensity = currentConfig.densityDpi;
168     }
169 
initDialog()170     private void initDialog() {
171         mDialog = new CustomDialog(mContext);
172 
173         mSpTexts = new SpTexts(mContext);
174         mLayoutTransition = new LayoutTransition();
175         mLayoutTransition.setDuration(new ValueAnimator().getDuration() / 2);
176         mHovering = false;
177         mShowing = false;
178         final Window window = mDialog.getWindow();
179         window.requestFeature(Window.FEATURE_NO_TITLE);
180         window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
181         window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
182         window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
183                 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
184                 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
185                 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
186                 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
187                 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
188         mDialog.setCanceledOnTouchOutside(true);
189         final Resources res = mContext.getResources();
190         final WindowManager.LayoutParams lp = window.getAttributes();
191         lp.type = mWindowType;
192         lp.format = PixelFormat.TRANSLUCENT;
193         lp.setTitle(VolumeDialog.class.getSimpleName());
194         lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
195         lp.y = res.getDimensionPixelSize(R.dimen.volume_offset_top);
196         lp.gravity = Gravity.TOP;
197         lp.windowAnimations = -1;
198         window.setAttributes(lp);
199         window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
200 
201 
202         mDialog.setContentView(R.layout.volume_dialog);
203         mDialogView = (ViewGroup) mDialog.findViewById(R.id.volume_dialog);
204         mDialogView.setOnHoverListener(new View.OnHoverListener() {
205             @Override
206             public boolean onHover(View v, MotionEvent event) {
207                 int action = event.getActionMasked();
208                 mHovering = (action == MotionEvent.ACTION_HOVER_ENTER)
209                         || (action == MotionEvent.ACTION_HOVER_MOVE);
210                 rescheduleTimeoutH();
211                 return true;
212             }
213         });
214         mDialogContentView = (ViewGroup) mDialog.findViewById(R.id.volume_dialog_content);
215         mExpanded = false;
216         mExpandButton = (ImageButton) mDialogView.findViewById(R.id.volume_expand_button);
217         mExpandButton.setOnClickListener(mClickExpand);
218         updateWindowWidthH();
219         updateExpandButtonH();
220 
221         mDialogContentView.setLayoutTransition(mLayoutTransition);
222         mMotion = new VolumeDialogMotion(mDialog, mDialogView, mDialogContentView, mExpandButton,
223                 new VolumeDialogMotion.Callback() {
224                     @Override
225                     public void onAnimatingChanged(boolean animating) {
226                         if (animating) return;
227                         if (mPendingStateChanged) {
228                             mHandler.sendEmptyMessage(H.STATE_CHANGED);
229                             mPendingStateChanged = false;
230                         }
231                         if (mPendingRecheckAll) {
232                             mHandler.sendEmptyMessage(H.RECHECK_ALL);
233                             mPendingRecheckAll = false;
234                         }
235                     }
236                 });
237 
238         if (mRows.isEmpty()) {
239             addRow(AudioManager.STREAM_RING,
240                     R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true);
241             addRow(AudioManager.STREAM_MUSIC,
242                     R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true);
243             addRow(AudioManager.STREAM_ALARM,
244                     R.drawable.ic_volume_alarm, R.drawable.ic_volume_alarm_mute, false);
245             addRow(AudioManager.STREAM_VOICE_CALL,
246                     R.drawable.ic_volume_voice, R.drawable.ic_volume_voice, false);
247             addRow(AudioManager.STREAM_BLUETOOTH_SCO,
248                     R.drawable.ic_volume_bt_sco, R.drawable.ic_volume_bt_sco, false);
249             addRow(AudioManager.STREAM_SYSTEM,
250                     R.drawable.ic_volume_system, R.drawable.ic_volume_system_mute, false);
251         } else {
252             addExistingRows();
253         }
254         mExpandButtonAnimationDuration = res.getInteger(R.integer.volume_expand_animation_duration);
255         mZenFooter = (ZenFooter) mDialog.findViewById(R.id.volume_zen_footer);
256         mZenFooter.init(mZenModeController);
257         mZenPanel = (TunerZenModePanel) mDialog.findViewById(R.id.tuner_zen_mode_panel);
258         mZenPanel.init(mZenModeController);
259         mZenPanel.setCallback(mZenPanelCallback);
260     }
261 
262     @Override
onTuningChanged(String key, String newValue)263     public void onTuningChanged(String key, String newValue) {
264         if (SHOW_FULL_ZEN.equals(key)) {
265             mShowFullZen = newValue != null && Integer.parseInt(newValue) != 0;
266         }
267     }
268 
loadColorStateList(int colorResId)269     private ColorStateList loadColorStateList(int colorResId) {
270         return ColorStateList.valueOf(mContext.getColor(colorResId));
271     }
272 
updateWindowWidthH()273     private void updateWindowWidthH() {
274         final ViewGroup.LayoutParams lp = mDialogView.getLayoutParams();
275         final DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
276         if (D.BUG) Log.d(TAG, "updateWindowWidth dm.w=" + dm.widthPixels);
277         int w = dm.widthPixels;
278         final int max = mContext.getResources()
279                 .getDimensionPixelSize(R.dimen.volume_dialog_panel_width);
280         if (w > max) {
281             w = max;
282         }
283         lp.width = w;
284         mDialogView.setLayoutParams(lp);
285     }
286 
setStreamImportant(int stream, boolean important)287     public void setStreamImportant(int stream, boolean important) {
288         mHandler.obtainMessage(H.SET_STREAM_IMPORTANT, stream, important ? 1 : 0).sendToTarget();
289     }
290 
setShowHeaders(boolean showHeaders)291     public void setShowHeaders(boolean showHeaders) {
292         if (showHeaders == mShowHeaders) return;
293         mShowHeaders = showHeaders;
294         mHandler.sendEmptyMessage(H.RECHECK_ALL);
295     }
296 
setAutomute(boolean automute)297     public void setAutomute(boolean automute) {
298         if (mAutomute == automute) return;
299         mAutomute = automute;
300         mHandler.sendEmptyMessage(H.RECHECK_ALL);
301     }
302 
setSilentMode(boolean silentMode)303     public void setSilentMode(boolean silentMode) {
304         if (mSilentMode == silentMode) return;
305         mSilentMode = silentMode;
306         mHandler.sendEmptyMessage(H.RECHECK_ALL);
307     }
308 
addRow(int stream, int iconRes, int iconMuteRes, boolean important)309     private void addRow(int stream, int iconRes, int iconMuteRes, boolean important) {
310         VolumeRow row = new VolumeRow();
311         initRow(row, stream, iconRes, iconMuteRes, important);
312         if (!mRows.isEmpty()) {
313             addSpacer(row);
314         }
315         mDialogContentView.addView(row.view, mDialogContentView.getChildCount() - 2);
316         mRows.add(row);
317     }
318 
addExistingRows()319     private void addExistingRows() {
320         int N = mRows.size();
321         for (int i = 0; i < N; i++) {
322             final VolumeRow row = mRows.get(i);
323             initRow(row, row.stream, row.iconRes, row.iconMuteRes, row.important);
324             if (i > 0) {
325                 addSpacer(row);
326             }
327             mDialogContentView.addView(row.view, mDialogContentView.getChildCount() - 2);
328         }
329     }
330 
addSpacer(VolumeRow row)331     private void addSpacer(VolumeRow row) {
332         final View v = new View(mContext);
333         v.setId(android.R.id.background);
334         final int h = mContext.getResources()
335                 .getDimensionPixelSize(R.dimen.volume_slider_interspacing);
336         final LinearLayout.LayoutParams lp =
337                 new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, h);
338         mDialogContentView.addView(v, mDialogContentView.getChildCount() - 2, lp);
339         row.space = v;
340     }
341 
isAttached()342     private boolean isAttached() {
343         return mDialogContentView != null && mDialogContentView.isAttachedToWindow();
344     }
345 
getActiveRow()346     private VolumeRow getActiveRow() {
347         for (VolumeRow row : mRows) {
348             if (row.stream == mActiveStream) {
349                 return row;
350             }
351         }
352         return mRows.get(0);
353     }
354 
findRow(int stream)355     private VolumeRow findRow(int stream) {
356         for (VolumeRow row : mRows) {
357             if (row.stream == stream) return row;
358         }
359         return null;
360     }
361 
dump(PrintWriter writer)362     public void dump(PrintWriter writer) {
363         writer.println(VolumeDialog.class.getSimpleName() + " state:");
364         writer.print("  mShowing: "); writer.println(mShowing);
365         writer.print("  mExpanded: "); writer.println(mExpanded);
366         writer.print("  mExpandButtonAnimationRunning: ");
367         writer.println(mExpandButtonAnimationRunning);
368         writer.print("  mActiveStream: "); writer.println(mActiveStream);
369         writer.print("  mDynamic: "); writer.println(mDynamic);
370         writer.print("  mShowHeaders: "); writer.println(mShowHeaders);
371         writer.print("  mAutomute: "); writer.println(mAutomute);
372         writer.print("  mSilentMode: "); writer.println(mSilentMode);
373         writer.print("  mCollapseTime: "); writer.println(mCollapseTime);
374         writer.print("  mAccessibility.mFeedbackEnabled: ");
375         writer.println(mAccessibility.mFeedbackEnabled);
376     }
377 
getImpliedLevel(SeekBar seekBar, int progress)378     private static int getImpliedLevel(SeekBar seekBar, int progress) {
379         final int m = seekBar.getMax();
380         final int n = m / 100 - 1;
381         final int level = progress == 0 ? 0
382                 : progress == m ? (m / 100) : (1 + (int)((progress / (float) m) * n));
383         return level;
384     }
385 
386     @SuppressLint("InflateParams")
initRow(final VolumeRow row, final int stream, int iconRes, int iconMuteRes, boolean important)387     private void initRow(final VolumeRow row, final int stream, int iconRes, int iconMuteRes,
388             boolean important) {
389         row.stream = stream;
390         row.iconRes = iconRes;
391         row.iconMuteRes = iconMuteRes;
392         row.important = important;
393         row.view = mDialog.getLayoutInflater().inflate(R.layout.volume_dialog_row, null);
394         row.view.setTag(row);
395         row.header = (TextView) row.view.findViewById(R.id.volume_row_header);
396         mSpTexts.add(row.header);
397         row.slider = (SeekBar) row.view.findViewById(R.id.volume_row_slider);
398         row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
399         row.anim = null;
400 
401         // forward events above the slider into the slider
402         row.view.setOnTouchListener(new OnTouchListener() {
403             private final Rect mSliderHitRect = new Rect();
404             private boolean mDragging;
405 
406             @SuppressLint("ClickableViewAccessibility")
407             @Override
408             public boolean onTouch(View v, MotionEvent event) {
409                 row.slider.getHitRect(mSliderHitRect);
410                 if (!mDragging && event.getActionMasked() == MotionEvent.ACTION_DOWN
411                         && event.getY() < mSliderHitRect.top) {
412                     mDragging = true;
413                 }
414                 if (mDragging) {
415                     event.offsetLocation(-mSliderHitRect.left, -mSliderHitRect.top);
416                     row.slider.dispatchTouchEvent(event);
417                     if (event.getActionMasked() == MotionEvent.ACTION_UP
418                             || event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
419                         mDragging = false;
420                     }
421                     return true;
422                 }
423                 return false;
424             }
425         });
426         row.icon = (ImageButton) row.view.findViewById(R.id.volume_row_icon);
427         row.icon.setImageResource(iconRes);
428         row.icon.setOnClickListener(new OnClickListener() {
429             @Override
430             public void onClick(View v) {
431                 Events.writeEvent(mContext, Events.EVENT_ICON_CLICK, row.stream, row.iconState);
432                 mController.setActiveStream(row.stream);
433                 if (row.stream == AudioManager.STREAM_RING) {
434                     final boolean hasVibrator = mController.hasVibrator();
435                     if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) {
436                         if (hasVibrator) {
437                             mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false);
438                         } else {
439                             final boolean wasZero = row.ss.level == 0;
440                             mController.setStreamVolume(stream, wasZero ? row.lastAudibleLevel : 0);
441                         }
442                     } else {
443                         mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
444                         if (row.ss.level == 0) {
445                             mController.setStreamVolume(stream, 1);
446                         }
447                     }
448                 } else {
449                     final boolean vmute = row.ss.level == row.ss.levelMin;
450                     mController.setStreamVolume(stream,
451                             vmute ? row.lastAudibleLevel : row.ss.levelMin);
452                 }
453                 row.userAttempt = 0;  // reset the grace period, slider should update immediately
454             }
455         });
456     }
457 
destroy()458     public void destroy() {
459         mController.removeCallback(mControllerCallbackH);
460     }
461 
show(int reason)462     public void show(int reason) {
463         mHandler.obtainMessage(H.SHOW, reason, 0).sendToTarget();
464     }
465 
dismiss(int reason)466     public void dismiss(int reason) {
467         mHandler.obtainMessage(H.DISMISS, reason, 0).sendToTarget();
468     }
469 
showH(int reason)470     private void showH(int reason) {
471         if (D.BUG) Log.d(TAG, "showH r=" + Events.DISMISS_REASONS[reason]);
472         mHandler.removeMessages(H.SHOW);
473         mHandler.removeMessages(H.DISMISS);
474         rescheduleTimeoutH();
475         if (mShowing) return;
476         mShowing = true;
477         mMotion.startShow();
478         Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
479         mController.notifyVisible(true);
480     }
481 
rescheduleTimeoutH()482     protected void rescheduleTimeoutH() {
483         mHandler.removeMessages(H.DISMISS);
484         final int timeout = computeTimeoutH();
485         mHandler.sendMessageDelayed(mHandler
486                 .obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT, 0), timeout);
487         if (D.BUG) Log.d(TAG, "rescheduleTimeout " + timeout + " " + Debug.getCaller());
488         mController.userActivity();
489     }
490 
computeTimeoutH()491     private int computeTimeoutH() {
492         if (mAccessibility.mFeedbackEnabled) return 20000;
493         if (mHovering) return 16000;
494         if (mSafetyWarning != null) return 5000;
495         if (mExpanded || mExpandButtonAnimationRunning) return 5000;
496         if (mActiveStream == AudioManager.STREAM_MUSIC) return 1500;
497         return 3000;
498     }
499 
dismissH(int reason)500     protected void dismissH(int reason) {
501         if (mMotion.isAnimating()) {
502             return;
503         }
504         mHandler.removeMessages(H.DISMISS);
505         mHandler.removeMessages(H.SHOW);
506         if (!mShowing) return;
507         mShowing = false;
508         mMotion.startDismiss(new Runnable() {
509             @Override
510             public void run() {
511                 setExpandedH(false);
512             }
513         });
514         if (mAccessibilityMgr.isEnabled()) {
515             AccessibilityEvent event =
516                     AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
517             event.setPackageName(mContext.getPackageName());
518             event.setClassName(CustomDialog.class.getSuperclass().getName());
519             event.getText().add(mContext.getString(
520                     R.string.volume_dialog_accessibility_dismissed_message));
521             mAccessibilityMgr.sendAccessibilityEvent(event);
522         }
523         Events.writeEvent(mContext, Events.EVENT_DISMISS_DIALOG, reason);
524         mController.notifyVisible(false);
525         synchronized (mSafetyWarningLock) {
526             if (mSafetyWarning != null) {
527                 if (D.BUG) Log.d(TAG, "SafetyWarning dismissed");
528                 mSafetyWarning.dismiss();
529             }
530         }
531     }
532 
updateDialogBottomMarginH()533     private void updateDialogBottomMarginH() {
534         final long diff = System.currentTimeMillis() - mCollapseTime;
535         final boolean collapsing = mCollapseTime != 0 && diff < getConservativeCollapseDuration();
536         final ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) mDialogView.getLayoutParams();
537         final int bottomMargin = collapsing ? mDialogContentView.getHeight() :
538                 mContext.getResources().getDimensionPixelSize(R.dimen.volume_dialog_margin_bottom);
539         if (bottomMargin != mlp.bottomMargin) {
540             if (D.BUG) Log.d(TAG, "bottomMargin " + mlp.bottomMargin + " -> " + bottomMargin);
541             mlp.bottomMargin = bottomMargin;
542             mDialogView.setLayoutParams(mlp);
543         }
544     }
545 
546     private long getConservativeCollapseDuration() {
547         return mExpandButtonAnimationDuration * 3;
548     }
549 
550     private void prepareForCollapse() {
551         mHandler.removeMessages(H.UPDATE_BOTTOM_MARGIN);
552         mCollapseTime = System.currentTimeMillis();
553         updateDialogBottomMarginH();
554         mHandler.sendEmptyMessageDelayed(H.UPDATE_BOTTOM_MARGIN, getConservativeCollapseDuration());
555     }
556 
557     private void setExpandedH(boolean expanded) {
558         if (mExpanded == expanded) return;
559         mExpanded = expanded;
560         mExpandButtonAnimationRunning = isAttached();
561         if (D.BUG) Log.d(TAG, "setExpandedH " + expanded);
562         if (!mExpanded && mExpandButtonAnimationRunning) {
563             prepareForCollapse();
564         }
565         updateRowsH();
566         if (mExpandButtonAnimationRunning) {
567             final Drawable d = mExpandButton.getDrawable();
568             if (d instanceof AnimatedVectorDrawable) {
569                 // workaround to reset drawable
570                 final AnimatedVectorDrawable avd = (AnimatedVectorDrawable) d.getConstantState()
571                         .newDrawable();
572                 mExpandButton.setImageDrawable(avd);
573                 avd.start();
574                 mHandler.postDelayed(new Runnable() {
575                     @Override
576                     public void run() {
577                         mExpandButtonAnimationRunning = false;
578                         updateExpandButtonH();
579                         rescheduleTimeoutH();
580                     }
581                 }, mExpandButtonAnimationDuration);
582             }
583         }
584         rescheduleTimeoutH();
585     }
586 
587     private void updateExpandButtonH() {
588         if (D.BUG) Log.d(TAG, "updateExpandButtonH");
589         mExpandButton.setClickable(!mExpandButtonAnimationRunning);
590         if (mExpandButtonAnimationRunning && isAttached()) return;
591         final int res = mExpanded ? R.drawable.ic_volume_collapse_animation
592                 : R.drawable.ic_volume_expand_animation;
593         if (hasTouchFeature()) {
594             mExpandButton.setImageResource(res);
595         } else {
596             // if there is no touch feature, show the volume ringer instead
597             mExpandButton.setImageResource(R.drawable.ic_volume_ringer);
598             mExpandButton.setBackgroundResource(0);  // remove gray background emphasis
599         }
600         mExpandButton.setContentDescription(mContext.getString(mExpanded ?
601                 R.string.accessibility_volume_collapse : R.string.accessibility_volume_expand));
602     }
603 
604     private boolean isVisibleH(VolumeRow row, boolean isActive) {
605         return mExpanded && row.view.getVisibility() == View.VISIBLE
606                 || (mExpanded && (row.important || isActive))
607                 || !mExpanded && isActive;
608     }
609 
610     private void updateRowsH() {
611         if (D.BUG) Log.d(TAG, "updateRowsH");
612         final VolumeRow activeRow = getActiveRow();
613         updateFooterH();
614         updateExpandButtonH();
615         if (!mShowing) {
616             trimObsoleteH();
617         }
618         // apply changes to all rows
619         for (VolumeRow row : mRows) {
620             final boolean isActive = row == activeRow;
621             final boolean visible = isVisibleH(row, isActive);
622             Util.setVisOrGone(row.view, visible);
623             Util.setVisOrGone(row.space, visible && mExpanded);
624             updateVolumeRowHeaderVisibleH(row);
625             row.header.setAlpha(mExpanded && isActive ? 1 : 0.5f);
626             updateVolumeRowSliderTintH(row, isActive);
627         }
628     }
629 
630     private void trimObsoleteH() {
631         if (D.BUG) Log.d(TAG, "trimObsoleteH");
632         for (int i = mRows.size() -1; i >= 0; i--) {
633             final VolumeRow row = mRows.get(i);
634             if (row.ss == null || !row.ss.dynamic) continue;
635             if (!mDynamic.get(row.stream)) {
636                 mRows.remove(i);
637                 mDialogContentView.removeView(row.view);
638                 mDialogContentView.removeView(row.space);
639             }
640         }
641     }
642 
onStateChangedH(State state)643     private void onStateChangedH(State state) {
644         final boolean animating = mMotion.isAnimating();
645         if (D.BUG) Log.d(TAG, "onStateChangedH animating=" + animating);
646         mState = state;
647         if (animating) {
648             mPendingStateChanged = true;
649             return;
650         }
651         mDynamic.clear();
652         // add any new dynamic rows
653         for (int i = 0; i < state.states.size(); i++) {
654             final int stream = state.states.keyAt(i);
655             final StreamState ss = state.states.valueAt(i);
656             if (!ss.dynamic) continue;
657             mDynamic.put(stream, true);
658             if (findRow(stream) == null) {
659                 addRow(stream, R.drawable.ic_volume_remote, R.drawable.ic_volume_remote_mute, true);
660             }
661         }
662 
663         if (mActiveStream != state.activeStream) {
664             mActiveStream = state.activeStream;
665             updateRowsH();
666             rescheduleTimeoutH();
667         }
668         for (VolumeRow row : mRows) {
669             updateVolumeRowH(row);
670         }
671         updateFooterH();
672     }
673 
updateFooterH()674     private void updateFooterH() {
675         if (D.BUG) Log.d(TAG, "updateFooterH");
676         final boolean wasVisible = mZenFooter.getVisibility() == View.VISIBLE;
677         final boolean visible = mState.zenMode != Global.ZEN_MODE_OFF
678                 && (mAudioManager.isStreamAffectedByRingerMode(mActiveStream) || mExpanded)
679                 && !mZenPanel.isEditing();
680         if (wasVisible != visible && !visible) {
681             prepareForCollapse();
682         }
683         Util.setVisOrGone(mZenFooter, visible);
684         mZenFooter.update();
685 
686         final boolean fullWasVisible = mZenPanel.getVisibility() == View.VISIBLE;
687         final boolean fullVisible = mShowFullZen && !visible;
688         if (fullWasVisible != fullVisible && !fullVisible) {
689             prepareForCollapse();
690         }
691         Util.setVisOrGone(mZenPanel, fullVisible);
692         if (fullVisible) {
693             mZenPanel.setZenState(mState.zenMode);
694             mZenPanel.setDoneListener(new OnClickListener() {
695                 @Override
696                 public void onClick(View v) {
697                     prepareForCollapse();
698                     mHandler.sendEmptyMessage(H.UPDATE_FOOTER);
699                 }
700             });
701         }
702     }
703 
updateVolumeRowH(VolumeRow row)704     private void updateVolumeRowH(VolumeRow row) {
705         if (D.BUG) Log.d(TAG, "updateVolumeRowH s=" + row.stream);
706         if (mState == null) return;
707         final StreamState ss = mState.states.get(row.stream);
708         if (ss == null) return;
709         row.ss = ss;
710         if (ss.level > 0) {
711             row.lastAudibleLevel = ss.level;
712         }
713         if (ss.level == row.requestedLevel) {
714             row.requestedLevel = -1;
715         }
716         final boolean isRingStream = row.stream == AudioManager.STREAM_RING;
717         final boolean isSystemStream = row.stream == AudioManager.STREAM_SYSTEM;
718         final boolean isAlarmStream = row.stream == AudioManager.STREAM_ALARM;
719         final boolean isMusicStream = row.stream == AudioManager.STREAM_MUSIC;
720         final boolean isRingVibrate = isRingStream
721                 && mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE;
722         final boolean isRingSilent = isRingStream
723                 && mState.ringerModeInternal == AudioManager.RINGER_MODE_SILENT;
724         final boolean isZenAlarms = mState.zenMode == Global.ZEN_MODE_ALARMS;
725         final boolean isZenNone = mState.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
726         final boolean isZenPriority = mState.zenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
727         final boolean isRingZenNone = (isRingStream || isSystemStream) && isZenNone;
728         final boolean isRingLimited = isRingStream && isZenPriority;
729         final boolean zenMuted = isZenAlarms ? (isRingStream || isSystemStream)
730                 : isZenNone ? (isRingStream || isSystemStream || isAlarmStream || isMusicStream)
731                 : false;
732 
733         // update slider max
734         final int max = ss.levelMax * 100;
735         if (max != row.slider.getMax()) {
736             row.slider.setMax(max);
737         }
738 
739         // update header visible
740         updateVolumeRowHeaderVisibleH(row);
741 
742         // update header text
743         String text = ss.name;
744         if (mShowHeaders) {
745             if (isRingZenNone) {
746                 text = mContext.getString(R.string.volume_stream_muted_dnd, ss.name);
747             } else if (isRingVibrate && isRingLimited) {
748                 text = mContext.getString(R.string.volume_stream_vibrate_dnd, ss.name);
749             } else if (isRingVibrate) {
750                 text = mContext.getString(R.string.volume_stream_vibrate, ss.name);
751             } else if (ss.muted || mAutomute && ss.level == 0) {
752                 text = mContext.getString(R.string.volume_stream_muted, ss.name);
753             } else if (isRingLimited) {
754                 text = mContext.getString(R.string.volume_stream_limited_dnd, ss.name);
755             }
756         }
757         Util.setText(row.header, text);
758 
759         // update icon
760         final boolean iconEnabled = (mAutomute || ss.muteSupported) && !zenMuted;
761         row.icon.setEnabled(iconEnabled);
762         row.icon.setAlpha(iconEnabled ? 1 : 0.5f);
763         final int iconRes =
764                 isRingVibrate ? R.drawable.ic_volume_ringer_vibrate
765                 : isRingSilent || zenMuted ? row.cachedIconRes
766                 : ss.routedToBluetooth ?
767                         (ss.muted ? R.drawable.ic_volume_media_bt_mute
768                                 : R.drawable.ic_volume_media_bt)
769                 : mAutomute && ss.level == 0 ? row.iconMuteRes
770                 : (ss.muted ? row.iconMuteRes : row.iconRes);
771         if (iconRes != row.cachedIconRes) {
772             if (row.cachedIconRes != 0 && isRingVibrate) {
773                 mController.vibrate();
774             }
775             row.cachedIconRes = iconRes;
776             row.icon.setImageResource(iconRes);
777         }
778         row.iconState =
779                 iconRes == R.drawable.ic_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE
780                 : (iconRes == R.drawable.ic_volume_media_bt_mute || iconRes == row.iconMuteRes)
781                         ? Events.ICON_STATE_MUTE
782                 : (iconRes == R.drawable.ic_volume_media_bt || iconRes == row.iconRes)
783                         ? Events.ICON_STATE_UNMUTE
784                 : Events.ICON_STATE_UNKNOWN;
785         if (iconEnabled) {
786             if (isRingStream) {
787                 if (isRingVibrate) {
788                     row.icon.setContentDescription(mContext.getString(
789                             R.string.volume_stream_content_description_unmute,
790                             ss.name));
791                 } else {
792                     if (mController.hasVibrator()) {
793                         row.icon.setContentDescription(mContext.getString(
794                                 R.string.volume_stream_content_description_vibrate,
795                                 ss.name));
796                     } else {
797                         row.icon.setContentDescription(mContext.getString(
798                                 R.string.volume_stream_content_description_mute,
799                                 ss.name));
800                     }
801                 }
802             } else {
803                 if (ss.muted || mAutomute && ss.level == 0) {
804                    row.icon.setContentDescription(mContext.getString(
805                            R.string.volume_stream_content_description_unmute,
806                            ss.name));
807                 } else {
808                     row.icon.setContentDescription(mContext.getString(
809                             R.string.volume_stream_content_description_mute,
810                             ss.name));
811                 }
812             }
813         } else {
814             row.icon.setContentDescription(ss.name);
815         }
816 
817         // update slider
818         final boolean enableSlider = !zenMuted;
819         final int vlevel = row.ss.muted && (isRingVibrate || !isRingStream && !zenMuted) ? 0
820                 : row.ss.level;
821         updateVolumeRowSliderH(row, enableSlider, vlevel);
822     }
823 
updateVolumeRowHeaderVisibleH(VolumeRow row)824     private void updateVolumeRowHeaderVisibleH(VolumeRow row) {
825         final boolean dynamic = row.ss != null && row.ss.dynamic;
826         final boolean showHeaders = mShowHeaders || mExpanded && dynamic;
827         if (row.cachedShowHeaders != showHeaders) {
828             row.cachedShowHeaders = showHeaders;
829             Util.setVisOrGone(row.header, showHeaders);
830         }
831     }
832 
updateVolumeRowSliderTintH(VolumeRow row, boolean isActive)833     private void updateVolumeRowSliderTintH(VolumeRow row, boolean isActive) {
834         if (isActive && mExpanded) {
835             row.slider.requestFocus();
836         }
837         final ColorStateList tint = isActive && row.slider.isEnabled() ? mActiveSliderTint
838                 : mInactiveSliderTint;
839         if (tint == row.cachedSliderTint) return;
840         row.cachedSliderTint = tint;
841         row.slider.setProgressTintList(tint);
842         row.slider.setThumbTintList(tint);
843     }
844 
updateVolumeRowSliderH(VolumeRow row, boolean enable, int vlevel)845     private void updateVolumeRowSliderH(VolumeRow row, boolean enable, int vlevel) {
846         row.slider.setEnabled(enable);
847         updateVolumeRowSliderTintH(row, row.stream == mActiveStream);
848         if (row.tracking) {
849             return;  // don't update if user is sliding
850         }
851         final int progress = row.slider.getProgress();
852         final int level = getImpliedLevel(row.slider, progress);
853         final boolean rowVisible = row.view.getVisibility() == View.VISIBLE;
854         final boolean inGracePeriod = (SystemClock.uptimeMillis() - row.userAttempt)
855                 < USER_ATTEMPT_GRACE_PERIOD;
856         mHandler.removeMessages(H.RECHECK, row);
857         if (mShowing && rowVisible && inGracePeriod) {
858             if (D.BUG) Log.d(TAG, "inGracePeriod");
859             mHandler.sendMessageAtTime(mHandler.obtainMessage(H.RECHECK, row),
860                     row.userAttempt + USER_ATTEMPT_GRACE_PERIOD);
861             return;  // don't update if visible and in grace period
862         }
863         if (vlevel == level) {
864             if (mShowing && rowVisible) {
865                 return;  // don't clamp if visible
866             }
867         }
868         final int newProgress = vlevel * 100;
869         if (progress != newProgress) {
870             if (mShowing && rowVisible) {
871                 // animate!
872                 if (row.anim != null && row.anim.isRunning()
873                         && row.animTargetProgress == newProgress) {
874                     return;  // already animating to the target progress
875                 }
876                 // start/update animation
877                 if (row.anim == null) {
878                     row.anim = ObjectAnimator.ofInt(row.slider, "progress", progress, newProgress);
879                     row.anim.setInterpolator(new DecelerateInterpolator());
880                 } else {
881                     row.anim.cancel();
882                     row.anim.setIntValues(progress, newProgress);
883                 }
884                 row.animTargetProgress = newProgress;
885                 row.anim.setDuration(UPDATE_ANIMATION_DURATION);
886                 row.anim.start();
887             } else {
888                 // update slider directly to clamped value
889                 if (row.anim != null) {
890                     row.anim.cancel();
891                 }
892                 row.slider.setProgress(newProgress);
893             }
894         }
895     }
896 
897     private void recheckH(VolumeRow row) {
898         if (row == null) {
899             if (D.BUG) Log.d(TAG, "recheckH ALL");
900             trimObsoleteH();
901             for (VolumeRow r : mRows) {
902                 updateVolumeRowH(r);
903             }
904         } else {
905             if (D.BUG) Log.d(TAG, "recheckH " + row.stream);
906             updateVolumeRowH(row);
907         }
908     }
909 
910     private void setStreamImportantH(int stream, boolean important) {
911         for (VolumeRow row : mRows) {
912             if (row.stream == stream) {
913                 row.important = important;
914                 return;
915             }
916         }
917     }
918 
919     private void showSafetyWarningH(int flags) {
920         if ((flags & (AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_SHOW_UI_WARNINGS)) != 0
921                 || mShowing) {
922             synchronized (mSafetyWarningLock) {
923                 if (mSafetyWarning != null) {
924                     return;
925                 }
926                 mSafetyWarning = new SafetyWarningDialog(mContext, mController.getAudioManager()) {
927                     @Override
928                     protected void cleanUp() {
929                         synchronized (mSafetyWarningLock) {
930                             mSafetyWarning = null;
931                         }
932                         recheckH(null);
933                     }
934                 };
935                 mSafetyWarning.show();
936             }
937             recheckH(null);
938         }
939         rescheduleTimeoutH();
940     }
941 
942     private boolean hasTouchFeature() {
943         final PackageManager pm = mContext.getPackageManager();
944         return pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN);
945     }
946 
947     private final VolumeDialogController.Callbacks mControllerCallbackH
948             = new VolumeDialogController.Callbacks() {
949         @Override
950         public void onShowRequested(int reason) {
951             showH(reason);
952         }
953 
954         @Override
955         public void onDismissRequested(int reason) {
956             dismissH(reason);
957         }
958 
959         @Override
960         public void onScreenOff() {
961             dismissH(Events.DISMISS_REASON_SCREEN_OFF);
962         }
963 
964         @Override
965         public void onStateChanged(State state) {
966             onStateChangedH(state);
967         }
968 
969         @Override
970         public void onLayoutDirectionChanged(int layoutDirection) {
971             mDialogView.setLayoutDirection(layoutDirection);
972         }
973 
974         @Override
975         public void onConfigurationChanged() {
976             Configuration newConfig = mContext.getResources().getConfiguration();
977             final int density = newConfig.densityDpi;
978             if (density != mDensity) {
979                 mDialog.dismiss();
980                 mZenFooter.cleanup();
981                 initDialog();
982             }
983             updateWindowWidthH();
984             mSpTexts.update();
985             mZenFooter.onConfigurationChanged();
986         }
987 
988         @Override
989         public void onShowVibrateHint() {
990             if (mSilentMode) {
991                 mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false);
992             }
993         }
994 
995         @Override
996         public void onShowSilentHint() {
997             if (mSilentMode) {
998                 mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
999             }
1000         }
1001 
1002         @Override
1003         public void onShowSafetyWarning(int flags) {
1004             showSafetyWarningH(flags);
1005         }
1006     };
1007 
1008     private final ZenModePanel.Callback mZenPanelCallback = new ZenModePanel.Callback() {
1009         @Override
1010         public void onPrioritySettings() {
1011             mCallback.onZenPrioritySettingsClicked();
1012         }
1013 
1014         @Override
1015         public void onInteraction() {
1016             mHandler.sendEmptyMessage(H.RESCHEDULE_TIMEOUT);
1017         }
1018 
1019         @Override
1020         public void onExpanded(boolean expanded) {
1021             // noop.
1022         }
1023     };
1024 
1025     private final OnClickListener mClickExpand = new OnClickListener() {
1026         @Override
1027         public void onClick(View v) {
1028             if (mExpandButtonAnimationRunning) return;
1029             final boolean newExpand = !mExpanded;
1030             Events.writeEvent(mContext, Events.EVENT_EXPAND, newExpand);
1031             setExpandedH(newExpand);
1032         }
1033     };
1034 
1035     private final class H extends Handler {
1036         private static final int SHOW = 1;
1037         private static final int DISMISS = 2;
1038         private static final int RECHECK = 3;
1039         private static final int RECHECK_ALL = 4;
1040         private static final int SET_STREAM_IMPORTANT = 5;
1041         private static final int RESCHEDULE_TIMEOUT = 6;
1042         private static final int STATE_CHANGED = 7;
1043         private static final int UPDATE_BOTTOM_MARGIN = 8;
1044         private static final int UPDATE_FOOTER = 9;
1045 
1046         public H() {
1047             super(Looper.getMainLooper());
1048         }
1049 
1050         @Override
1051         public void handleMessage(Message msg) {
1052             switch (msg.what) {
1053                 case SHOW: showH(msg.arg1); break;
1054                 case DISMISS: dismissH(msg.arg1); break;
1055                 case RECHECK: recheckH((VolumeRow) msg.obj); break;
1056                 case RECHECK_ALL: recheckH(null); break;
1057                 case SET_STREAM_IMPORTANT: setStreamImportantH(msg.arg1, msg.arg2 != 0); break;
1058                 case RESCHEDULE_TIMEOUT: rescheduleTimeoutH(); break;
1059                 case STATE_CHANGED: onStateChangedH(mState); break;
1060                 case UPDATE_BOTTOM_MARGIN: updateDialogBottomMarginH(); break;
1061                 case UPDATE_FOOTER: updateFooterH(); break;
1062             }
1063         }
1064     }
1065 
1066     private final class CustomDialog extends Dialog {
1067         public CustomDialog(Context context) {
1068             super(context);
1069         }
1070 
1071         @Override
1072         public boolean dispatchTouchEvent(MotionEvent ev) {
1073             rescheduleTimeoutH();
1074             return super.dispatchTouchEvent(ev);
1075         }
1076 
1077         @Override
1078         protected void onStop() {
1079             super.onStop();
1080             final boolean animating = mMotion.isAnimating();
1081             if (D.BUG) Log.d(TAG, "onStop animating=" + animating);
1082             if (animating) {
1083                 mPendingRecheckAll = true;
1084                 return;
1085             }
1086             mHandler.sendEmptyMessage(H.RECHECK_ALL);
1087         }
1088 
1089         @Override
1090         public boolean onTouchEvent(MotionEvent event) {
1091             if (isShowing()) {
1092                 if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
1093                     dismissH(Events.DISMISS_REASON_TOUCH_OUTSIDE);
1094                     return true;
1095                 }
1096             }
1097             return false;
1098         }
1099 
1100         @Override
1101         public boolean dispatchPopulateAccessibilityEvent(@NonNull AccessibilityEvent event) {
1102             event.setClassName(getClass().getSuperclass().getName());
1103             event.setPackageName(mContext.getPackageName());
1104 
1105             ViewGroup.LayoutParams params = getWindow().getAttributes();
1106             boolean isFullScreen = (params.width == ViewGroup.LayoutParams.MATCH_PARENT) &&
1107                     (params.height == ViewGroup.LayoutParams.MATCH_PARENT);
1108             event.setFullScreen(isFullScreen);
1109 
1110             if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
1111                 if (mShowing) {
1112                     event.getText().add(mContext.getString(
1113                             R.string.volume_dialog_accessibility_shown_message,
1114                             getActiveRow().ss.name));
1115                     return true;
1116                 }
1117             }
1118             return false;
1119         }
1120     }
1121 
1122     private final class VolumeSeekBarChangeListener implements OnSeekBarChangeListener {
1123         private final VolumeRow mRow;
1124 
1125         private VolumeSeekBarChangeListener(VolumeRow row) {
1126             mRow = row;
1127         }
1128 
1129         @Override
1130         public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
1131             if (mRow.ss == null) return;
1132             if (D.BUG) Log.d(TAG, AudioSystem.streamToString(mRow.stream)
1133                     + " onProgressChanged " + progress + " fromUser=" + fromUser);
1134             if (!fromUser) return;
1135             if (mRow.ss.levelMin > 0) {
1136                 final int minProgress = mRow.ss.levelMin * 100;
1137                 if (progress < minProgress) {
1138                     seekBar.setProgress(minProgress);
1139                     progress = minProgress;
1140                 }
1141             }
1142             final int userLevel = getImpliedLevel(seekBar, progress);
1143             if (mRow.ss.level != userLevel || mRow.ss.muted && userLevel > 0) {
1144                 mRow.userAttempt = SystemClock.uptimeMillis();
1145                 if (mRow.requestedLevel != userLevel) {
1146                     mController.setStreamVolume(mRow.stream, userLevel);
1147                     mRow.requestedLevel = userLevel;
1148                     Events.writeEvent(mContext, Events.EVENT_TOUCH_LEVEL_CHANGED, mRow.stream,
1149                             userLevel);
1150                 }
1151             }
1152         }
1153 
1154         @Override
onStartTrackingTouch(SeekBar seekBar)1155         public void onStartTrackingTouch(SeekBar seekBar) {
1156             if (D.BUG) Log.d(TAG, "onStartTrackingTouch"+ " " + mRow.stream);
1157             mController.setActiveStream(mRow.stream);
1158             mRow.tracking = true;
1159         }
1160 
1161         @Override
onStopTrackingTouch(SeekBar seekBar)1162         public void onStopTrackingTouch(SeekBar seekBar) {
1163             if (D.BUG) Log.d(TAG, "onStopTrackingTouch"+ " " + mRow.stream);
1164             mRow.tracking = false;
1165             mRow.userAttempt = SystemClock.uptimeMillis();
1166             final int userLevel = getImpliedLevel(seekBar, seekBar.getProgress());
1167             Events.writeEvent(mContext, Events.EVENT_TOUCH_LEVEL_DONE, mRow.stream, userLevel);
1168             if (mRow.ss.level != userLevel) {
1169                 mHandler.sendMessageDelayed(mHandler.obtainMessage(H.RECHECK, mRow),
1170                         USER_ATTEMPT_GRACE_PERIOD);
1171             }
1172         }
1173     }
1174 
1175     private final class Accessibility extends AccessibilityDelegate {
1176         private boolean mFeedbackEnabled;
1177 
init()1178         public void init() {
1179             mDialogView.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
1180                 @Override
1181                 public void onViewDetachedFromWindow(View v) {
1182                     if (D.BUG) Log.d(TAG, "onViewDetachedFromWindow");
1183                 }
1184 
1185                 @Override
1186                 public void onViewAttachedToWindow(View v) {
1187                     if (D.BUG) Log.d(TAG, "onViewAttachedToWindow");
1188                     updateFeedbackEnabled();
1189                 }
1190             });
1191             mDialogView.setAccessibilityDelegate(this);
1192             mAccessibilityMgr.addAccessibilityStateChangeListener(
1193                     new AccessibilityStateChangeListener() {
1194                         @Override
1195                         public void onAccessibilityStateChanged(boolean enabled) {
1196                             updateFeedbackEnabled();
1197                         }
1198                     });
1199             updateFeedbackEnabled();
1200         }
1201 
1202         @Override
onRequestSendAccessibilityEvent(ViewGroup host, View child, AccessibilityEvent event)1203         public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
1204                 AccessibilityEvent event) {
1205             rescheduleTimeoutH();
1206             return super.onRequestSendAccessibilityEvent(host, child, event);
1207         }
1208 
updateFeedbackEnabled()1209         private void updateFeedbackEnabled() {
1210             mFeedbackEnabled = computeFeedbackEnabled();
1211         }
1212 
computeFeedbackEnabled()1213         private boolean computeFeedbackEnabled() {
1214             // are there any enabled non-generic a11y services?
1215             final List<AccessibilityServiceInfo> services =
1216                     mAccessibilityMgr.getEnabledAccessibilityServiceList(FEEDBACK_ALL_MASK);
1217             for (AccessibilityServiceInfo asi : services) {
1218                 if (asi.feedbackType != 0 && asi.feedbackType != FEEDBACK_GENERIC) {
1219                     return true;
1220                 }
1221             }
1222             return false;
1223         }
1224     }
1225 
1226     private static class VolumeRow {
1227         private View view;
1228         private View space;
1229         private TextView header;
1230         private ImageButton icon;
1231         private SeekBar slider;
1232         private int stream;
1233         private StreamState ss;
1234         private long userAttempt;  // last user-driven slider change
1235         private boolean tracking;  // tracking slider touch
1236         private int requestedLevel = -1;  // pending user-requested level via progress changed
1237         private int iconRes;
1238         private int iconMuteRes;
1239         private boolean important;
1240         private int cachedIconRes;
1241         private ColorStateList cachedSliderTint;
1242         private int iconState;  // from Events
1243         private boolean cachedShowHeaders = VolumePrefs.DEFAULT_SHOW_HEADERS;
1244         private ObjectAnimator anim;  // slider progress animation for non-touch-related updates
1245         private int animTargetProgress;
1246         private int lastAudibleLevel = 1;
1247     }
1248 
1249     public interface Callback {
onZenSettingsClicked()1250         void onZenSettingsClicked();
onZenPrioritySettingsClicked()1251         void onZenPrioritySettingsClicked();
1252     }
1253 }
1254