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