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 static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
20 import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_GENERIC;
21 import static android.media.AudioManager.RINGER_MODE_NORMAL;
22 import static android.media.AudioManager.RINGER_MODE_SILENT;
23 import static android.media.AudioManager.RINGER_MODE_VIBRATE;
24 import static android.media.AudioManager.STREAM_ACCESSIBILITY;
25 import static android.media.AudioManager.STREAM_ALARM;
26 import static android.media.AudioManager.STREAM_MUSIC;
27 import static android.media.AudioManager.STREAM_RING;
28 import static android.media.AudioManager.STREAM_VOICE_CALL;
29 import static android.view.View.GONE;
30 import static android.view.View.VISIBLE;
31 
32 import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED;
33 
34 import android.accessibilityservice.AccessibilityServiceInfo;
35 import android.animation.ObjectAnimator;
36 import android.annotation.SuppressLint;
37 import android.app.Dialog;
38 import android.app.KeyguardManager;
39 import android.content.ContentResolver;
40 import android.content.Context;
41 import android.content.DialogInterface;
42 import android.content.Intent;
43 import android.content.res.ColorStateList;
44 import android.content.res.Configuration;
45 import android.content.res.Resources;
46 import android.content.res.TypedArray;
47 import android.graphics.Color;
48 import android.graphics.PixelFormat;
49 import android.graphics.PorterDuff;
50 import android.graphics.drawable.ColorDrawable;
51 import android.media.AudioManager;
52 import android.media.AudioSystem;
53 import android.os.Debug;
54 import android.os.Handler;
55 import android.os.Looper;
56 import android.os.Message;
57 import android.os.SystemClock;
58 import android.os.VibrationEffect;
59 import android.provider.Settings;
60 import android.provider.Settings.Global;
61 import android.text.InputFilter;
62 import android.util.Log;
63 import android.util.Slog;
64 import android.util.SparseBooleanArray;
65 import android.view.ContextThemeWrapper;
66 import android.view.Gravity;
67 import android.view.MotionEvent;
68 import android.view.View;
69 import android.view.View.AccessibilityDelegate;
70 import android.view.View.OnAttachStateChangeListener;
71 import android.view.ViewGroup;
72 import android.view.ViewPropertyAnimator;
73 import android.view.Window;
74 import android.view.WindowManager;
75 import android.view.accessibility.AccessibilityEvent;
76 import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
77 import android.view.accessibility.AccessibilityNodeInfo;
78 import android.view.animation.DecelerateInterpolator;
79 import android.widget.FrameLayout;
80 import android.widget.ImageButton;
81 import android.widget.SeekBar;
82 import android.widget.SeekBar.OnSeekBarChangeListener;
83 import android.widget.TextView;
84 import android.widget.Toast;
85 
86 import com.android.settingslib.Utils;
87 import com.android.systemui.Dependency;
88 import com.android.systemui.Prefs;
89 import com.android.systemui.R;
90 import com.android.systemui.plugins.ActivityStarter;
91 import com.android.systemui.plugins.VolumeDialog;
92 import com.android.systemui.plugins.VolumeDialogController;
93 import com.android.systemui.plugins.VolumeDialogController.State;
94 import com.android.systemui.plugins.VolumeDialogController.StreamState;
95 import com.android.systemui.statusbar.phone.StatusBar;
96 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
97 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
98 
99 import java.io.PrintWriter;
100 import java.util.ArrayList;
101 import java.util.List;
102 
103 /**
104  * Visual presentation of the volume dialog.
105  *
106  * A client of VolumeDialogControllerImpl and its state model.
107  *
108  * Methods ending in "H" must be called on the (ui) handler.
109  */
110 public class VolumeDialogImpl implements VolumeDialog {
111     private static final String TAG = Util.logTag(VolumeDialogImpl.class);
112 
113     private static final long USER_ATTEMPT_GRACE_PERIOD = 1000;
114     private static final int UPDATE_ANIMATION_DURATION = 80;
115 
116     private final Context mContext;
117     private final H mHandler = new H();
118     private final VolumeDialogController mController;
119     private final DeviceProvisionedController mDeviceProvisionedController;
120 
121     private Window mWindow;
122     private CustomDialog mDialog;
123     private ViewGroup mDialogView;
124     private ViewGroup mDialogRowsView;
125     private ViewGroup mRinger;
126     private ImageButton mRingerIcon;
127     private View mSettingsView;
128     private ImageButton mSettingsIcon;
129     private FrameLayout mZenIcon;
130     private final List<VolumeRow> mRows = new ArrayList<>();
131     private ConfigurableTexts mConfigurableTexts;
132     private final SparseBooleanArray mDynamic = new SparseBooleanArray();
133     private final KeyguardManager mKeyguard;
134     private final AccessibilityManagerWrapper mAccessibilityMgr;
135     private final Object mSafetyWarningLock = new Object();
136     private final Accessibility mAccessibility = new Accessibility();
137     private ColorStateList mActiveTint;
138     private int mActiveAlpha;
139     private ColorStateList mInactiveTint;
140     private int mInactiveAlpha;
141 
142     private boolean mShowing;
143     private boolean mShowA11yStream;
144 
145     private int mActiveStream;
146     private int mPrevActiveStream;
147     private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE;
148     private boolean mSilentMode = VolumePrefs.DEFAULT_ENABLE_SILENT_MODE;
149     private State mState;
150     private SafetyWarningDialog mSafetyWarning;
151     private boolean mHovering = false;
152 
VolumeDialogImpl(Context context)153     public VolumeDialogImpl(Context context) {
154         mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
155         mController = Dependency.get(VolumeDialogController.class);
156         mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
157         mAccessibilityMgr = Dependency.get(AccessibilityManagerWrapper.class);
158         mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
159     }
160 
init(int windowType, Callback callback)161     public void init(int windowType, Callback callback) {
162         initDialog();
163 
164         mAccessibility.init();
165 
166         mController.addCallback(mControllerCallbackH, mHandler);
167         mController.getState();
168     }
169 
170     @Override
destroy()171     public void destroy() {
172         mAccessibility.destroy();
173         mController.removeCallback(mControllerCallbackH);
174         mHandler.removeCallbacksAndMessages(null);
175     }
176 
initDialog()177     private void initDialog() {
178         mDialog = new CustomDialog(mContext);
179 
180         mConfigurableTexts = new ConfigurableTexts(mContext);
181         mHovering = false;
182         mShowing = false;
183         mWindow = mDialog.getWindow();
184         mWindow.requestFeature(Window.FEATURE_NO_TITLE);
185         mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
186         mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND
187                 | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
188         mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
189                 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
190                 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
191                 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
192                 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
193                 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
194         mWindow.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
195         mWindow.setWindowAnimations(com.android.internal.R.style.Animation_Toast);
196         final WindowManager.LayoutParams lp = mWindow.getAttributes();
197         lp.format = PixelFormat.TRANSLUCENT;
198         lp.setTitle(VolumeDialogImpl.class.getSimpleName());
199         lp.gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL;
200         lp.windowAnimations = -1;
201         mWindow.setAttributes(lp);
202         mWindow.setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
203 
204         mDialog.setCanceledOnTouchOutside(true);
205         mDialog.setContentView(R.layout.volume_dialog);
206         mDialog.setOnShowListener(dialog -> {
207             if (!isLandscape()) mDialogView.setTranslationX(mDialogView.getWidth() / 2);
208             mDialogView.setAlpha(0);
209             mDialogView.animate()
210                     .alpha(1)
211                     .translationX(0)
212                     .setDuration(300)
213                     .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator())
214                     .withEndAction(() -> {
215                         if (!Prefs.getBoolean(mContext, Prefs.Key.TOUCHED_RINGER_TOGGLE, false)) {
216                             mRingerIcon.postOnAnimationDelayed(mSinglePress, 1500);
217                         }
218                     })
219                     .start();
220         });
221         mDialogView = mDialog.findViewById(R.id.volume_dialog);
222         mDialogView.setOnHoverListener((v, event) -> {
223             int action = event.getActionMasked();
224             mHovering = (action == MotionEvent.ACTION_HOVER_ENTER)
225                     || (action == MotionEvent.ACTION_HOVER_MOVE);
226             rescheduleTimeoutH();
227             return true;
228         });
229 
230         mActiveTint = ColorStateList.valueOf(Utils.getColorAccent(mContext));
231         mActiveAlpha = Color.alpha(mActiveTint.getDefaultColor());
232         mInactiveTint = ColorStateList.valueOf(
233                 Utils.getColorAttr(mContext, android.R.attr.colorForeground));
234         mInactiveAlpha = getAlphaAttr(android.R.attr.secondaryContentAlpha);
235 
236         mDialogRowsView = mDialog.findViewById(R.id.volume_dialog_rows);
237         mRinger = mDialog.findViewById(R.id.ringer);
238         mRingerIcon = mRinger.findViewById(R.id.ringer_icon);
239         mZenIcon = mRinger.findViewById(R.id.dnd_icon);
240         mSettingsView = mDialog.findViewById(R.id.settings_container);
241         mSettingsIcon = mDialog.findViewById(R.id.settings);
242 
243         if (mRows.isEmpty()) {
244             if (!AudioSystem.isSingleVolume(mContext)) {
245                 addRow(STREAM_ACCESSIBILITY, R.drawable.ic_volume_accessibility,
246                         R.drawable.ic_volume_accessibility, true, false);
247             }
248             addRow(AudioManager.STREAM_MUSIC,
249                     R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true, true);
250             if (!AudioSystem.isSingleVolume(mContext)) {
251                 addRow(AudioManager.STREAM_RING,
252                         R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true, false);
253                 addRow(STREAM_ALARM,
254                         R.drawable.ic_volume_alarm, R.drawable.ic_volume_alarm_mute, true, false);
255                 addRow(AudioManager.STREAM_VOICE_CALL,
256                         R.drawable.ic_volume_voice, R.drawable.ic_volume_voice, false, false);
257                 addRow(AudioManager.STREAM_BLUETOOTH_SCO,
258                         R.drawable.ic_volume_bt_sco, R.drawable.ic_volume_bt_sco, false, false);
259                 addRow(AudioManager.STREAM_SYSTEM, R.drawable.ic_volume_system,
260                         R.drawable.ic_volume_system_mute, false, false);
261             }
262         } else {
263             addExistingRows();
264         }
265 
266         updateRowsH(getActiveRow());
267         initRingerH();
268         initSettingsH();
269     }
270 
getDialogView()271     protected ViewGroup getDialogView() {
272         return mDialogView;
273     }
274 
getAlphaAttr(int attr)275     private int getAlphaAttr(int attr) {
276         TypedArray ta = mContext.obtainStyledAttributes(new int[]{attr});
277         float alpha = ta.getFloat(0, 0);
278         ta.recycle();
279         return (int) (alpha * 255);
280     }
281 
isLandscape()282     private boolean isLandscape() {
283         return mContext.getResources().getConfiguration().orientation ==
284                 Configuration.ORIENTATION_LANDSCAPE;
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 
setAutomute(boolean automute)291     public void setAutomute(boolean automute) {
292         if (mAutomute == automute) return;
293         mAutomute = automute;
294         mHandler.sendEmptyMessage(H.RECHECK_ALL);
295     }
296 
setSilentMode(boolean silentMode)297     public void setSilentMode(boolean silentMode) {
298         if (mSilentMode == silentMode) return;
299         mSilentMode = silentMode;
300         mHandler.sendEmptyMessage(H.RECHECK_ALL);
301     }
302 
addRow(int stream, int iconRes, int iconMuteRes, boolean important, boolean defaultStream)303     private void addRow(int stream, int iconRes, int iconMuteRes, boolean important,
304             boolean defaultStream) {
305         addRow(stream, iconRes, iconMuteRes, important, defaultStream, false);
306     }
307 
addRow(int stream, int iconRes, int iconMuteRes, boolean important, boolean defaultStream, boolean dynamic)308     private void addRow(int stream, int iconRes, int iconMuteRes, boolean important,
309             boolean defaultStream, boolean dynamic) {
310         if (D.BUG) Slog.d(TAG, "Adding row for stream " + stream);
311         VolumeRow row = new VolumeRow();
312         initRow(row, stream, iconRes, iconMuteRes, important, defaultStream);
313         mDialogRowsView.addView(row.view);
314         mRows.add(row);
315     }
316 
addExistingRows()317     private void addExistingRows() {
318         int N = mRows.size();
319         for (int i = 0; i < N; i++) {
320             final VolumeRow row = mRows.get(i);
321             initRow(row, row.stream, row.iconRes, row.iconMuteRes, row.important,
322                     row.defaultStream);
323             mDialogRowsView.addView(row.view);
324             updateVolumeRowH(row);
325         }
326     }
327 
getActiveRow()328     private VolumeRow getActiveRow() {
329         for (VolumeRow row : mRows) {
330             if (row.stream == mActiveStream) {
331                 return row;
332             }
333         }
334         for (VolumeRow row : mRows) {
335             if (row.stream == STREAM_MUSIC) {
336                 return row;
337             }
338         }
339         return mRows.get(0);
340     }
341 
findRow(int stream)342     private VolumeRow findRow(int stream) {
343         for (VolumeRow row : mRows) {
344             if (row.stream == stream) return row;
345         }
346         return null;
347     }
348 
dump(PrintWriter writer)349     public void dump(PrintWriter writer) {
350         writer.println(VolumeDialogImpl.class.getSimpleName() + " state:");
351         writer.print("  mShowing: "); writer.println(mShowing);
352         writer.print("  mActiveStream: "); writer.println(mActiveStream);
353         writer.print("  mDynamic: "); writer.println(mDynamic);
354         writer.print("  mAutomute: "); writer.println(mAutomute);
355         writer.print("  mSilentMode: "); writer.println(mSilentMode);
356         writer.print("  mAccessibility.mFeedbackEnabled: ");
357         writer.println(mAccessibility.mFeedbackEnabled);
358     }
359 
getImpliedLevel(SeekBar seekBar, int progress)360     private static int getImpliedLevel(SeekBar seekBar, int progress) {
361         final int m = seekBar.getMax();
362         final int n = m / 100 - 1;
363         final int level = progress == 0 ? 0
364                 : progress == m ? (m / 100) : (1 + (int)((progress / (float) m) * n));
365         return level;
366     }
367 
368     @SuppressLint("InflateParams")
initRow(final VolumeRow row, final int stream, int iconRes, int iconMuteRes, boolean important, boolean defaultStream)369     private void initRow(final VolumeRow row, final int stream, int iconRes, int iconMuteRes,
370             boolean important, boolean defaultStream) {
371         row.stream = stream;
372         row.iconRes = iconRes;
373         row.iconMuteRes = iconMuteRes;
374         row.important = important;
375         row.defaultStream = defaultStream;
376         row.view = mDialog.getLayoutInflater().inflate(R.layout.volume_dialog_row, null);
377         row.view.setId(row.stream);
378         row.view.setTag(row);
379         row.header = row.view.findViewById(R.id.volume_row_header);
380         row.header.setId(20 * row.stream);
381         if (stream == STREAM_ACCESSIBILITY) {
382             row.header.setFilters(new InputFilter[] {new InputFilter.LengthFilter(13)});
383         }
384         row.dndIcon = row.view.findViewById(R.id.dnd_icon);
385         row.slider =  row.view.findViewById(R.id.volume_row_slider);
386         row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
387         row.anim = null;
388 
389         row.icon = row.view.findViewById(R.id.volume_row_icon);
390         row.icon.setImageResource(iconRes);
391         if (row.stream != AudioSystem.STREAM_ACCESSIBILITY) {
392             row.icon.setOnClickListener(v -> {
393                 Events.writeEvent(mContext, Events.EVENT_ICON_CLICK, row.stream, row.iconState);
394                 mController.setActiveStream(row.stream);
395                 if (row.stream == AudioManager.STREAM_RING) {
396                     final boolean hasVibrator = mController.hasVibrator();
397                     if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) {
398                         if (hasVibrator) {
399                             mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false);
400                         } else {
401                             final boolean wasZero = row.ss.level == 0;
402                             mController.setStreamVolume(stream,
403                                     wasZero ? row.lastAudibleLevel : 0);
404                         }
405                     } else {
406                         mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
407                         if (row.ss.level == 0) {
408                             mController.setStreamVolume(stream, 1);
409                         }
410                     }
411                 } else {
412                     final boolean vmute = row.ss.level == row.ss.levelMin;
413                     mController.setStreamVolume(stream,
414                             vmute ? row.lastAudibleLevel : row.ss.levelMin);
415                 }
416                 row.userAttempt = 0;  // reset the grace period, slider updates immediately
417             });
418         } else {
419             row.icon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
420         }
421     }
422 
initSettingsH()423     public void initSettingsH() {
424         mSettingsView.setVisibility(
425                 mDeviceProvisionedController.isCurrentUserSetup() ? VISIBLE : GONE);
426         mSettingsIcon.setOnClickListener(v -> {
427             Events.writeEvent(mContext, Events.EVENT_SETTINGS_CLICK);
428             Intent intent = new Intent(Settings.ACTION_SOUND_SETTINGS);
429             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
430             dismissH(DISMISS_REASON_SETTINGS_CLICKED);
431             Dependency.get(ActivityStarter.class).startActivity(intent, true /* dismissShade */);
432         });
433     }
434 
initRingerH()435     public void initRingerH() {
436         mRingerIcon.setOnClickListener(v -> {
437             Prefs.putBoolean(mContext, Prefs.Key.TOUCHED_RINGER_TOGGLE, true);
438             final StreamState ss = mState.states.get(AudioManager.STREAM_RING);
439             if (ss == null) {
440                 return;
441             }
442             // normal -> vibrate -> silent -> normal (skip vibrate if device doesn't have
443             // a vibrator.
444             int newRingerMode;
445             final boolean hasVibrator = mController.hasVibrator();
446             if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) {
447                 if (hasVibrator) {
448                     newRingerMode = AudioManager.RINGER_MODE_VIBRATE;
449                 } else {
450                     newRingerMode = AudioManager.RINGER_MODE_SILENT;
451                 }
452             } else if (mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) {
453                 newRingerMode = AudioManager.RINGER_MODE_SILENT;
454             } else {
455                 newRingerMode = AudioManager.RINGER_MODE_NORMAL;
456                 if (ss.level == 0) {
457                     mController.setStreamVolume(AudioManager.STREAM_RING, 1);
458                 }
459             }
460             Events.writeEvent(mContext, Events.EVENT_RINGER_TOGGLE, newRingerMode);
461             incrementManualToggleCount();
462             updateRingerH();
463             provideTouchFeedbackH(newRingerMode);
464             mController.setRingerMode(newRingerMode, false);
465             maybeShowToastH(newRingerMode);
466         });
467         updateRingerH();
468     }
469 
incrementManualToggleCount()470     private void incrementManualToggleCount() {
471         ContentResolver cr = mContext.getContentResolver();
472         int ringerCount = Settings.Secure.getInt(cr, Settings.Secure.MANUAL_RINGER_TOGGLE_COUNT, 0);
473         Settings.Secure.putInt(cr, Settings.Secure.MANUAL_RINGER_TOGGLE_COUNT, ringerCount + 1);
474     }
475 
provideTouchFeedbackH(int newRingerMode)476     private void provideTouchFeedbackH(int newRingerMode) {
477         VibrationEffect effect = null;
478         switch (newRingerMode) {
479             case RINGER_MODE_NORMAL:
480                 mController.scheduleTouchFeedback();
481                 break;
482             case RINGER_MODE_SILENT:
483                 effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
484                 break;
485             case RINGER_MODE_VIBRATE:
486             default:
487                 effect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
488         }
489         if (effect != null) {
490             mController.vibrate(effect);
491         }
492     }
493 
maybeShowToastH(int newRingerMode)494     private void maybeShowToastH(int newRingerMode) {
495         int seenToastCount = Prefs.getInt(mContext, Prefs.Key.SEEN_RINGER_GUIDANCE_COUNT, 0);
496 
497         if (seenToastCount > VolumePrefs.SHOW_RINGER_TOAST_COUNT) {
498             return;
499         }
500         CharSequence toastText = null;
501         switch (newRingerMode) {
502             case RINGER_MODE_NORMAL:
503                 final StreamState ss = mState.states.get(AudioManager.STREAM_RING);
504                 if (ss != null) {
505                     toastText = mContext.getString(
506                             R.string.volume_dialog_ringer_guidance_ring,
507                             Utils.formatPercentage(ss.level, ss.levelMax));
508                 }
509                 break;
510             case RINGER_MODE_SILENT:
511                 toastText = mContext.getString(
512                         com.android.internal.R.string.volume_dialog_ringer_guidance_silent);
513                 break;
514             case RINGER_MODE_VIBRATE:
515             default:
516                 toastText = mContext.getString(
517                         com.android.internal.R.string.volume_dialog_ringer_guidance_vibrate);
518         }
519 
520         Toast.makeText(mContext, toastText, Toast.LENGTH_SHORT).show();
521         seenToastCount++;
522         Prefs.putInt(mContext, Prefs.Key.SEEN_RINGER_GUIDANCE_COUNT, seenToastCount);
523     }
524 
show(int reason)525     public void show(int reason) {
526         mHandler.obtainMessage(H.SHOW, reason, 0).sendToTarget();
527     }
528 
dismiss(int reason)529     public void dismiss(int reason) {
530         mHandler.obtainMessage(H.DISMISS, reason, 0).sendToTarget();
531     }
532 
showH(int reason)533     private void showH(int reason) {
534         if (D.BUG) Log.d(TAG, "showH r=" + Events.DISMISS_REASONS[reason]);
535         mHandler.removeMessages(H.SHOW);
536         mHandler.removeMessages(H.DISMISS);
537         rescheduleTimeoutH();
538         mShowing = true;
539 
540         initSettingsH();
541         mDialog.show();
542         Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
543         mController.notifyVisible(true);
544     }
545 
rescheduleTimeoutH()546     protected void rescheduleTimeoutH() {
547         mHandler.removeMessages(H.DISMISS);
548         final int timeout = computeTimeoutH();
549         mHandler.sendMessageDelayed(mHandler
550                 .obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT, 0), timeout);
551         if (D.BUG) Log.d(TAG, "rescheduleTimeout " + timeout + " " + Debug.getCaller());
552         mController.userActivity();
553     }
554 
computeTimeoutH()555     private int computeTimeoutH() {
556         if (mAccessibility.mFeedbackEnabled) return 20000;
557         if (mHovering) return 16000;
558         if (mSafetyWarning != null) return 5000;
559         return 3000;
560     }
561 
dismissH(int reason)562     protected void dismissH(int reason) {
563         mHandler.removeMessages(H.DISMISS);
564         mHandler.removeMessages(H.SHOW);
565         mDialogView.animate().cancel();
566         mShowing = false;
567 
568         mDialogView.setTranslationX(0);
569         mDialogView.setAlpha(1);
570         ViewPropertyAnimator animator = mDialogView.animate()
571                 .alpha(0)
572                 .setDuration(250)
573                 .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
574                 .withEndAction(() -> mHandler.postDelayed(() -> {
575                     if (D.BUG) Log.d(TAG, "mDialog.dismiss()");
576                     mDialog.dismiss();
577                 }, 50));
578         if (!isLandscape()) animator.translationX(mDialogView.getWidth() / 2);
579         animator.start();
580 
581         Events.writeEvent(mContext, Events.EVENT_DISMISS_DIALOG, reason);
582         mController.notifyVisible(false);
583         synchronized (mSafetyWarningLock) {
584             if (mSafetyWarning != null) {
585                 if (D.BUG) Log.d(TAG, "SafetyWarning dismissed");
586                 mSafetyWarning.dismiss();
587             }
588         }
589     }
590 
shouldBeVisibleH(VolumeRow row, VolumeRow activeRow)591     private boolean shouldBeVisibleH(VolumeRow row, VolumeRow activeRow) {
592         boolean isActive = row.stream == activeRow.stream;
593         if (row.stream == AudioSystem.STREAM_ACCESSIBILITY) {
594             return mShowA11yStream;
595         }
596 
597         // if the active row is accessibility, then continue to display previous
598         // active row since accessibility is displayed under it
599         if (activeRow.stream == AudioSystem.STREAM_ACCESSIBILITY &&
600                 row.stream == mPrevActiveStream) {
601             return true;
602         }
603 
604         if (isActive) {
605             return true;
606         }
607 
608         if (row.defaultStream) {
609             return activeRow.stream == STREAM_RING
610                     || activeRow.stream == STREAM_ALARM
611                     || activeRow.stream == STREAM_VOICE_CALL
612                     || activeRow.stream == STREAM_ACCESSIBILITY
613                     || mDynamic.get(activeRow.stream);
614         }
615 
616         return false;
617     }
618 
updateRowsH(final VolumeRow activeRow)619     private void updateRowsH(final VolumeRow activeRow) {
620         if (D.BUG) Log.d(TAG, "updateRowsH");
621         if (!mShowing) {
622             trimObsoleteH();
623         }
624         // apply changes to all rows
625         for (final VolumeRow row : mRows) {
626             final boolean isActive = row == activeRow;
627             final boolean shouldBeVisible = shouldBeVisibleH(row, activeRow);
628             Util.setVisOrGone(row.view, shouldBeVisible);
629             if (row.view.isShown()) {
630                 updateVolumeRowTintH(row, isActive);
631             }
632         }
633     }
634 
updateRingerH()635     protected void updateRingerH() {
636         if (mState != null) {
637             final StreamState ss = mState.states.get(AudioManager.STREAM_RING);
638             if (ss == null) {
639                 return;
640             }
641 
642             boolean isZenMuted = mState.zenMode == Global.ZEN_MODE_ALARMS
643                     || mState.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS
644                     || (mState.zenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
645                         && mState.disallowRinger);
646             enableRingerViewsH(!isZenMuted);
647             switch (mState.ringerModeInternal) {
648                 case AudioManager.RINGER_MODE_VIBRATE:
649                     mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_vibrate);
650                     addAccessibilityDescription(mRingerIcon, RINGER_MODE_VIBRATE,
651                             mContext.getString(R.string.volume_ringer_hint_mute));
652                     mRingerIcon.setTag(Events.ICON_STATE_VIBRATE);
653                     break;
654                 case AudioManager.RINGER_MODE_SILENT:
655                     mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
656                     mRingerIcon.setTag(Events.ICON_STATE_MUTE);
657                     addAccessibilityDescription(mRingerIcon, RINGER_MODE_SILENT,
658                             mContext.getString(R.string.volume_ringer_hint_unmute));
659                     break;
660                 case AudioManager.RINGER_MODE_NORMAL:
661                 default:
662                     boolean muted = (mAutomute && ss.level == 0) || ss.muted;
663                     if (!isZenMuted && muted) {
664                         mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
665                         addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL,
666                                 mContext.getString(R.string.volume_ringer_hint_unmute));
667                         mRingerIcon.setTag(Events.ICON_STATE_MUTE);
668                     } else {
669                         mRingerIcon.setImageResource(R.drawable.ic_volume_ringer);
670                         if (mController.hasVibrator()) {
671                             addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL,
672                                     mContext.getString(R.string.volume_ringer_hint_vibrate));
673                         } else {
674                             addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL,
675                                     mContext.getString(R.string.volume_ringer_hint_mute));
676                         }
677                         mRingerIcon.setTag(Events.ICON_STATE_UNMUTE);
678                     }
679                     break;
680             }
681         }
682     }
683 
addAccessibilityDescription(View view, int currState, String hintLabel)684     private void addAccessibilityDescription(View view, int currState, String hintLabel) {
685         int currStateResId;
686         switch (currState) {
687             case RINGER_MODE_SILENT:
688                 currStateResId = R.string.volume_ringer_status_silent;
689                 break;
690             case RINGER_MODE_VIBRATE:
691                 currStateResId = R.string.volume_ringer_status_vibrate;
692                 break;
693             case RINGER_MODE_NORMAL:
694             default:
695                 currStateResId = R.string.volume_ringer_status_normal;
696         }
697 
698         view.setContentDescription(mContext.getString(currStateResId));
699 
700         view.setAccessibilityDelegate(new AccessibilityDelegate() {
701             public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
702                 super.onInitializeAccessibilityNodeInfo(host, info);
703                 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
704                                 AccessibilityNodeInfo.ACTION_CLICK, hintLabel));
705             }
706         });
707     }
708 
709     /**
710      * Toggles enable state of views in a VolumeRow (not including seekbar or icon)
711      * Hides/shows zen icon
712      * @param enable whether to enable volume row views and hide dnd icon
713      */
enableVolumeRowViewsH(VolumeRow row, boolean enable)714     private void enableVolumeRowViewsH(VolumeRow row, boolean enable) {
715         boolean showDndIcon = !enable;
716         row.dndIcon.setVisibility(showDndIcon ? VISIBLE : GONE);
717     }
718 
719     /**
720      * Toggles enable state of footer/ringer views
721      * Hides/shows zen icon
722      * @param enable whether to enable ringer views and hide dnd icon
723      */
enableRingerViewsH(boolean enable)724     private void enableRingerViewsH(boolean enable) {
725         mRingerIcon.setEnabled(enable);
726         mZenIcon.setVisibility(enable ? GONE : VISIBLE);
727     }
728 
trimObsoleteH()729     private void trimObsoleteH() {
730         if (D.BUG) Log.d(TAG, "trimObsoleteH");
731         for (int i = mRows.size() - 1; i >= 0; i--) {
732             final VolumeRow row = mRows.get(i);
733             if (row.ss == null || !row.ss.dynamic) continue;
734             if (!mDynamic.get(row.stream)) {
735                 mRows.remove(i);
736                 mDialogRowsView.removeView(row.view);
737             }
738         }
739     }
740 
onStateChangedH(State state)741     protected void onStateChangedH(State state) {
742         if (mState != null && state != null
743                 && mState.ringerModeInternal != state.ringerModeInternal
744                 && state.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) {
745             mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK));
746         }
747 
748         mState = state;
749         mDynamic.clear();
750         // add any new dynamic rows
751         for (int i = 0; i < state.states.size(); i++) {
752             final int stream = state.states.keyAt(i);
753             final StreamState ss = state.states.valueAt(i);
754             if (!ss.dynamic) continue;
755             mDynamic.put(stream, true);
756             if (findRow(stream) == null) {
757                 addRow(stream, R.drawable.ic_volume_remote, R.drawable.ic_volume_remote_mute, true,
758                         false, true);
759             }
760         }
761 
762         if (mActiveStream != state.activeStream) {
763             mPrevActiveStream = mActiveStream;
764             mActiveStream = state.activeStream;
765             VolumeRow activeRow = getActiveRow();
766             updateRowsH(activeRow);
767             rescheduleTimeoutH();
768         }
769         for (VolumeRow row : mRows) {
770             updateVolumeRowH(row);
771         }
772         updateRingerH();
773         mWindow.setTitle(composeWindowTitle());
774     }
775 
composeWindowTitle()776     CharSequence composeWindowTitle() {
777         return mContext.getString(R.string.volume_dialog_title, getStreamLabelH(getActiveRow().ss));
778     }
779 
updateVolumeRowH(VolumeRow row)780     private void updateVolumeRowH(VolumeRow row) {
781         if (D.BUG) Log.d(TAG, "updateVolumeRowH s=" + row.stream);
782         if (mState == null) return;
783         final StreamState ss = mState.states.get(row.stream);
784         if (ss == null) return;
785         row.ss = ss;
786         if (ss.level > 0) {
787             row.lastAudibleLevel = ss.level;
788         }
789         if (ss.level == row.requestedLevel) {
790             row.requestedLevel = -1;
791         }
792         final boolean isA11yStream = row.stream == STREAM_ACCESSIBILITY;
793         final boolean isRingStream = row.stream == AudioManager.STREAM_RING;
794         final boolean isSystemStream = row.stream == AudioManager.STREAM_SYSTEM;
795         final boolean isAlarmStream = row.stream == STREAM_ALARM;
796         final boolean isMusicStream = row.stream == AudioManager.STREAM_MUSIC;
797         final boolean isRingVibrate = isRingStream
798                 && mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE;
799         final boolean isRingSilent = isRingStream
800                 && mState.ringerModeInternal == AudioManager.RINGER_MODE_SILENT;
801         final boolean isZenPriorityOnly = mState.zenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
802         final boolean isZenAlarms = mState.zenMode == Global.ZEN_MODE_ALARMS;
803         final boolean isZenNone = mState.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
804         final boolean zenMuted = isZenAlarms ? (isRingStream || isSystemStream)
805                 : isZenNone ? (isRingStream || isSystemStream || isAlarmStream || isMusicStream)
806                 : isZenPriorityOnly ? ((isAlarmStream && mState.disallowAlarms) ||
807                         (isMusicStream && mState.disallowMedia) ||
808                         (isRingStream && mState.disallowRinger) ||
809                         (isSystemStream && mState.disallowSystem))
810                 : false;
811 
812         // update slider max
813         final int max = ss.levelMax * 100;
814         if (max != row.slider.getMax()) {
815             row.slider.setMax(max);
816         }
817         // update slider min
818         final int min = ss.levelMin * 100;
819         if (min != row.slider.getMin()) {
820             row.slider.setMin(min);
821         }
822 
823         // update header text
824         Util.setText(row.header, getStreamLabelH(ss));
825         row.slider.setContentDescription(row.header.getText());
826         mConfigurableTexts.add(row.header, ss.name);
827 
828         // update icon
829         final boolean iconEnabled = (mAutomute || ss.muteSupported) && !zenMuted;
830         row.icon.setEnabled(iconEnabled);
831         row.icon.setAlpha(iconEnabled ? 1 : 0.5f);
832         final int iconRes =
833                 isRingVibrate ? R.drawable.ic_volume_ringer_vibrate
834                 : isRingSilent || zenMuted ? row.iconMuteRes
835                 : ss.routedToBluetooth ?
836                         (ss.muted ? R.drawable.ic_volume_media_bt_mute
837                                 : R.drawable.ic_volume_media_bt)
838                 : mAutomute && ss.level == 0 ? row.iconMuteRes
839                 : (ss.muted ? row.iconMuteRes : row.iconRes);
840         row.icon.setImageResource(iconRes);
841         row.iconState =
842                 iconRes == R.drawable.ic_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE
843                 : (iconRes == R.drawable.ic_volume_media_bt_mute || iconRes == row.iconMuteRes)
844                         ? Events.ICON_STATE_MUTE
845                 : (iconRes == R.drawable.ic_volume_media_bt || iconRes == row.iconRes)
846                         ? Events.ICON_STATE_UNMUTE
847                 : Events.ICON_STATE_UNKNOWN;
848         if (iconEnabled) {
849             if (isRingStream) {
850                 if (isRingVibrate) {
851                     row.icon.setContentDescription(mContext.getString(
852                             R.string.volume_stream_content_description_unmute,
853                             getStreamLabelH(ss)));
854                 } else {
855                     if (mController.hasVibrator()) {
856                         row.icon.setContentDescription(mContext.getString(
857                                 mShowA11yStream
858                                         ? R.string.volume_stream_content_description_vibrate_a11y
859                                         : R.string.volume_stream_content_description_vibrate,
860                                 getStreamLabelH(ss)));
861                     } else {
862                         row.icon.setContentDescription(mContext.getString(
863                                 mShowA11yStream
864                                         ? R.string.volume_stream_content_description_mute_a11y
865                                         : R.string.volume_stream_content_description_mute,
866                                 getStreamLabelH(ss)));
867                     }
868                 }
869             } else if (isA11yStream) {
870                 row.icon.setContentDescription(getStreamLabelH(ss));
871             } else {
872                 if (ss.muted || mAutomute && ss.level == 0) {
873                    row.icon.setContentDescription(mContext.getString(
874                            R.string.volume_stream_content_description_unmute,
875                            getStreamLabelH(ss)));
876                 } else {
877                     row.icon.setContentDescription(mContext.getString(
878                             mShowA11yStream
879                                     ? R.string.volume_stream_content_description_mute_a11y
880                                     : R.string.volume_stream_content_description_mute,
881                             getStreamLabelH(ss)));
882                 }
883             }
884         } else {
885             row.icon.setContentDescription(getStreamLabelH(ss));
886         }
887 
888         // ensure tracking is disabled if zenMuted
889         if (zenMuted) {
890             row.tracking = false;
891         }
892         enableVolumeRowViewsH(row, !zenMuted);
893 
894         // update slider
895         final boolean enableSlider = !zenMuted;
896         final int vlevel = row.ss.muted && (!isRingStream && !zenMuted) ? 0
897                 : row.ss.level;
898         updateVolumeRowSliderH(row, enableSlider, vlevel);
899     }
900 
updateVolumeRowTintH(VolumeRow row, boolean isActive)901     private void updateVolumeRowTintH(VolumeRow row, boolean isActive) {
902         if (isActive) {
903             row.slider.requestFocus();
904         }
905         boolean useActiveColoring = isActive && row.slider.isEnabled();
906         final ColorStateList tint = useActiveColoring ? mActiveTint : mInactiveTint;
907         final int alpha = useActiveColoring ? mActiveAlpha : mInactiveAlpha;
908         if (tint == row.cachedTint) return;
909         row.slider.setProgressTintList(tint);
910         row.slider.setThumbTintList(tint);
911         row.slider.setProgressBackgroundTintList(tint);
912         row.slider.setAlpha(((float) alpha) / 255);
913         row.icon.setImageTintList(tint);
914         row.icon.setImageAlpha(alpha);
915         row.cachedTint = tint;
916     }
917 
updateVolumeRowSliderH(VolumeRow row, boolean enable, int vlevel)918     private void updateVolumeRowSliderH(VolumeRow row, boolean enable, int vlevel) {
919         row.slider.setEnabled(enable);
920         updateVolumeRowTintH(row, row.stream == mActiveStream);
921         if (row.tracking) {
922             return;  // don't update if user is sliding
923         }
924         final int progress = row.slider.getProgress();
925         final int level = getImpliedLevel(row.slider, progress);
926         final boolean rowVisible = row.view.getVisibility() == VISIBLE;
927         final boolean inGracePeriod = (SystemClock.uptimeMillis() - row.userAttempt)
928                 < USER_ATTEMPT_GRACE_PERIOD;
929         mHandler.removeMessages(H.RECHECK, row);
930         if (mShowing && rowVisible && inGracePeriod) {
931             if (D.BUG) Log.d(TAG, "inGracePeriod");
932             mHandler.sendMessageAtTime(mHandler.obtainMessage(H.RECHECK, row),
933                     row.userAttempt + USER_ATTEMPT_GRACE_PERIOD);
934             return;  // don't update if visible and in grace period
935         }
936         if (vlevel == level) {
937             if (mShowing && rowVisible) {
938                 return;  // don't clamp if visible
939             }
940         }
941         final int newProgress = vlevel * 100;
942         if (progress != newProgress) {
943             if (mShowing && rowVisible) {
944                 // animate!
945                 if (row.anim != null && row.anim.isRunning()
946                         && row.animTargetProgress == newProgress) {
947                     return;  // already animating to the target progress
948                 }
949                 // start/update animation
950                 if (row.anim == null) {
951                     row.anim = ObjectAnimator.ofInt(row.slider, "progress", progress, newProgress);
952                     row.anim.setInterpolator(new DecelerateInterpolator());
953                 } else {
954                     row.anim.cancel();
955                     row.anim.setIntValues(progress, newProgress);
956                 }
957                 row.animTargetProgress = newProgress;
958                 row.anim.setDuration(UPDATE_ANIMATION_DURATION);
959                 row.anim.start();
960             } else {
961                 // update slider directly to clamped value
962                 if (row.anim != null) {
963                     row.anim.cancel();
964                 }
965                 row.slider.setProgress(newProgress, true);
966             }
967         }
968     }
969 
970     private void recheckH(VolumeRow row) {
971         if (row == null) {
972             if (D.BUG) Log.d(TAG, "recheckH ALL");
973             trimObsoleteH();
974             for (VolumeRow r : mRows) {
975                 updateVolumeRowH(r);
976             }
977         } else {
978             if (D.BUG) Log.d(TAG, "recheckH " + row.stream);
979             updateVolumeRowH(row);
980         }
981     }
982 
983     private void setStreamImportantH(int stream, boolean important) {
984         for (VolumeRow row : mRows) {
985             if (row.stream == stream) {
986                 row.important = important;
987                 return;
988             }
989         }
990     }
991 
992     private void showSafetyWarningH(int flags) {
993         if ((flags & (AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_SHOW_UI_WARNINGS)) != 0
994                 || mShowing) {
995             synchronized (mSafetyWarningLock) {
996                 if (mSafetyWarning != null) {
997                     return;
998                 }
999                 mSafetyWarning = new SafetyWarningDialog(mContext, mController.getAudioManager()) {
1000                     @Override
1001                     protected void cleanUp() {
1002                         synchronized (mSafetyWarningLock) {
1003                             mSafetyWarning = null;
1004                         }
1005                         recheckH(null);
1006                     }
1007                 };
1008                 mSafetyWarning.show();
1009             }
1010             recheckH(null);
1011         }
1012         rescheduleTimeoutH();
1013     }
1014 
1015     private String getStreamLabelH(StreamState ss) {
1016         if (ss == null) {
1017             return "";
1018         }
1019         if (ss.remoteLabel != null) {
1020             return ss.remoteLabel;
1021         }
1022         try {
1023             return mContext.getResources().getString(ss.name);
1024         } catch (Resources.NotFoundException e) {
1025             Slog.e(TAG, "Can't find translation for stream " + ss);
1026             return "";
1027         }
1028     }
1029 
1030     private Runnable mSinglePress = new Runnable() {
1031         @Override
1032         public void run() {
1033             mRingerIcon.setPressed(true);
1034             mRingerIcon.postOnAnimationDelayed(mSingleUnpress, 200);
1035         }
1036     };
1037 
1038     private Runnable mSingleUnpress = new Runnable() {
1039         @Override
1040         public void run() {
1041             mRingerIcon.setPressed(false);
1042         }
1043     };
1044 
1045     private final VolumeDialogController.Callbacks mControllerCallbackH
1046             = new VolumeDialogController.Callbacks() {
1047         @Override
1048         public void onShowRequested(int reason) {
1049             showH(reason);
1050         }
1051 
1052         @Override
1053         public void onDismissRequested(int reason) {
1054             dismissH(reason);
1055         }
1056 
1057         @Override
1058         public void onScreenOff() {
1059             dismissH(Events.DISMISS_REASON_SCREEN_OFF);
1060         }
1061 
1062         @Override
1063         public void onStateChanged(State state) {
1064             onStateChangedH(state);
1065         }
1066 
1067         @Override
1068         public void onLayoutDirectionChanged(int layoutDirection) {
1069             mDialogView.setLayoutDirection(layoutDirection);
1070         }
1071 
1072         @Override
1073         public void onConfigurationChanged() {
1074             mDialog.dismiss();
1075             initDialog();
1076             mConfigurableTexts.update();
1077         }
1078 
1079         @Override
1080         public void onShowVibrateHint() {
1081             if (mSilentMode) {
1082                 mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false);
1083             }
1084         }
1085 
1086         @Override
1087         public void onShowSilentHint() {
1088             if (mSilentMode) {
1089                 mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
1090             }
1091         }
1092 
1093         @Override
1094         public void onShowSafetyWarning(int flags) {
1095             showSafetyWarningH(flags);
1096         }
1097 
1098         @Override
1099         public void onAccessibilityModeChanged(Boolean showA11yStream) {
1100             mShowA11yStream = showA11yStream == null ? false : showA11yStream;
1101             VolumeRow activeRow = getActiveRow();
1102             if (!mShowA11yStream && STREAM_ACCESSIBILITY == activeRow.stream) {
1103                 dismissH(Events.DISMISS_STREAM_GONE);
1104             } else {
1105                 updateRowsH(activeRow);
1106             }
1107 
1108         }
1109     };
1110 
1111     private final class H extends Handler {
1112         private static final int SHOW = 1;
1113         private static final int DISMISS = 2;
1114         private static final int RECHECK = 3;
1115         private static final int RECHECK_ALL = 4;
1116         private static final int SET_STREAM_IMPORTANT = 5;
1117         private static final int RESCHEDULE_TIMEOUT = 6;
1118         private static final int STATE_CHANGED = 7;
1119 
1120         public H() {
1121             super(Looper.getMainLooper());
1122         }
1123 
1124         @Override
1125         public void handleMessage(Message msg) {
1126             switch (msg.what) {
1127                 case SHOW: showH(msg.arg1); break;
1128                 case DISMISS: dismissH(msg.arg1); break;
1129                 case RECHECK: recheckH((VolumeRow) msg.obj); break;
1130                 case RECHECK_ALL: recheckH(null); break;
1131                 case SET_STREAM_IMPORTANT: setStreamImportantH(msg.arg1, msg.arg2 != 0); break;
1132                 case RESCHEDULE_TIMEOUT: rescheduleTimeoutH(); break;
1133                 case STATE_CHANGED: onStateChangedH(mState); break;
1134             }
1135         }
1136     }
1137 
1138     private final class CustomDialog extends Dialog implements DialogInterface {
1139         public CustomDialog(Context context) {
1140             super(context, com.android.systemui.R.style.qs_theme);
1141         }
1142 
1143         @Override
1144         public boolean dispatchTouchEvent(MotionEvent ev) {
1145             rescheduleTimeoutH();
1146             return super.dispatchTouchEvent(ev);
1147         }
1148 
1149         @Override
1150         protected void onStart() {
1151             super.setCanceledOnTouchOutside(true);
1152             super.onStart();
1153         }
1154 
1155         @Override
1156         protected void onStop() {
1157             super.onStop();
1158             mHandler.sendEmptyMessage(H.RECHECK_ALL);
1159         }
1160 
1161         @Override
1162         public boolean onTouchEvent(MotionEvent event) {
1163             if (isShowing()) {
1164                 if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
1165                     dismissH(Events.DISMISS_REASON_TOUCH_OUTSIDE);
1166                     return true;
1167                 }
1168             }
1169             return false;
1170         }
1171     }
1172 
1173     private final class VolumeSeekBarChangeListener implements OnSeekBarChangeListener {
1174         private final VolumeRow mRow;
1175 
1176         private VolumeSeekBarChangeListener(VolumeRow row) {
1177             mRow = row;
1178         }
1179 
1180         @Override
1181         public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
1182             if (mRow.ss == null) return;
1183             if (D.BUG) Log.d(TAG, AudioSystem.streamToString(mRow.stream)
1184                     + " onProgressChanged " + progress + " fromUser=" + fromUser);
1185             if (!fromUser) return;
1186             if (mRow.ss.levelMin > 0) {
1187                 final int minProgress = mRow.ss.levelMin * 100;
1188                 if (progress < minProgress) {
1189                     seekBar.setProgress(minProgress);
1190                     progress = minProgress;
1191                 }
1192             }
1193             final int userLevel = getImpliedLevel(seekBar, progress);
1194             if (mRow.ss.level != userLevel || mRow.ss.muted && userLevel > 0) {
1195                 mRow.userAttempt = SystemClock.uptimeMillis();
1196                 if (mRow.requestedLevel != userLevel) {
1197                     mController.setStreamVolume(mRow.stream, userLevel);
1198                     mRow.requestedLevel = userLevel;
1199                     Events.writeEvent(mContext, Events.EVENT_TOUCH_LEVEL_CHANGED, mRow.stream,
1200                             userLevel);
1201                 }
1202             }
1203         }
1204 
1205         @Override
onStartTrackingTouch(SeekBar seekBar)1206         public void onStartTrackingTouch(SeekBar seekBar) {
1207             if (D.BUG) Log.d(TAG, "onStartTrackingTouch"+ " " + mRow.stream);
1208             mController.setActiveStream(mRow.stream);
1209             mRow.tracking = true;
1210         }
1211 
1212         @Override
onStopTrackingTouch(SeekBar seekBar)1213         public void onStopTrackingTouch(SeekBar seekBar) {
1214             if (D.BUG) Log.d(TAG, "onStopTrackingTouch"+ " " + mRow.stream);
1215             mRow.tracking = false;
1216             mRow.userAttempt = SystemClock.uptimeMillis();
1217             final int userLevel = getImpliedLevel(seekBar, seekBar.getProgress());
1218             Events.writeEvent(mContext, Events.EVENT_TOUCH_LEVEL_DONE, mRow.stream, userLevel);
1219             if (mRow.ss.level != userLevel) {
1220                 mHandler.sendMessageDelayed(mHandler.obtainMessage(H.RECHECK, mRow),
1221                         USER_ATTEMPT_GRACE_PERIOD);
1222             }
1223         }
1224     }
1225 
1226     private final class Accessibility extends AccessibilityDelegate {
1227         private boolean mFeedbackEnabled;
1228 
init()1229         public void init() {
1230             mDialogView.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
1231                 @Override
1232                 public void onViewDetachedFromWindow(View v) {
1233                     if (D.BUG) Log.d(TAG, "onViewDetachedFromWindow");
1234                 }
1235 
1236                 @Override
1237                 public void onViewAttachedToWindow(View v) {
1238                     if (D.BUG) Log.d(TAG, "onViewAttachedToWindow");
1239                     updateFeedbackEnabled();
1240                 }
1241             });
1242             mDialogView.setAccessibilityDelegate(this);
1243             mAccessibilityMgr.addCallback(mListener);
1244             updateFeedbackEnabled();
1245         }
1246 
destroy()1247         public void destroy() {
1248             mAccessibilityMgr.removeCallback(mListener);
1249         }
1250 
1251         @Override
dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event)1252         public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
1253             // Activities populate their title here. Follow that example.
1254             event.getText().add(composeWindowTitle());
1255             return true;
1256         }
1257 
1258         @Override
onRequestSendAccessibilityEvent(ViewGroup host, View child, AccessibilityEvent event)1259         public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
1260                 AccessibilityEvent event) {
1261             rescheduleTimeoutH();
1262             return super.onRequestSendAccessibilityEvent(host, child, event);
1263         }
1264 
updateFeedbackEnabled()1265         private void updateFeedbackEnabled() {
1266             mFeedbackEnabled = computeFeedbackEnabled();
1267         }
1268 
computeFeedbackEnabled()1269         private boolean computeFeedbackEnabled() {
1270             // are there any enabled non-generic a11y services?
1271             final List<AccessibilityServiceInfo> services =
1272                     mAccessibilityMgr.getEnabledAccessibilityServiceList(FEEDBACK_ALL_MASK);
1273             for (AccessibilityServiceInfo asi : services) {
1274                 if (asi.feedbackType != 0 && asi.feedbackType != FEEDBACK_GENERIC) {
1275                     return true;
1276                 }
1277             }
1278             return false;
1279         }
1280 
1281         private final AccessibilityServicesStateChangeListener mListener =
1282                 enabled -> updateFeedbackEnabled();
1283     }
1284 
1285     private static class VolumeRow {
1286         private View view;
1287         private TextView header;
1288         private ImageButton icon;
1289         private SeekBar slider;
1290         private int stream;
1291         private StreamState ss;
1292         private long userAttempt;  // last user-driven slider change
1293         private boolean tracking;  // tracking slider touch
1294         private int requestedLevel = -1;  // pending user-requested level via progress changed
1295         private int iconRes;
1296         private int iconMuteRes;
1297         private boolean important;
1298         private boolean defaultStream;
1299         private ColorStateList cachedTint;
1300         private int iconState;  // from Events
1301         private ObjectAnimator anim;  // slider progress animation for non-touch-related updates
1302         private int animTargetProgress;
1303         private int lastAudibleLevel = 1;
1304         private FrameLayout dndIcon;
1305     }
1306 }
1307