• 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 static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
20 import static android.media.AudioManager.RINGER_MODE_NORMAL;
21 import static android.media.AudioManager.RINGER_MODE_SILENT;
22 import static android.media.AudioManager.RINGER_MODE_VIBRATE;
23 import static android.media.AudioManager.STREAM_ACCESSIBILITY;
24 import static android.media.AudioManager.STREAM_ALARM;
25 import static android.media.AudioManager.STREAM_MUSIC;
26 import static android.media.AudioManager.STREAM_RING;
27 import static android.media.AudioManager.STREAM_VOICE_CALL;
28 import static android.view.View.ACCESSIBILITY_LIVE_REGION_POLITE;
29 import static android.view.View.GONE;
30 import static android.view.View.INVISIBLE;
31 import static android.view.View.LAYOUT_DIRECTION_RTL;
32 import static android.view.View.VISIBLE;
33 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
34 import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL;
35 import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder;
36 import static com.android.settingslib.flags.Flags.audioSharingDeveloperOption;
37 import static com.android.settingslib.flags.Flags.volumeDialogAudioSharingFix;
38 import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED;
39 import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED;
40 
41 import android.animation.Animator;
42 import android.animation.AnimatorListenerAdapter;
43 import android.animation.ArgbEvaluator;
44 import android.animation.ObjectAnimator;
45 import android.animation.ValueAnimator;
46 import android.annotation.SuppressLint;
47 import android.app.ActivityManager;
48 import android.app.Dialog;
49 import android.app.KeyguardManager;
50 import android.content.ContentResolver;
51 import android.content.Context;
52 import android.content.DialogInterface;
53 import android.content.pm.PackageManager;
54 import android.content.res.ColorStateList;
55 import android.content.res.Configuration;
56 import android.content.res.Resources;
57 import android.content.res.TypedArray;
58 import android.graphics.Color;
59 import android.graphics.Outline;
60 import android.graphics.PixelFormat;
61 import android.graphics.Rect;
62 import android.graphics.Region;
63 import android.graphics.drawable.ColorDrawable;
64 import android.graphics.drawable.Drawable;
65 import android.graphics.drawable.LayerDrawable;
66 import android.graphics.drawable.RotateDrawable;
67 import android.graphics.drawable.ShapeDrawable;
68 import android.graphics.drawable.shapes.RoundRectShape;
69 import android.media.AudioManager;
70 import android.media.AudioSystem;
71 import android.os.Debug;
72 import android.os.Handler;
73 import android.os.Looper;
74 import android.os.Message;
75 import android.os.SystemClock;
76 import android.os.Trace;
77 import android.os.VibrationEffect;
78 import android.provider.Settings;
79 import android.provider.Settings.Global;
80 import android.text.InputFilter;
81 import android.util.Log;
82 import android.util.Slog;
83 import android.util.SparseBooleanArray;
84 import android.view.ContextThemeWrapper;
85 import android.view.Gravity;
86 import android.view.MotionEvent;
87 import android.view.View;
88 import android.view.View.AccessibilityDelegate;
89 import android.view.View.OnAttachStateChangeListener;
90 import android.view.ViewGroup;
91 import android.view.ViewOutlineProvider;
92 import android.view.ViewPropertyAnimator;
93 import android.view.ViewStub;
94 import android.view.ViewTreeObserver;
95 import android.view.Window;
96 import android.view.WindowManager;
97 import android.view.accessibility.AccessibilityEvent;
98 import android.view.accessibility.AccessibilityManager;
99 import android.view.accessibility.AccessibilityNodeInfo;
100 import android.view.animation.DecelerateInterpolator;
101 import android.widget.ImageButton;
102 import android.widget.ImageView;
103 import android.widget.LinearLayout;
104 import android.widget.SeekBar;
105 import android.widget.SeekBar.OnSeekBarChangeListener;
106 import android.widget.TextView;
107 import android.widget.Toast;
108 
109 import androidx.annotation.NonNull;
110 import androidx.annotation.Nullable;
111 
112 import com.android.app.animation.Interpolators;
113 import com.android.internal.annotations.GuardedBy;
114 import com.android.internal.annotations.VisibleForTesting;
115 import com.android.internal.graphics.drawable.BackgroundBlurDrawable;
116 import com.android.internal.jank.InteractionJankMonitor;
117 import com.android.internal.view.RotationPolicy;
118 import com.android.settingslib.Utils;
119 import com.android.systemui.Dumpable;
120 import com.android.systemui.Flags;
121 import com.android.systemui.Prefs;
122 import com.android.systemui.dump.DumpManager;
123 import com.android.systemui.haptics.slider.HapticSlider;
124 import com.android.systemui.haptics.slider.HapticSliderPlugin;
125 import com.android.systemui.haptics.slider.HapticSliderViewBinder;
126 import com.android.systemui.haptics.slider.SeekableSliderTrackerConfig;
127 import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig;
128 import com.android.systemui.haptics.slider.SliderHapticFeedbackFilter;
129 import com.android.systemui.media.dialog.MediaOutputDialogManager;
130 import com.android.systemui.plugins.VolumeDialog;
131 import com.android.systemui.plugins.VolumeDialogController;
132 import com.android.systemui.plugins.VolumeDialogController.State;
133 import com.android.systemui.plugins.VolumeDialogController.StreamState;
134 import com.android.systemui.res.R;
135 import com.android.systemui.statusbar.VibratorHelper;
136 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
137 import com.android.systemui.statusbar.policy.ConfigurationController;
138 import com.android.systemui.statusbar.policy.DevicePostureController;
139 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
140 import com.android.systemui.util.AlphaTintDrawableWrapper;
141 import com.android.systemui.util.RoundedCornerProgressDrawable;
142 import com.android.systemui.util.settings.SecureSettings;
143 import com.android.systemui.volume.domain.interactor.VolumeDialogInteractor;
144 import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor;
145 import com.android.systemui.volume.panel.shared.flag.VolumePanelFlag;
146 import com.android.systemui.volume.ui.navigation.VolumeNavigator;
147 import com.google.android.msdl.domain.MSDLPlayer;
148 
149 import java.io.PrintWriter;
150 import java.util.ArrayList;
151 import java.util.Collections;
152 import java.util.List;
153 import java.util.Optional;
154 import java.util.function.Consumer;
155 
156 import dagger.Lazy;
157 
158 /**
159  * Visual presentation of the volume dialog.
160  *
161  * A client of VolumeDialogControllerImpl and its state model.
162  *
163  * Methods ending in "H" must be called on the (ui) handler.
164  */
165 public class VolumeDialogImpl implements VolumeDialog, Dumpable,
166         ConfigurationController.ConfigurationListener,
167         ViewTreeObserver.OnComputeInternalInsetsListener {
168     private static final String TAG = Util.logTag(VolumeDialogImpl.class);
169 
170     private static final long USER_ATTEMPT_GRACE_PERIOD = 1000;
171     private static final int UPDATE_ANIMATION_DURATION = 80;
172 
173     static final int DIALOG_TIMEOUT_MILLIS = 3000;
174     static final int DIALOG_SAFETYWARNING_TIMEOUT_MILLIS = 5000;
175     static final int DIALOG_ODI_CAPTIONS_TOOLTIP_TIMEOUT_MILLIS = 5000;
176     static final int DIALOG_HOVERING_TIMEOUT_MILLIS = 16000;
177 
178     private static final int DRAWER_ANIMATION_DURATION_SHORT = 175;
179     private static final int DRAWER_ANIMATION_DURATION = 250;
180     private static final int DISPLAY_RANGE_MULTIPLIER = 100;
181 
182     /** Shows volume dialog show animation. */
183     private static final String TYPE_SHOW = "show";
184     /** Dismiss volume dialog animation.  */
185     private static final String TYPE_DISMISS = "dismiss";
186     /** Volume dialog slider animation. */
187     private static final String TYPE_UPDATE = "update";
188 
189     /**
190      *  TODO(b/290612381): remove lingering animations or tolerate them
191      *  When false, this will cause this class to not listen to animator events and not record jank
192      *  events. This should never be false in production code, and only is false for unit tests for
193      *  this class. This flag should be true in Scenario/Integration tests.
194      */
195     private final boolean mShouldListenForJank;
196     private final int mDialogShowAnimationDurationMs;
197     private final int mDialogHideAnimationDurationMs;
198     private int mDialogWidth;
199     private int mDialogCornerRadius;
200     private int mRingerDrawerItemSize;
201     private int mRingerRowsPadding;
202     private boolean mShowVibrate;
203     private int mRingerCount;
204     private final boolean mShowLowMediaVolumeIcon;
205     private final boolean mChangeVolumeRowTintWhenInactive;
206 
207     private final Context mContext;
208     private final H mHandler;
209     private final VolumeDialogController mController;
210     private final DeviceProvisionedController mDeviceProvisionedController;
211     private final Region mTouchableRegion = new Region();
212 
213     private Window mWindow;
214     private CustomDialog mDialog;
215     private ViewGroup mDialogView;
216     private ViewGroup mDialogRowsViewContainer;
217     private ViewGroup mDialogRowsView;
218     private ViewGroup mRinger;
219 
220     /**
221      * Container for the top part of the dialog, which contains the ringer, the ringer drawer, the
222      * volume rows, and the ellipsis button. This does not include the live caption button.
223      */
224     @Nullable private View mTopContainer;
225 
226     /** Container for the ringer icon, and for the (initially hidden) ringer drawer view. */
227     @Nullable private View mRingerAndDrawerContainer;
228 
229     /**
230      * Background drawable for the ringer and drawer container. The background's top bound is
231      * initially inset by the height of the (hidden) ringer drawer. When the drawer is animated in,
232      * this top bound is animated to accommodate it.
233      */
234     @Nullable private Drawable mRingerAndDrawerContainerBackground;
235 
236     private ViewGroup mSelectedRingerContainer;
237     private ImageView mSelectedRingerIcon;
238 
239     private ViewGroup mRingerDrawerContainer;
240     private ViewGroup mRingerDrawerMute;
241     private ViewGroup mRingerDrawerVibrate;
242     private ViewGroup mRingerDrawerNormal;
243     private ImageView mRingerDrawerMuteIcon;
244     private ImageView mRingerDrawerVibrateIcon;
245     private ImageView mRingerDrawerNormalIcon;
246 
247     /**
248      * View that draws the 'selected' background behind one of the three ringer choices in the
249      * drawer.
250      */
251     private ViewGroup mRingerDrawerNewSelectionBg;
252 
253     private final ValueAnimator mRingerDrawerIconColorAnimator = ValueAnimator.ofFloat(0f, 1f);
254     private ImageView mRingerDrawerIconAnimatingSelected;
255     private ImageView mRingerDrawerIconAnimatingDeselected;
256 
257     /**
258      * Animates the volume dialog's background drawable bounds upwards, to match the height of the
259      * expanded ringer drawer.
260      */
261     private final ValueAnimator mAnimateUpBackgroundToMatchDrawer = ValueAnimator.ofFloat(1f, 0f);
262 
263     private boolean mIsRingerDrawerOpen = false;
264     private float mRingerDrawerClosedAmount = 1f;
265 
266     private ImageButton mRingerIcon;
267     private ViewGroup mODICaptionsView;
268     private CaptionsToggleImageButton mODICaptionsIcon;
269     private View mSettingsView;
270     private ImageButton mSettingsIcon;
271     private final List<VolumeRow> mRows = new ArrayList<>();
272     private ConfigurableTexts mConfigurableTexts;
273     private final SparseBooleanArray mDynamic = new SparseBooleanArray();
274     private final KeyguardManager mKeyguard;
275     private final ActivityManager mActivityManager;
276     private final AccessibilityManagerWrapper mAccessibilityMgr;
277     private final Object mSafetyWarningLock = new Object();
278     private final Accessibility mAccessibility = new Accessibility();
279     private final ConfigurationController mConfigurationController;
280     private final MediaOutputDialogManager mMediaOutputDialogManager;
281     private final CsdWarningDialog.Factory mCsdWarningDialogFactory;
282     private final VolumePanelNavigationInteractor mVolumePanelNavigationInteractor;
283     private final VolumeNavigator mVolumeNavigator;
284     private boolean mShowing;
285     private boolean mShowA11yStream;
286     private int mActiveStream;
287     private int mPrevActiveStream;
288     private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE;
289     private boolean mSilentMode = VolumePrefs.DEFAULT_ENABLE_SILENT_MODE;
290     private State mState;
291     @GuardedBy("mSafetyWarningLock")
292     private SafetyWarningDialog mSafetyWarning;
293     @GuardedBy("mSafetyWarningLock")
294     private CsdWarningDialog mCsdDialog;
295     private boolean mHovering = false;
296     private final boolean mIsTv;
297     private boolean mConfigChanged = false;
298     private boolean mIsAnimatingDismiss = false;
299     private boolean mHasSeenODICaptionsTooltip;
300     private ViewStub mODICaptionsTooltipViewStub;
301     @VisibleForTesting View mODICaptionsTooltipView = null;
302 
303     private final boolean mUseBackgroundBlur;
304     private Consumer<Boolean> mCrossWindowBlurEnabledListener;
305     private BackgroundBlurDrawable mDialogRowsViewBackground;
306     private final InteractionJankMonitor mInteractionJankMonitor;
307 
308     private int mWindowGravity;
309 
310     @VisibleForTesting
311     final int mVolumeRingerIconDrawableId = R.drawable.ic_legacy_speaker_on;
312     @VisibleForTesting
313     final int mVolumeRingerMuteIconDrawableId = R.drawable.ic_legacy_speaker_mute;
314 
315     private int mOriginalGravity;
316     private final DevicePostureController.Callback mDevicePostureControllerCallback;
317     private final DevicePostureController mDevicePostureController;
318     private @DevicePostureController.DevicePostureInt int mDevicePosture;
319     private int mOrientation;
320     private final Lazy<SecureSettings> mSecureSettings;
321     private int mDialogTimeoutMillis;
322     private final VibratorHelper mVibratorHelper;
323     private final MSDLPlayer mMSDLPlayer;
324     private final com.android.systemui.util.time.SystemClock mSystemClock;
325     private final VolumePanelFlag mVolumePanelFlag;
326     private final VolumeDialogInteractor mInteractor;
327     // Optional actions for soundDose
328     private Optional<List<CsdWarningAction>>
329             mCsdWarningNotificationActions = Optional.of(Collections.emptyList());
330 
VolumeDialogImpl( Context context, VolumeDialogController volumeDialogController, AccessibilityManagerWrapper accessibilityManagerWrapper, DeviceProvisionedController deviceProvisionedController, ConfigurationController configurationController, MediaOutputDialogManager mediaOutputDialogManager, InteractionJankMonitor interactionJankMonitor, VolumePanelNavigationInteractor volumePanelNavigationInteractor, VolumeNavigator volumeNavigator, boolean shouldListenForJank, CsdWarningDialog.Factory csdWarningDialogFactory, DevicePostureController devicePostureController, Looper looper, VolumePanelFlag volumePanelFlag, DumpManager dumpManager, Lazy<SecureSettings> secureSettings, VibratorHelper vibratorHelper, MSDLPlayer msdlPlayer, com.android.systemui.util.time.SystemClock systemClock, VolumeDialogInteractor interactor)331     public VolumeDialogImpl(
332             Context context,
333             VolumeDialogController volumeDialogController,
334             AccessibilityManagerWrapper accessibilityManagerWrapper,
335             DeviceProvisionedController deviceProvisionedController,
336             ConfigurationController configurationController,
337             MediaOutputDialogManager mediaOutputDialogManager,
338             InteractionJankMonitor interactionJankMonitor,
339             VolumePanelNavigationInteractor volumePanelNavigationInteractor,
340             VolumeNavigator volumeNavigator,
341             boolean shouldListenForJank,
342             CsdWarningDialog.Factory csdWarningDialogFactory,
343             DevicePostureController devicePostureController,
344             Looper looper,
345             VolumePanelFlag volumePanelFlag,
346             DumpManager dumpManager,
347             Lazy<SecureSettings> secureSettings,
348             VibratorHelper vibratorHelper,
349             MSDLPlayer msdlPlayer,
350             com.android.systemui.util.time.SystemClock systemClock,
351             VolumeDialogInteractor interactor) {
352         mContext =
353                 new ContextThemeWrapper(context, R.style.volume_dialog_theme);
354         mHandler = new H(looper);
355         mVibratorHelper = vibratorHelper;
356         mMSDLPlayer = msdlPlayer;
357         mSystemClock = systemClock;
358         mShouldListenForJank = shouldListenForJank;
359         mController = volumeDialogController;
360         mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
361         mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
362         mAccessibilityMgr = accessibilityManagerWrapper;
363         mDeviceProvisionedController = deviceProvisionedController;
364         mConfigurationController = configurationController;
365         mMediaOutputDialogManager = mediaOutputDialogManager;
366         mCsdWarningDialogFactory = csdWarningDialogFactory;
367         mIsTv = isTv();
368         mHasSeenODICaptionsTooltip =
369                 Prefs.getBoolean(context, Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, false);
370         mShowLowMediaVolumeIcon =
371             mContext.getResources().getBoolean(R.bool.config_showLowMediaVolumeIcon);
372         mChangeVolumeRowTintWhenInactive =
373             mContext.getResources().getBoolean(R.bool.config_changeVolumeRowTintWhenInactive);
374         mDialogShowAnimationDurationMs =
375             mContext.getResources().getInteger(R.integer.config_dialogShowAnimationDurationMs);
376         mDialogHideAnimationDurationMs =
377             mContext.getResources().getInteger(R.integer.config_dialogHideAnimationDurationMs);
378         mUseBackgroundBlur =
379             mContext.getResources().getBoolean(R.bool.config_volumeDialogUseBackgroundBlur);
380         mInteractionJankMonitor = interactionJankMonitor;
381         mVolumePanelNavigationInteractor = volumePanelNavigationInteractor;
382         mVolumeNavigator = volumeNavigator;
383         mSecureSettings = secureSettings;
384         mDialogTimeoutMillis = DIALOG_TIMEOUT_MILLIS;
385         mVolumePanelFlag = volumePanelFlag;
386         mInteractor = interactor;
387 
388         dumpManager.registerDumpable("VolumeDialogImpl", this);
389 
390         if (mUseBackgroundBlur) {
391             final int dialogRowsViewColorAboveBlur = mContext.getColor(
392                     R.color.volume_dialog_background_color_above_blur);
393             final int dialogRowsViewColorNoBlur = mContext.getColor(
394                     R.color.volume_dialog_background_color);
395             mCrossWindowBlurEnabledListener = (enabled) -> {
396                 mDialogRowsViewBackground.setColor(
397                         enabled ? dialogRowsViewColorAboveBlur : dialogRowsViewColorNoBlur);
398                 mDialogRowsView.invalidate();
399             };
400         }
401 
402         initDimens();
403 
404         mOrientation = mContext.getResources().getConfiguration().orientation;
405         mDevicePostureController = devicePostureController;
406         if (mDevicePostureController != null) {
407             int initialPosture = mDevicePostureController.getDevicePosture();
408             mDevicePosture = initialPosture;
409             mDevicePostureControllerCallback = this::onPostureChanged;
410         } else {
411             mDevicePostureControllerCallback = null;
412         }
413     }
414 
415     /**
416      * Adjust the dialog location on the screen in order to avoid drawing on the hinge.
417      */
adjustPositionOnScreen()418     private void adjustPositionOnScreen() {
419         final boolean isPortrait = mOrientation == Configuration.ORIENTATION_PORTRAIT;
420         final boolean isHalfOpen =
421                 mDevicePosture == DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
422         final boolean isTabletop = isPortrait && isHalfOpen;
423         WindowManager.LayoutParams lp =  mWindow.getAttributes();
424         int gravity = isTabletop ? (mOriginalGravity | Gravity.TOP) : mOriginalGravity;
425         mWindowGravity = Gravity.getAbsoluteGravity(gravity,
426                 mContext.getResources().getConfiguration().getLayoutDirection());
427         lp.gravity = mWindowGravity;
428     }
429 
getWindowGravity()430     @VisibleForTesting int getWindowGravity() {
431         return mWindowGravity;
432     }
433 
434     @Override
onUiModeChanged()435     public void onUiModeChanged() {
436         mContext.getTheme().applyStyle(mContext.getThemeResId(), true);
437     }
438 
init(int windowType, Callback callback)439     public void init(int windowType, Callback callback) {
440         initDialog(mActivityManager.getLockTaskModeState());
441 
442         mController.addCallback(mControllerCallbackH, mHandler);
443         mController.getState();
444 
445         mConfigurationController.addCallback(this);
446 
447         if (mDevicePostureController != null) {
448             mDevicePostureController.addCallback(mDevicePostureControllerCallback);
449         }
450     }
451 
452     @Override
destroy()453     public void destroy() {
454         Log.d(TAG, "destroy() called");
455         mController.removeCallback(mControllerCallbackH);
456         mHandler.removeCallbacksAndMessages(null);
457         mConfigurationController.removeCallback(this);
458         if (mDevicePostureController != null) {
459             mDevicePostureController.removeCallback(mDevicePostureControllerCallback);
460         }
461     }
462 
463     @Override
onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo internalInsetsInfo)464     public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo internalInsetsInfo) {
465         // Set touchable region insets on the root dialog view. This tells WindowManager that
466         // touches outside of this region should not be delivered to the volume window, and instead
467         // go to the window below. This is the only way to do this - returning false in
468         // onDispatchTouchEvent results in the event being ignored entirely, rather than passed to
469         // the next window.
470         internalInsetsInfo.setTouchableInsets(
471                 ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
472 
473         mTouchableRegion.setEmpty();
474 
475         // Set the touchable region to the union of all child view bounds and the live caption
476         // tooltip. We don't use touches on the volume dialog container itself, so this is fine.
477         for (int i = 0; i < mDialogView.getChildCount(); i++) {
478             unionViewBoundstoTouchableRegion(mDialogView.getChildAt(i));
479         }
480 
481         if (mODICaptionsTooltipView != null && mODICaptionsTooltipView.getVisibility() == VISIBLE) {
482             unionViewBoundstoTouchableRegion(mODICaptionsTooltipView);
483         }
484 
485         internalInsetsInfo.touchableRegion.set(mTouchableRegion);
486     }
487 
unionViewBoundstoTouchableRegion(final View view)488     private void unionViewBoundstoTouchableRegion(final View view) {
489         final int[] locInWindow = new int[2];
490         view.getLocationInWindow(locInWindow);
491 
492         float x = locInWindow[0];
493         float y = locInWindow[1];
494 
495         // The ringer and rows container has extra height at the top to fit the expanded ringer
496         // drawer. This area should not be touchable unless the ringer drawer is open.
497         // In landscape the ringer expands to the left and it has to be ensured that if there
498         // are multiple rows they are touchable.
499         if (view == mTopContainer && !mIsRingerDrawerOpen) {
500             if (!isLandscape()) {
501                 y += getRingerDrawerOpenExtraSize();
502             } else if (getRingerDrawerOpenExtraSize() > getVisibleRowsExtraSize()) {
503                 x += (getRingerDrawerOpenExtraSize() - getVisibleRowsExtraSize());
504             }
505         }
506 
507         mTouchableRegion.op(
508                 (int) x,
509                 (int) y,
510                 locInWindow[0] + view.getWidth(),
511                 locInWindow[1] + view.getHeight(),
512                 Region.Op.UNION);
513     }
514 
initDialog(int lockTaskModeState)515     private void initDialog(int lockTaskModeState) {
516         Log.d(TAG, "initDialog: called!");
517         mDialog = new CustomDialog(mContext);
518         initDimens();
519 
520         mConfigurableTexts = new ConfigurableTexts(mContext);
521         mHovering = false;
522         mShowing = false;
523         mWindow = mDialog.getWindow();
524         mWindow.requestFeature(Window.FEATURE_NO_TITLE);
525         mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
526         mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND
527                 | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
528         mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
529                 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
530                 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
531                 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
532                 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
533         mWindow.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY);
534         mWindow.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
535         mWindow.setWindowAnimations(com.android.internal.R.style.Animation_Toast);
536         WindowManager.LayoutParams lp = mWindow.getAttributes();
537         lp.format = PixelFormat.TRANSLUCENT;
538         lp.setTitle(VolumeDialogImpl.class.getSimpleName());
539         lp.windowAnimations = -1;
540 
541         mOriginalGravity = mContext.getResources().getInteger(R.integer.volume_dialog_gravity);
542         mWindowGravity = Gravity.getAbsoluteGravity(mOriginalGravity,
543                 mContext.getResources().getConfiguration().getLayoutDirection());
544         lp.gravity = mWindowGravity;
545 
546         mWindow.setAttributes(lp);
547         mWindow.setLayout(WRAP_CONTENT, WRAP_CONTENT);
548         mDialog.setContentView(R.layout.volume_dialog_legacy);
549         mDialogView = mDialog.findViewById(R.id.volume_dialog);
550         mDialogView.setAlpha(0);
551         mDialogTimeoutMillis = mSecureSettings.get().getInt(
552                 Settings.Secure.VOLUME_DIALOG_DISMISS_TIMEOUT, DIALOG_TIMEOUT_MILLIS);
553         mDialog.setCanceledOnTouchOutside(true);
554         mDialog.setOnShowListener(dialog -> {
555             mDialogView.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
556             if (!shouldSlideInVolumeTray()) {
557                 mDialogView.setTranslationX(
558                         (isWindowGravityLeft() ? -1 : 1) * mDialogView.getWidth() / 2.0f);
559             }
560             mDialogView.setAlpha(0);
561             mDialogView.animate()
562                     .alpha(1)
563                     .translationX(0)
564                     .setDuration(mDialogShowAnimationDurationMs)
565                     .setListener(getJankListener(getDialogView(), TYPE_SHOW, mDialogTimeoutMillis))
566                     .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator())
567                     .withEndAction(() -> {
568                         if (!Prefs.getBoolean(mContext, Prefs.Key.TOUCHED_RINGER_TOGGLE, false)) {
569                             if (mRingerIcon != null) {
570                                 mRingerIcon.postOnAnimationDelayed(
571                                         getSinglePressFor(mRingerIcon), 1500);
572                             }
573                         }
574                     })
575                     .start();
576         });
577 
578         mDialog.setOnDismissListener(dialogInterface ->
579                 mDialogView
580                         .getViewTreeObserver()
581                         .removeOnComputeInternalInsetsListener(VolumeDialogImpl.this));
582 
583         mDialogView.setOnHoverListener((v, event) -> {
584             int action = event.getActionMasked();
585             mHovering = (action == MotionEvent.ACTION_HOVER_ENTER)
586                     || (action == MotionEvent.ACTION_HOVER_MOVE);
587             rescheduleTimeoutH();
588             return true;
589         });
590 
591         mDialogRowsView = mDialog.findViewById(R.id.volume_dialog_rows);
592         if (mUseBackgroundBlur) {
593             mDialogView.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
594                 @Override
595                 public void onViewAttachedToWindow(View v) {
596                     mWindow.getWindowManager().addCrossWindowBlurEnabledListener(
597                             mCrossWindowBlurEnabledListener);
598 
599                     mDialogRowsViewBackground = v.getViewRootImpl().createBackgroundBlurDrawable();
600 
601                     final Resources resources = mContext.getResources();
602                     mDialogRowsViewBackground.setCornerRadius(
603                             mContext.getResources().getDimensionPixelSize(Utils.getThemeAttr(
604                                     mContext, android.R.attr.dialogCornerRadius)));
605                     mDialogRowsViewBackground.setBlurRadius(resources.getDimensionPixelSize(
606                             R.dimen.volume_dialog_background_blur_radius));
607                     mDialogRowsView.setBackground(mDialogRowsViewBackground);
608                 }
609 
610                 @Override
611                 public void onViewDetachedFromWindow(View v) {
612                     mWindow.getWindowManager().removeCrossWindowBlurEnabledListener(
613                             mCrossWindowBlurEnabledListener);
614                 }
615             });
616         }
617 
618         mDialogRowsViewContainer = mDialogView.findViewById(R.id.volume_dialog_rows_container);
619         mTopContainer = mDialogView.findViewById(R.id.volume_dialog_top_container);
620         mRingerAndDrawerContainer = mDialogView.findViewById(
621                 R.id.volume_ringer_and_drawer_container);
622 
623         if (mRingerAndDrawerContainer != null) {
624             if (isLandscape()) {
625                 // In landscape, we need to add padding to the bottom of the ringer drawer so that
626                 // when it expands to the left, it doesn't overlap any additional volume rows.
627                 mRingerAndDrawerContainer.setPadding(
628                         mRingerAndDrawerContainer.getPaddingLeft(),
629                         mRingerAndDrawerContainer.getPaddingTop(),
630                         mRingerAndDrawerContainer.getPaddingRight(),
631                         mRingerRowsPadding);
632 
633                 // Since the ringer drawer is expanding to the left, outside of the background of
634                 // the dialog, it needs its own rounded background drawable. We also need that
635                 // background to be rounded on all sides. We'll use a background rounded on all four
636                 // corners, and then extend the container's background later to fill in the bottom
637                 // corners when the drawer is closed.
638                 mRingerAndDrawerContainer.setBackgroundDrawable(
639                         mContext.getDrawable(R.drawable.volume_background_top_rounded));
640             }
641 
642             // Post to wait for layout so that the background bounds are set.
643             mRingerAndDrawerContainer.post(() -> {
644                 final LayerDrawable ringerAndDrawerBg =
645                         (LayerDrawable) mRingerAndDrawerContainer.getBackground();
646 
647                 // Retrieve the ShapeDrawable from within the background - this is what we will
648                 // animate up and down when the drawer is opened/closed.
649                 if (ringerAndDrawerBg != null && ringerAndDrawerBg.getNumberOfLayers() > 0) {
650                     mRingerAndDrawerContainerBackground = ringerAndDrawerBg.getDrawable(0);
651 
652                     updateBackgroundForDrawerClosedAmount();
653                     setTopContainerBackgroundDrawable();
654                 }
655             });
656         }
657 
658         mRinger = mDialog.findViewById(R.id.ringer);
659         if (mRinger != null) {
660             mRingerIcon = mRinger.findViewById(R.id.ringer_icon);
661         }
662 
663         if (Flags.hideRingerButtonInSingleVolumeMode() && AudioSystem.isSingleVolume(mContext)) {
664             mRingerAndDrawerContainer.setVisibility(INVISIBLE);
665             mRinger.setVisibility(INVISIBLE);
666         }
667 
668         mSelectedRingerIcon = mDialog.findViewById(R.id.volume_new_ringer_active_icon);
669         mSelectedRingerContainer = mDialog.findViewById(
670                 R.id.volume_new_ringer_active_icon_container);
671 
672         mRingerDrawerMute = mDialog.findViewById(R.id.volume_drawer_mute);
673         mRingerDrawerNormal = mDialog.findViewById(R.id.volume_drawer_normal);
674         mRingerDrawerVibrate = mDialog.findViewById(R.id.volume_drawer_vibrate);
675         mRingerDrawerMuteIcon = mDialog.findViewById(R.id.volume_drawer_mute_icon);
676         mRingerDrawerVibrateIcon = mDialog.findViewById(R.id.volume_drawer_vibrate_icon);
677         mRingerDrawerNormalIcon = mDialog.findViewById(R.id.volume_drawer_normal_icon);
678         mRingerDrawerNewSelectionBg = mDialog.findViewById(R.id.volume_drawer_selection_background);
679 
680         if (mRingerDrawerMuteIcon != null) {
681             mRingerDrawerMuteIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
682         }
683         if (mRingerDrawerNormalIcon != null) {
684             mRingerDrawerNormalIcon.setImageResource(mVolumeRingerIconDrawableId);
685         }
686 
687         setupRingerDrawer();
688 
689         mODICaptionsView = mDialog.findViewById(R.id.odi_captions);
690         if (mODICaptionsView != null) {
691             mODICaptionsIcon = mODICaptionsView.findViewById(R.id.odi_captions_icon);
692         }
693         mODICaptionsTooltipViewStub = mDialog.findViewById(R.id.odi_captions_tooltip_stub);
694         if (mHasSeenODICaptionsTooltip && mODICaptionsTooltipViewStub != null) {
695             mDialogView.removeView(mODICaptionsTooltipViewStub);
696             mODICaptionsTooltipViewStub = null;
697         }
698 
699         mSettingsView = mDialog.findViewById(R.id.settings_container);
700         mSettingsIcon = mDialog.findViewById(R.id.settings);
701 
702         if (mRows.isEmpty()) {
703             if (!AudioSystem.isSingleVolume(mContext)) {
704                 addRow(STREAM_ACCESSIBILITY, R.drawable.ic_volume_accessibility,
705                         R.drawable.ic_volume_accessibility, true, false);
706             }
707             addRow(AudioManager.STREAM_MUSIC,
708                     R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true, true);
709             if (!AudioSystem.isSingleVolume(mContext)) {
710                 addRow(AudioManager.STREAM_RING, R.drawable.ic_ring_volume,
711                         R.drawable.ic_ring_volume_off, true, false);
712                 addRow(AudioManager.STREAM_NOTIFICATION, R.drawable.ic_volume_ringer,
713                         R.drawable.ic_volume_off, true, false);
714                 addRow(STREAM_ALARM,
715                         R.drawable.ic_alarm, R.drawable.ic_volume_alarm_mute, true, false);
716                 addRow(AudioManager.STREAM_VOICE_CALL,
717                         com.android.internal.R.drawable.ic_phone,
718                         com.android.internal.R.drawable.ic_phone, false, false);
719                 addRow(AudioManager.STREAM_SYSTEM, R.drawable.ic_volume_system,
720                         R.drawable.ic_volume_system_mute, false, false);
721             }
722         } else {
723             addExistingRows();
724         }
725 
726         updateRowsH(getActiveRow());
727         initRingerH();
728         initSettingsH(lockTaskModeState);
729         initODICaptionsH();
730         mAccessibility.init();
731     }
732 
isWindowGravityLeft()733     private boolean isWindowGravityLeft() {
734         return (mWindowGravity & Gravity.LEFT) == Gravity.LEFT;
735     }
736 
initDimens()737     private void initDimens() {
738         mDialogWidth = mContext.getResources().getDimensionPixelSize(
739                 R.dimen.volume_dialog_panel_width);
740         mDialogCornerRadius = mContext.getResources().getDimensionPixelSize(
741                 R.dimen.volume_dialog_panel_width_half);
742         mRingerDrawerItemSize = mContext.getResources().getDimensionPixelSize(
743                 R.dimen.volume_ringer_drawer_item_size);
744         mRingerRowsPadding = mContext.getResources().getDimensionPixelSize(
745                 R.dimen.volume_dialog_ringer_rows_padding);
746         mShowVibrate = mController.hasVibrator();
747 
748         // Normal, mute, and possibly vibrate.
749         mRingerCount = mShowVibrate ? 3 : 2;
750     }
751 
getDialogView()752     protected ViewGroup getDialogView() {
753         return mDialogView;
754     }
755 
getAlphaAttr(int attr)756     private int getAlphaAttr(int attr) {
757         TypedArray ta = mContext.obtainStyledAttributes(new int[]{attr});
758         float alpha = ta.getFloat(0, 0);
759         ta.recycle();
760         return (int) (alpha * 255);
761     }
762 
shouldSlideInVolumeTray()763     private boolean shouldSlideInVolumeTray() {
764         return mContext.getDisplay().getRotation() != RotationPolicy.NATURAL_ROTATION;
765     }
766 
isLandscape()767     private boolean isLandscape() {
768         return mContext.getResources().getConfiguration().orientation ==
769                 Configuration.ORIENTATION_LANDSCAPE;
770     }
771 
isRtl()772     private boolean isRtl() {
773         return mContext.getResources().getConfiguration().getLayoutDirection()
774                 == LAYOUT_DIRECTION_RTL;
775     }
776 
setStreamImportant(int stream, boolean important)777     public void setStreamImportant(int stream, boolean important) {
778         mHandler.obtainMessage(H.SET_STREAM_IMPORTANT, stream, important ? 1 : 0).sendToTarget();
779     }
780 
setAutomute(boolean automute)781     public void setAutomute(boolean automute) {
782         if (mAutomute == automute) return;
783         mAutomute = automute;
784         mHandler.sendEmptyMessage(H.RECHECK_ALL);
785     }
786 
setSilentMode(boolean silentMode)787     public void setSilentMode(boolean silentMode) {
788         if (mSilentMode == silentMode) return;
789         mSilentMode = silentMode;
790         mHandler.sendEmptyMessage(H.RECHECK_ALL);
791     }
792 
addRow(int stream, int iconRes, int iconMuteRes, boolean important, boolean defaultStream)793     private void addRow(int stream, int iconRes, int iconMuteRes, boolean important,
794             boolean defaultStream) {
795         addRow(stream, iconRes, iconMuteRes, important, defaultStream, false);
796     }
797 
addRow(int stream, int iconRes, int iconMuteRes, boolean important, boolean defaultStream, boolean dynamic)798     private void addRow(int stream, int iconRes, int iconMuteRes, boolean important,
799             boolean defaultStream, boolean dynamic) {
800         if (D.BUG) Slog.d(TAG, "Adding row for stream " + stream);
801         VolumeRow row = new VolumeRow();
802         initRow(row, stream, iconRes, iconMuteRes, important, defaultStream);
803         mDialogRowsView.addView(row.view);
804         mRows.add(row);
805     }
806 
addExistingRows()807     private void addExistingRows() {
808         int N = mRows.size();
809         for (int i = 0; i < N; i++) {
810             final VolumeRow row = mRows.get(i);
811             initRow(row, row.stream, row.iconRes, row.iconMuteRes, row.important,
812                     row.defaultStream);
813             mDialogRowsView.addView(row.view);
814             updateVolumeRowH(row);
815         }
816     }
817 
getActiveRow()818     private VolumeRow getActiveRow() {
819         for (VolumeRow row : mRows) {
820             if (row.stream == mActiveStream) {
821                 return row;
822             }
823         }
824         for (VolumeRow row : mRows) {
825             if (row.stream == STREAM_MUSIC) {
826                 return row;
827             }
828         }
829         return mRows.get(0);
830     }
831 
findRow(int stream)832     private VolumeRow findRow(int stream) {
833         for (VolumeRow row : mRows) {
834             if (row.stream == stream) return row;
835         }
836         return null;
837     }
838 
839     /**
840      * Print dump info for debugging.
841      */
dump(PrintWriter writer, String[] unusedArgs)842     public void dump(PrintWriter writer, String[] unusedArgs) {
843         writer.println(VolumeDialogImpl.class.getSimpleName() + " state:");
844         writer.print("  mShowing: "); writer.println(mShowing);
845         writer.print("  mIsAnimatingDismiss: "); writer.println(mIsAnimatingDismiss);
846         writer.print("  mActiveStream: "); writer.println(mActiveStream);
847         writer.print("  mDynamic: "); writer.println(mDynamic);
848         writer.print("  mAutomute: "); writer.println(mAutomute);
849         writer.print("  mSilentMode: "); writer.println(mSilentMode);
850     }
851 
getVolumeFromProgress(StreamState state, SeekBar seekBar, int progress)852     private static int getVolumeFromProgress(StreamState state, SeekBar seekBar, int progress) {
853         return (int) Util.translateToRange(progress, seekBar.getMin(), seekBar.getMax(),
854                 state.levelMin, state.levelMax);
855     }
856 
getProgressFromVolume(StreamState state, SeekBar seekBar, int volume)857     private static int getProgressFromVolume(StreamState state, SeekBar seekBar, int volume) {
858         return (int) Util.translateToRange(volume, state.levelMin, state.levelMax, seekBar.getMin(),
859                 seekBar.getMax());
860     }
861 
862     @SuppressLint("InflateParams")
initRow(final VolumeRow row, final int stream, int iconRes, int iconMuteRes, boolean important, boolean defaultStream)863     private void initRow(final VolumeRow row, final int stream, int iconRes, int iconMuteRes,
864             boolean important, boolean defaultStream) {
865         row.stream = stream;
866         row.iconRes = iconRes;
867         row.iconMuteRes = iconMuteRes;
868         row.important = important;
869         row.defaultStream = defaultStream;
870         row.view = mDialog.getLayoutInflater().inflate(R.layout.volume_dialog_row, null);
871         row.view.setId(row.stream);
872         row.view.setTag(row);
873         row.header = row.view.findViewById(R.id.volume_row_header);
874         row.header.setId(20 * row.stream);
875         if (stream == STREAM_ACCESSIBILITY) {
876             row.header.setFilters(new InputFilter[] {new InputFilter.LengthFilter(13)});
877         }
878         row.slider = row.view.findViewById(R.id.volume_row_slider);
879         addSliderHapticsToRow(row);
880         row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
881         row.number = row.view.findViewById(R.id.volume_number);
882         row.slider.setAccessibilityDelegate(
883                 new VolumeDialogSeekBarAccessibilityDelegate(DISPLAY_RANGE_MULTIPLIER));
884 
885         row.anim = null;
886 
887         final LayerDrawable seekbarDrawable =
888                 (LayerDrawable) mContext.getDrawable(R.drawable.volume_row_seekbar);
889 
890         final LayerDrawable seekbarProgressDrawable = (LayerDrawable)
891                 ((RoundedCornerProgressDrawable) seekbarDrawable.findDrawableByLayerId(
892                         android.R.id.progress)).getDrawable();
893 
894         row.sliderProgressSolid = seekbarProgressDrawable.findDrawableByLayerId(
895                 R.id.volume_seekbar_progress_solid);
896         final Drawable sliderProgressIcon = seekbarProgressDrawable.findDrawableByLayerId(
897                         R.id.volume_seekbar_progress_icon);
898         row.sliderProgressIcon = sliderProgressIcon != null ? (AlphaTintDrawableWrapper)
899                 ((RotateDrawable) sliderProgressIcon).getDrawable() : null;
900 
901         row.slider.setProgressDrawable(seekbarDrawable);
902 
903         row.icon = row.view.findViewById(R.id.volume_row_icon);
904 
905         row.setIcon(iconRes, mContext.getTheme());
906 
907         if (row.icon != null) {
908             if (row.stream != AudioSystem.STREAM_ACCESSIBILITY) {
909                 row.icon.setOnClickListener(v -> {
910                     Events.writeEvent(Events.EVENT_ICON_CLICK, row.stream, row.iconState);
911                     mController.setActiveStream(row.stream);
912                     if (row.stream == AudioManager.STREAM_RING) {
913                         final boolean hasVibrator = mController.hasVibrator();
914                         if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) {
915                             if (hasVibrator) {
916                                 mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false);
917                             } else {
918                                 final boolean wasZero = row.ss.level == 0;
919                                 mController.setStreamVolume(stream,
920                                         wasZero ? row.lastAudibleLevel : 0);
921                             }
922                         } else {
923                             mController.setRingerMode(
924                                     AudioManager.RINGER_MODE_NORMAL, false);
925                             if (row.ss.level == 0) {
926                                 mController.setStreamVolume(stream, 1);
927                             }
928                         }
929                     } else {
930                         final boolean vmute = row.ss.level == row.ss.levelMin;
931                         mController.setStreamVolume(stream,
932                                 vmute ? row.lastAudibleLevel : row.ss.levelMin);
933                     }
934                     row.userAttempt = 0;  // reset the grace period, slider updates immediately
935                 });
936             } else {
937                 row.icon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
938             }
939         }
940     }
941 
addSliderHapticsToRow(VolumeRow row)942     private void addSliderHapticsToRow(VolumeRow row) {
943         row.createPlugin(row.slider, mVibratorHelper, mMSDLPlayer, mSystemClock);
944         HapticSliderViewBinder.bind(row.slider, row.mHapticPlugin);
945     }
946 
addSliderHapticsToRows()947     @VisibleForTesting void addSliderHapticsToRows() {
948         for (VolumeRow row: mRows) {
949             addSliderHapticsToRow(row);
950         }
951     }
952 
removeDismissMessages()953     @VisibleForTesting void removeDismissMessages() {
954         mHandler.removeMessages(H.DISMISS);
955     }
956 
setRingerMode(int newRingerMode)957     private void setRingerMode(int newRingerMode) {
958         Events.writeEvent(Events.EVENT_RINGER_TOGGLE, newRingerMode);
959         incrementManualToggleCount();
960         updateRingerH();
961         provideTouchFeedbackH(newRingerMode);
962         mController.setRingerMode(newRingerMode, false);
963         maybeShowToastH(newRingerMode);
964     }
965 
setupRingerDrawer()966     private void setupRingerDrawer() {
967         mRingerDrawerContainer = mDialog.findViewById(R.id.volume_drawer_container);
968 
969         if (mRingerDrawerContainer == null) {
970             return;
971         }
972 
973         if (!mShowVibrate) {
974             mRingerDrawerVibrate.setVisibility(GONE);
975         }
976 
977         // In portrait, add padding to the bottom to account for the height of the open ringer
978         // drawer.
979         if (!isLandscape()) {
980             mDialogView.setPadding(
981                     mDialogView.getPaddingLeft(),
982                     mDialogView.getPaddingTop(),
983                     mDialogView.getPaddingRight(),
984                     mDialogView.getPaddingBottom() + getRingerDrawerOpenExtraSize());
985         } else {
986             mDialogView.setPadding(
987                     mDialogView.getPaddingLeft() + getRingerDrawerOpenExtraSize(),
988                     mDialogView.getPaddingTop(),
989                     mDialogView.getPaddingRight(),
990                     mDialogView.getPaddingBottom());
991         }
992 
993         ((LinearLayout) mRingerDrawerContainer.findViewById(R.id.volume_drawer_options))
994                 .setOrientation(isLandscape() ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);
995 
996         mSelectedRingerContainer.setOnClickListener(view -> {
997             if (mIsRingerDrawerOpen) {
998                 hideRingerDrawer();
999             } else {
1000                 showRingerDrawer();
1001             }
1002         });
1003         updateSelectedRingerContainerDescription(mIsRingerDrawerOpen);
1004 
1005         mRingerDrawerVibrate.setOnClickListener(
1006                 new RingerDrawerItemClickListener(RINGER_MODE_VIBRATE));
1007         mRingerDrawerMute.setOnClickListener(
1008                 new RingerDrawerItemClickListener(RINGER_MODE_SILENT));
1009         mRingerDrawerNormal.setOnClickListener(
1010                 new RingerDrawerItemClickListener(RINGER_MODE_NORMAL));
1011 
1012         final int unselectedColor = Utils.getColorAccentDefaultColor(mContext);
1013         final int selectedColor = Utils.getColorAttrDefaultColor(
1014                 mContext, android.R.attr.colorBackgroundFloating);
1015 
1016         // Add an update listener that animates the deselected icon to the unselected color, and the
1017         // selected icon to the selected color.
1018         mRingerDrawerIconColorAnimator.addUpdateListener(
1019                 anim -> {
1020                     final float currentValue = (float) anim.getAnimatedValue();
1021                     final int curUnselectedColor = (int) ArgbEvaluator.getInstance().evaluate(
1022                             currentValue, selectedColor, unselectedColor);
1023                     final int curSelectedColor = (int) ArgbEvaluator.getInstance().evaluate(
1024                             currentValue, unselectedColor, selectedColor);
1025 
1026                     mRingerDrawerIconAnimatingDeselected.setColorFilter(curUnselectedColor);
1027                     mRingerDrawerIconAnimatingSelected.setColorFilter(curSelectedColor);
1028                 });
1029         mRingerDrawerIconColorAnimator.addListener(new AnimatorListenerAdapter() {
1030             @Override
1031             public void onAnimationEnd(Animator animation) {
1032                 mRingerDrawerIconAnimatingDeselected.clearColorFilter();
1033                 mRingerDrawerIconAnimatingSelected.clearColorFilter();
1034             }
1035         });
1036         mRingerDrawerIconColorAnimator.setDuration(DRAWER_ANIMATION_DURATION_SHORT);
1037 
1038         mAnimateUpBackgroundToMatchDrawer.addUpdateListener(valueAnimator -> {
1039             mRingerDrawerClosedAmount = (float) valueAnimator.getAnimatedValue();
1040             updateBackgroundForDrawerClosedAmount();
1041         });
1042     }
1043 
getDrawerIconViewForMode(int mode)1044     private ImageView getDrawerIconViewForMode(int mode) {
1045         if (mode == RINGER_MODE_VIBRATE) {
1046             return mRingerDrawerVibrateIcon;
1047         } else if (mode == RINGER_MODE_SILENT) {
1048             return mRingerDrawerMuteIcon;
1049         } else {
1050             return mRingerDrawerNormalIcon;
1051         }
1052     }
1053 
1054     /**
1055      * Translation to apply form the origin (either top or left) to overlap the selection background
1056      * with the given mode in the drawer.
1057      */
getTranslationInDrawerForRingerMode(int mode)1058     private float getTranslationInDrawerForRingerMode(int mode) {
1059         return mode == RINGER_MODE_VIBRATE
1060                 ? -mRingerDrawerItemSize * 2
1061                 : mode == RINGER_MODE_SILENT
1062                         ? -mRingerDrawerItemSize
1063                         : 0;
1064     }
1065 
getSelectedRingerContainerDescription()1066     @VisibleForTesting String getSelectedRingerContainerDescription() {
1067         return mSelectedRingerContainer == null ? null :
1068                 mSelectedRingerContainer.getContentDescription().toString();
1069     }
1070 
toggleRingerDrawer(boolean show)1071     @VisibleForTesting void toggleRingerDrawer(boolean show) {
1072         if (show) {
1073             showRingerDrawer();
1074         } else {
1075             hideRingerDrawer();
1076         }
1077     }
1078 
1079     /** Animates in the ringer drawer. */
showRingerDrawer()1080     private void showRingerDrawer() {
1081         if (mIsRingerDrawerOpen) {
1082             return;
1083         }
1084 
1085         // Show all ringer icons except the currently selected one, since we're going to animate the
1086         // ringer button to that position.
1087         mRingerDrawerVibrateIcon.setVisibility(
1088                 mState.ringerModeInternal == RINGER_MODE_VIBRATE ? INVISIBLE : VISIBLE);
1089         mRingerDrawerMuteIcon.setVisibility(
1090                 mState.ringerModeInternal == RINGER_MODE_SILENT ? INVISIBLE : VISIBLE);
1091         mRingerDrawerNormalIcon.setVisibility(
1092                 mState.ringerModeInternal == RINGER_MODE_NORMAL ? INVISIBLE : VISIBLE);
1093 
1094         // Hide the selection background - we use this to show a selection when one is
1095         // tapped, so it should be invisible until that happens. However, position it below
1096         // the currently selected ringer so that it's ready to animate.
1097         mRingerDrawerNewSelectionBg.setAlpha(0f);
1098 
1099         if (!isLandscape()) {
1100             mRingerDrawerNewSelectionBg.setTranslationY(
1101                     getTranslationInDrawerForRingerMode(mState.ringerModeInternal));
1102         } else {
1103             mRingerDrawerNewSelectionBg.setTranslationX(
1104                     getTranslationInDrawerForRingerMode(mState.ringerModeInternal));
1105         }
1106 
1107         // Move the drawer so that the top/rightmost ringer choice overlaps with the selected ringer
1108         // icon.
1109         if (!isLandscape()) {
1110             mRingerDrawerContainer.setTranslationY(mRingerDrawerItemSize * (mRingerCount - 1));
1111         } else {
1112             mRingerDrawerContainer.setTranslationX(mRingerDrawerItemSize * (mRingerCount - 1));
1113         }
1114         mRingerDrawerContainer.setAlpha(0f);
1115         mRingerDrawerContainer.setVisibility(VISIBLE);
1116 
1117         final int ringerDrawerAnimationDuration = mState.ringerModeInternal == RINGER_MODE_VIBRATE
1118                 ? DRAWER_ANIMATION_DURATION_SHORT
1119                 : DRAWER_ANIMATION_DURATION;
1120 
1121         // Animate the drawer up and visible.
1122         mRingerDrawerContainer.animate()
1123                 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
1124                 // Vibrate is way farther up, so give the selected ringer icon a head start if
1125                 // vibrate is selected.
1126                 .setDuration(ringerDrawerAnimationDuration)
1127                 .setStartDelay(mState.ringerModeInternal == RINGER_MODE_VIBRATE
1128                         ? DRAWER_ANIMATION_DURATION - DRAWER_ANIMATION_DURATION_SHORT
1129                         : 0)
1130                 .alpha(1f)
1131                 .translationX(0f)
1132                 .translationY(0f)
1133                 .start();
1134 
1135         // Animate the selected ringer view up to that ringer's position in the drawer.
1136         mSelectedRingerContainer.animate()
1137                 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
1138                 .setDuration(DRAWER_ANIMATION_DURATION)
1139                 .withEndAction(() ->
1140                         getDrawerIconViewForMode(mState.ringerModeInternal).setVisibility(VISIBLE));
1141 
1142         mAnimateUpBackgroundToMatchDrawer.setDuration(ringerDrawerAnimationDuration);
1143         mAnimateUpBackgroundToMatchDrawer.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
1144         mAnimateUpBackgroundToMatchDrawer.start();
1145 
1146         if (!isLandscape()) {
1147             mSelectedRingerContainer.animate()
1148                     .translationY(getTranslationInDrawerForRingerMode(mState.ringerModeInternal))
1149                     .start();
1150         } else {
1151             mSelectedRingerContainer.animate()
1152                     .translationX(getTranslationInDrawerForRingerMode(mState.ringerModeInternal))
1153                     .start();
1154         }
1155 
1156         updateSelectedRingerContainerDescription(true);
1157         mSelectedRingerContainer.setImportantForAccessibility(
1158                 View.IMPORTANT_FOR_ACCESSIBILITY_NO);
1159         mSelectedRingerContainer.clearFocus();
1160         mIsRingerDrawerOpen = true;
1161     }
1162 
1163     /** Animates away the ringer drawer. */
hideRingerDrawer()1164     private void hideRingerDrawer() {
1165 
1166         // If the ringer drawer isn't present, don't try to hide it.
1167         if (mRingerDrawerContainer == null) {
1168             return;
1169         }
1170 
1171         if (!mIsRingerDrawerOpen) {
1172             return;
1173         }
1174 
1175         // Hide the drawer icon for the selected ringer - it's visible in the ringer button and we
1176         // don't want to be able to see it while it animates away.
1177         getDrawerIconViewForMode(mState.ringerModeInternal).setVisibility(INVISIBLE);
1178 
1179         mRingerDrawerContainer.animate()
1180                 .alpha(0f)
1181                 .setDuration(DRAWER_ANIMATION_DURATION)
1182                 .setStartDelay(0)
1183                 .withEndAction(() -> mRingerDrawerContainer.setVisibility(INVISIBLE));
1184 
1185         if (!isLandscape()) {
1186             mRingerDrawerContainer.animate()
1187                     .translationY(mRingerDrawerItemSize * 2)
1188                     .start();
1189         } else {
1190             mRingerDrawerContainer.animate()
1191                     .translationX(mRingerDrawerItemSize * 2)
1192                     .start();
1193         }
1194 
1195         mAnimateUpBackgroundToMatchDrawer.setDuration(DRAWER_ANIMATION_DURATION);
1196         mAnimateUpBackgroundToMatchDrawer.setInterpolator(Interpolators.FAST_OUT_SLOW_IN_REVERSE);
1197         mAnimateUpBackgroundToMatchDrawer.reverse();
1198 
1199         mSelectedRingerContainer.animate()
1200                 .translationX(0f)
1201                 .translationY(0f)
1202                 .start();
1203 
1204         updateSelectedRingerContainerDescription(false);
1205         mSelectedRingerContainer.setImportantForAccessibility(
1206                 View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
1207         mIsRingerDrawerOpen = false;
1208     }
1209 
1210 
1211     /**
1212      * @param open false to set the description when drawer is closed
1213      */
updateSelectedRingerContainerDescription(boolean open)1214     private void updateSelectedRingerContainerDescription(boolean open) {
1215         if (mState == null || mSelectedRingerContainer == null) return;
1216 
1217         String currentMode = mContext.getString(getStringDescriptionResourceForRingerMode(
1218                 mState.ringerModeInternal));
1219         String tapToSelect;
1220 
1221         if (open) {
1222             // When the ringer drawer is open, tapping the currently selected ringer will set the
1223             // ringer to the current ringer mode. Change the content description to that, instead of
1224             // the 'tap to change ringer mode' default.
1225             tapToSelect = "";
1226 
1227         } else {
1228             // When the drawer is closed, tapping the selected ringer drawer will open it, allowing
1229             // the user to change the ringer. The user needs to know that, and also the current mode
1230             currentMode += ", ";
1231             tapToSelect = mContext.getString(R.string.volume_ringer_change);
1232         }
1233 
1234         mSelectedRingerContainer.setContentDescription(currentMode + tapToSelect);
1235     }
1236 
initSettingsH(int lockTaskModeState)1237     private void initSettingsH(int lockTaskModeState) {
1238         if (mSettingsView != null) {
1239             mSettingsView.setVisibility(
1240                     mDeviceProvisionedController.isCurrentUserSetup() &&
1241                             lockTaskModeState == LOCK_TASK_MODE_NONE ? VISIBLE : GONE);
1242         }
1243         if (mSettingsIcon != null) {
1244             mSettingsIcon.setOnClickListener(v -> {
1245                 Events.writeEvent(Events.EVENT_SETTINGS_CLICK);
1246                 dismissH(DISMISS_REASON_SETTINGS_CLICKED);
1247                 mMediaOutputDialogManager.dismiss();
1248                 mVolumeNavigator.openVolumePanel(
1249                         mVolumePanelNavigationInteractor.getVolumePanelRoute());
1250             });
1251         }
1252     }
1253 
initRingerH()1254     public void initRingerH() {
1255         if (mRingerIcon != null) {
1256             mRingerIcon.setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);
1257             mRingerIcon.setOnClickListener(v -> {
1258                 Prefs.putBoolean(mContext, Prefs.Key.TOUCHED_RINGER_TOGGLE, true);
1259                 final StreamState ss = mState.states.get(AudioManager.STREAM_RING);
1260                 if (ss == null) {
1261                     return;
1262                 }
1263                 // normal -> vibrate -> silent -> normal (skip vibrate if device doesn't have
1264                 // a vibrator.
1265                 int newRingerMode;
1266                 final boolean hasVibrator = mController.hasVibrator();
1267                 if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) {
1268                     if (hasVibrator) {
1269                         newRingerMode = AudioManager.RINGER_MODE_VIBRATE;
1270                     } else {
1271                         newRingerMode = AudioManager.RINGER_MODE_SILENT;
1272                     }
1273                 } else if (mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) {
1274                     newRingerMode = AudioManager.RINGER_MODE_SILENT;
1275                 } else {
1276                     newRingerMode = AudioManager.RINGER_MODE_NORMAL;
1277                     if (ss.level == 0) {
1278                         mController.setStreamVolume(AudioManager.STREAM_RING, 1);
1279                     }
1280                 }
1281 
1282                 setRingerMode(newRingerMode);
1283             });
1284         }
1285         updateRingerH();
1286     }
1287 
initODICaptionsH()1288     private void initODICaptionsH() {
1289         if (mODICaptionsIcon != null) {
1290             mODICaptionsIcon.setOnConfirmedTapListener(() -> {
1291                 onCaptionIconClicked();
1292                 Events.writeEvent(Events.EVENT_ODI_CAPTIONS_CLICK);
1293             }, mHandler);
1294         }
1295 
1296         mController.getCaptionsComponentState(false);
1297     }
1298 
checkODICaptionsTooltip(boolean fromDismiss)1299     private void checkODICaptionsTooltip(boolean fromDismiss) {
1300         if (!mHasSeenODICaptionsTooltip && !fromDismiss && mODICaptionsTooltipViewStub != null) {
1301             mController.getCaptionsComponentState(true);
1302         } else {
1303             if (mHasSeenODICaptionsTooltip && fromDismiss && mODICaptionsTooltipView != null) {
1304                 hideCaptionsTooltip();
1305             }
1306         }
1307     }
1308 
showCaptionsTooltip()1309     protected void showCaptionsTooltip() {
1310         if (!mHasSeenODICaptionsTooltip && mODICaptionsTooltipViewStub != null) {
1311             mODICaptionsTooltipView = mODICaptionsTooltipViewStub.inflate();
1312             mODICaptionsTooltipView.findViewById(R.id.dismiss).setOnClickListener(v -> {
1313                 hideCaptionsTooltip();
1314                 Events.writeEvent(Events.EVENT_ODI_CAPTIONS_TOOLTIP_CLICK);
1315             });
1316             mODICaptionsTooltipViewStub = null;
1317             rescheduleTimeoutH();
1318         }
1319 
1320         // We need to wait for layout and then center the caption view. Since the height of the
1321         // dialog is now dynamic (with the variable ringer drawer height changing the height of
1322         // the dialog), we need to do this here in code vs. in XML.
1323         mHandler.post(() -> {
1324             if (mODICaptionsTooltipView != null) {
1325                 mODICaptionsTooltipView.setAlpha(0.0f);
1326 
1327                 final int[] odiTooltipLocation = mODICaptionsTooltipView.getLocationOnScreen();
1328                 final int[] odiButtonLocation = mODICaptionsIcon.getLocationOnScreen();
1329 
1330                 final float heightDiffForCentering =
1331                         (mODICaptionsTooltipView.getHeight() - mODICaptionsIcon.getHeight()) / 2f;
1332 
1333                 mODICaptionsTooltipView.setTranslationY(
1334                         odiButtonLocation[1] - odiTooltipLocation[1] - heightDiffForCentering);
1335 
1336                 mODICaptionsTooltipView.animate()
1337                         .alpha(1.0f)
1338                         .setStartDelay(mDialogShowAnimationDurationMs)
1339                         .withEndAction(() -> {
1340                             if (D.BUG) {
1341                                 Log.d(TAG, "tool:checkODICaptionsTooltip() putBoolean true");
1342                             }
1343                             Prefs.putBoolean(mContext,
1344                                     Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, true);
1345                             mHasSeenODICaptionsTooltip = true;
1346                             if (mODICaptionsIcon != null) {
1347                                 mODICaptionsIcon
1348                                         .postOnAnimation(getSinglePressFor(mODICaptionsIcon));
1349                             }
1350                         })
1351                         .start();
1352             }
1353         });
1354     }
1355 
hideCaptionsTooltip()1356     private void hideCaptionsTooltip() {
1357         if (mODICaptionsTooltipView != null && mODICaptionsTooltipView.getVisibility() == VISIBLE) {
1358             mODICaptionsTooltipView.animate().cancel();
1359             mODICaptionsTooltipView.setAlpha(1.f);
1360             mODICaptionsTooltipView.animate()
1361                     .alpha(0.f)
1362                     .setStartDelay(0)
1363                     .setDuration(mDialogHideAnimationDurationMs)
1364                     .withEndAction(() -> {
1365                         // It might have been nulled out by tryToRemoveCaptionsTooltip.
1366                         if (mODICaptionsTooltipView != null) {
1367                             mODICaptionsTooltipView.setVisibility(INVISIBLE);
1368                         }
1369                     })
1370                     .start();
1371         }
1372     }
1373 
tryToRemoveCaptionsTooltip()1374     protected void tryToRemoveCaptionsTooltip() {
1375         if (mHasSeenODICaptionsTooltip && mODICaptionsTooltipView != null && mDialog != null) {
1376             ViewGroup container = mDialog.findViewById(R.id.volume_dialog_container);
1377             container.removeView(mODICaptionsTooltipView);
1378             mODICaptionsTooltipView = null;
1379         }
1380     }
1381 
updateODICaptionsH(boolean isServiceComponentEnabled, boolean fromTooltip)1382     private void updateODICaptionsH(boolean isServiceComponentEnabled, boolean fromTooltip) {
1383         // don't show captions view when the new volume panel is enabled.
1384         isServiceComponentEnabled =
1385                 isServiceComponentEnabled && !mVolumePanelFlag.canUseNewVolumePanel();
1386         if (mODICaptionsView != null) {
1387             mODICaptionsView.setVisibility(isServiceComponentEnabled ? VISIBLE : GONE);
1388         }
1389 
1390         if (!isServiceComponentEnabled) return;
1391 
1392         checkEnabledStateForCaptionsIconUpdate();
1393         if (fromTooltip) showCaptionsTooltip();
1394     }
1395 
updateCaptionsEnabledH(boolean isCaptionsEnabled, boolean checkForSwitchState)1396     private void updateCaptionsEnabledH(boolean isCaptionsEnabled, boolean checkForSwitchState) {
1397         if (checkForSwitchState) {
1398             mController.setCaptionsEnabledState(!isCaptionsEnabled);
1399         } else {
1400             updateCaptionsIcon(isCaptionsEnabled);
1401         }
1402     }
1403 
checkEnabledStateForCaptionsIconUpdate()1404     private void checkEnabledStateForCaptionsIconUpdate() {
1405         mController.getCaptionsEnabledState(false);
1406     }
1407 
updateCaptionsIcon(boolean isCaptionsEnabled)1408     private void updateCaptionsIcon(boolean isCaptionsEnabled) {
1409         if (mODICaptionsIcon.getCaptionsEnabled() != isCaptionsEnabled) {
1410             mHandler.post(mODICaptionsIcon.setCaptionsEnabled(isCaptionsEnabled));
1411         }
1412     }
1413 
onCaptionIconClicked()1414     private void onCaptionIconClicked() {
1415         mController.getCaptionsEnabledState(true);
1416     }
1417 
incrementManualToggleCount()1418     private void incrementManualToggleCount() {
1419         ContentResolver cr = mContext.getContentResolver();
1420         int ringerCount = Settings.Secure.getInt(cr, Settings.Secure.MANUAL_RINGER_TOGGLE_COUNT, 0);
1421         Settings.Secure.putInt(cr, Settings.Secure.MANUAL_RINGER_TOGGLE_COUNT, ringerCount + 1);
1422     }
1423 
provideTouchFeedbackH(int newRingerMode)1424     private void provideTouchFeedbackH(int newRingerMode) {
1425         VibrationEffect effect = null;
1426         switch (newRingerMode) {
1427             case RINGER_MODE_NORMAL:
1428                 mController.scheduleTouchFeedback();
1429                 break;
1430             case RINGER_MODE_SILENT:
1431                 effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
1432                 break;
1433             case RINGER_MODE_VIBRATE:
1434                 // Feedback handled by onStateChange, for feedback both when user toggles
1435                 // directly in volume dialog, or drags slider to a value of 0 in settings.
1436                 break;
1437             default:
1438                 effect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
1439         }
1440         if (effect != null) {
1441             mController.vibrate(effect);
1442         }
1443     }
1444 
maybeShowToastH(int newRingerMode)1445     private void maybeShowToastH(int newRingerMode) {
1446         int seenToastCount = Prefs.getInt(mContext, Prefs.Key.SEEN_RINGER_GUIDANCE_COUNT, 0);
1447 
1448         if (seenToastCount > VolumePrefs.SHOW_RINGER_TOAST_COUNT) {
1449             return;
1450         }
1451         CharSequence toastText = null;
1452         switch (newRingerMode) {
1453             case RINGER_MODE_NORMAL:
1454                 final StreamState ss = mState.states.get(AudioManager.STREAM_RING);
1455                 if (ss != null) {
1456                     toastText = mContext.getString(
1457                             R.string.volume_dialog_ringer_guidance_ring,
1458                             Utils.formatPercentage(ss.level, ss.levelMax));
1459                 }
1460                 break;
1461             case RINGER_MODE_SILENT:
1462                 toastText = mContext.getString(
1463                         com.android.internal.R.string.volume_dialog_ringer_guidance_silent);
1464                 break;
1465             case RINGER_MODE_VIBRATE:
1466             default:
1467                 toastText = mContext.getString(
1468                         com.android.internal.R.string.volume_dialog_ringer_guidance_vibrate);
1469         }
1470 
1471         Toast.makeText(mContext, toastText, Toast.LENGTH_SHORT).show();
1472         seenToastCount++;
1473         Prefs.putInt(mContext, Prefs.Key.SEEN_RINGER_GUIDANCE_COUNT, seenToastCount);
1474     }
1475 
show(int reason)1476     public void show(int reason) {
1477         mHandler.obtainMessage(H.SHOW, reason, 0).sendToTarget();
1478     }
1479 
dismiss(int reason)1480     public void dismiss(int reason) {
1481         mHandler.obtainMessage(H.DISMISS, reason, 0).sendToTarget();
1482     }
1483 
getJankListener(View v, String type, long timeout)1484     private Animator.AnimatorListener getJankListener(View v, String type, long timeout) {
1485         if (!mShouldListenForJank) {
1486             // TODO(b/290612381): temporary fix to prevent null pointers on leftover JankMonitors
1487             return null;
1488         } else return new Animator.AnimatorListener() {
1489             @Override
1490             public void onAnimationStart(@NonNull Animator animation) {
1491                 if (!v.isAttachedToWindow()) {
1492                     if (D.BUG) Log.d(TAG, "onAnimationStart view do not attached to window:" + v);
1493                     return;
1494                 }
1495                 mInteractionJankMonitor.begin(Builder.withView(CUJ_VOLUME_CONTROL, v).setTag(type)
1496                         .setTimeout(timeout));
1497             }
1498 
1499             @Override
1500             public void onAnimationEnd(@NonNull Animator animation) {
1501                 mInteractionJankMonitor.end(CUJ_VOLUME_CONTROL);
1502             }
1503 
1504             @Override
1505             public void onAnimationCancel(@NonNull Animator animation) {
1506                 mInteractionJankMonitor.cancel(CUJ_VOLUME_CONTROL);
1507                 Log.d(TAG, "onAnimationCancel");
1508             }
1509 
1510             @Override
1511             public void onAnimationRepeat(@NonNull Animator animation) {
1512                 // no-op
1513             }
1514         };
1515     }
1516 
1517     private void showH(int reason, boolean keyguardLocked, int lockTaskModeState) {
1518         Trace.beginSection("VolumeDialogImpl#showH");
1519         Log.i(TAG, "showH r=" + Events.SHOW_REASONS[reason]);
1520         mHandler.removeMessages(H.SHOW);
1521         mHandler.removeMessages(H.DISMISS);
1522         rescheduleTimeoutH();
1523 
1524         if (mConfigChanged) {
1525             initDialog(lockTaskModeState); // resets mShowing to false
1526             mConfigurableTexts.update();
1527             mConfigChanged = false;
1528         }
1529 
1530         initSettingsH(lockTaskModeState);
1531         mShowing = true;
1532         mIsAnimatingDismiss = false;
1533         mDialog.show();
1534         mInteractor.onDialogShown();
1535         Events.writeEvent(Events.EVENT_SHOW_DIALOG, reason, keyguardLocked);
1536         mController.notifyVisible(true);
1537         mController.getCaptionsComponentState(false);
1538         checkODICaptionsTooltip(false);
1539         updateBackgroundForDrawerClosedAmount();
1540         for (int i = 0; i < mRows.size(); i++) {
1541             VolumeRow row = mRows.get(i);
1542             if (row.slider.getVisibility() == VISIBLE) {
1543                 row.addTouchListener();
1544             }
1545         }
1546         Trace.endSection();
1547     }
1548 
1549     protected void rescheduleTimeoutH() {
1550         mHandler.removeMessages(H.DISMISS);
1551         final int timeout = computeTimeoutH();
1552         mHandler.sendMessageDelayed(mHandler
1553                 .obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT, 0), timeout);
1554         Log.i(TAG, "rescheduleTimeout " + timeout + " " + Debug.getCaller());
1555         mController.userActivity();
1556     }
1557 
1558     private int computeTimeoutH() {
1559         if (mHovering) {
1560             return mAccessibilityMgr.getRecommendedTimeoutMillis(DIALOG_HOVERING_TIMEOUT_MILLIS,
1561                     AccessibilityManager.FLAG_CONTENT_CONTROLS);
1562         }
1563         if (mSafetyWarning != null) {
1564             return mAccessibilityMgr.getRecommendedTimeoutMillis(
1565                     DIALOG_SAFETYWARNING_TIMEOUT_MILLIS,
1566                     AccessibilityManager.FLAG_CONTENT_TEXT
1567                             | AccessibilityManager.FLAG_CONTENT_CONTROLS);
1568         }
1569         if (!mHasSeenODICaptionsTooltip && mODICaptionsTooltipView != null) {
1570             return mAccessibilityMgr.getRecommendedTimeoutMillis(
1571                     DIALOG_ODI_CAPTIONS_TOOLTIP_TIMEOUT_MILLIS,
1572                     AccessibilityManager.FLAG_CONTENT_TEXT
1573                             | AccessibilityManager.FLAG_CONTENT_CONTROLS);
1574         }
1575         return mAccessibilityMgr.getRecommendedTimeoutMillis(mDialogTimeoutMillis,
1576                 AccessibilityManager.FLAG_CONTENT_CONTROLS);
1577     }
1578 
1579     protected void scheduleCsdTimeoutH(int timeoutMs) {
1580         mHandler.removeMessages(H.CSD_TIMEOUT);
1581         mHandler.sendMessageDelayed(mHandler.obtainMessage(H.CSD_TIMEOUT,
1582                 Events.DISMISS_REASON_CSD_WARNING_TIMEOUT, 0), timeoutMs);
1583         Log.i(TAG, "scheduleCsdTimeoutH " + timeoutMs + "ms " + Debug.getCaller());
1584         mController.userActivity();
1585     }
1586 
1587     private void onCsdTimeoutH() {
1588         synchronized (mSafetyWarningLock) {
1589             if (mCsdDialog == null) {
1590                 return;
1591             }
1592             mCsdDialog.dismiss();
1593         }
1594     }
1595 
1596     protected void dismissH(int reason) {
1597         Trace.beginSection("VolumeDialogImpl#dismissH");
1598         for (int i = 0; i < mRows.size(); i++) {
1599             mRows.get(i).removeHaptics();
1600         }
1601         Log.i(TAG, "mDialog.dismiss() reason: " + Events.DISMISS_REASONS[reason]
1602                 + " from: " + Debug.getCaller());
1603 
1604         mHandler.removeMessages(H.DISMISS);
1605         mHandler.removeMessages(H.SHOW);
1606 
1607         boolean showingStateInconsistent = !mShowing && mDialog != null && mDialog.isShowing();
1608         // If incorrectly assuming dialog is not showing, continue and make the state consistent.
1609         if (showingStateInconsistent) {
1610             Log.d(TAG, "dismissH: volume dialog possible in inconsistent state:"
1611                     + "mShowing=" + mShowing + ", mDialog==null?" + (mDialog == null));
1612         }
1613         if (mIsAnimatingDismiss && !showingStateInconsistent) {
1614             Log.d(TAG, "dismissH: skipping dismiss because isAnimatingDismiss is true"
1615                     + " and showingStateInconsistent is false");
1616             Trace.endSection();
1617             return;
1618         }
1619         mIsAnimatingDismiss = true;
1620         mDialogView.animate().cancel();
1621         mInteractor.onDialogDismissed();
1622         if (mShowing) {
1623             mShowing = false;
1624             // Only logs when the volume dialog visibility is changed.
1625             Events.writeEvent(Events.EVENT_DISMISS_DIALOG, reason);
1626         }
1627         mDialogView.setTranslationX(0);
1628         mDialogView.setAlpha(1);
1629         ViewPropertyAnimator animator = mDialogView.animate()
1630                 .alpha(0)
1631                 .setDuration(mDialogHideAnimationDurationMs)
1632                 .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
1633                 .withEndAction(() -> mHandler.postDelayed(() -> {
1634                     if (mController != null) {
1635                         mController.notifyVisible(false);
1636                     }
1637                     if (mDialog != null) {
1638                         mDialog.dismiss();
1639                     }
1640                     tryToRemoveCaptionsTooltip();
1641                     mIsAnimatingDismiss = false;
1642 
1643                     hideRingerDrawer();
1644                 }, 50));
1645         if (!shouldSlideInVolumeTray()) {
1646             animator.translationX(
1647                     (isWindowGravityLeft() ? -1 : 1) * mDialogView.getWidth() / 2.0f);
1648         }
1649 
1650         animator.setListener(getJankListener(getDialogView(), TYPE_DISMISS,
1651                 mDialogHideAnimationDurationMs)).start();
1652 
1653         checkODICaptionsTooltip(true);
1654         synchronized (mSafetyWarningLock) {
1655             if (mSafetyWarning != null) {
1656                 if (D.BUG) Log.d(TAG, "SafetyWarning dismissed");
1657                 mSafetyWarning.dismiss();
1658             }
1659         }
1660         Trace.endSection();
1661     }
1662 
1663     private boolean isTv() {
1664         return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)
1665                 || mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION);
1666     }
1667 
1668     private boolean shouldBeVisibleH(VolumeRow row, VolumeRow activeRow) {
1669         boolean isActive = row.stream == activeRow.stream;
1670 
1671         if (isActive) {
1672             return true;
1673         }
1674 
1675         if (!mIsTv) {
1676             if (row.stream == AudioSystem.STREAM_ACCESSIBILITY) {
1677                 return mShowA11yStream;
1678             }
1679 
1680             // if the active row is accessibility, then continue to display previous
1681             // active row since accessibility is displayed under it
1682             if (activeRow.stream == AudioSystem.STREAM_ACCESSIBILITY &&
1683                     row.stream == mPrevActiveStream) {
1684                 return true;
1685             }
1686 
1687             // Always show the stream for audio sharing if it exists.
1688             if ((volumeDialogAudioSharingFix() || audioSharingDeveloperOption())
1689                     && row.ss != null
1690                     && mContext.getString(R.string.audio_sharing_description)
1691                     .equals(row.ss.remoteLabel)) {
1692                 return true;
1693             }
1694 
1695             if (row.defaultStream) {
1696                 return activeRow.stream == STREAM_RING
1697                         || activeRow.stream == STREAM_ALARM
1698                         || activeRow.stream == STREAM_VOICE_CALL
1699                         || activeRow.stream == STREAM_ACCESSIBILITY
1700                         || mDynamic.get(activeRow.stream);
1701             }
1702 
1703             // Continue to display row if it is visible to user.
1704             if (row.view != null && mShowing) {
1705                 return row.view.getVisibility() == VISIBLE;
1706             }
1707         }
1708 
1709         return false;
1710     }
1711 
1712     private void updateRowsH(final VolumeRow activeRow) {
1713         Trace.beginSection("VolumeDialogImpl#updateRowsH");
1714         if (D.BUG) Log.d(TAG, "updateRowsH");
1715         if (!mShowing) {
1716             trimObsoleteH();
1717         }
1718 
1719         // Index of the last row that is actually visible.
1720         int rightmostVisibleRowIndex = !isRtl() ? -1 : Short.MAX_VALUE;
1721 
1722         // apply changes to all rows
1723         for (final VolumeRow row : mRows) {
1724             final boolean isActive = row == activeRow;
1725             final boolean shouldBeVisible = shouldBeVisibleH(row, activeRow);
1726             Util.setVisOrGone(row.view, shouldBeVisible);
1727 
1728             if (shouldBeVisible && mRingerAndDrawerContainerBackground != null) {
1729                 // For RTL, the rightmost row has the lowest index since child views are laid out
1730                 // from right to left.
1731                 rightmostVisibleRowIndex =
1732                         !isRtl()
1733                                 ? Math.max(rightmostVisibleRowIndex,
1734                                 mDialogRowsView.indexOfChild(row.view))
1735                                 : Math.min(rightmostVisibleRowIndex,
1736                                         mDialogRowsView.indexOfChild(row.view));
1737 
1738                 // Add spacing between each of the visible rows - we'll remove the spacing from the
1739                 // last row after the loop.
1740                 final ViewGroup.LayoutParams layoutParams = row.view.getLayoutParams();
1741                 if (layoutParams instanceof LinearLayout.LayoutParams) {
1742                     final LinearLayout.LayoutParams linearLayoutParams =
1743                             ((LinearLayout.LayoutParams) layoutParams);
1744                     if (!isRtl()) {
1745                         linearLayoutParams.setMarginEnd(mRingerRowsPadding);
1746                     } else {
1747                         linearLayoutParams.setMarginStart(mRingerRowsPadding);
1748                     }
1749                 }
1750 
1751                 // Set the background on each of the rows. We'll remove this from the last row after
1752                 // the loop, since the last row's background is drawn by the main volume container.
1753                 row.view.setBackgroundDrawable(
1754                         mContext.getDrawable(R.drawable.volume_row_rounded_background));
1755             }
1756 
1757             if (row.view.isShown()) {
1758                 updateVolumeRowTintH(row, isActive);
1759             }
1760         }
1761 
1762         if (rightmostVisibleRowIndex > -1 && rightmostVisibleRowIndex < Short.MAX_VALUE) {
1763             final View lastVisibleChild = mDialogRowsView.getChildAt(rightmostVisibleRowIndex);
1764             final ViewGroup.LayoutParams layoutParams = lastVisibleChild.getLayoutParams();
1765             // Remove the spacing on the last row, and remove its background since the container is
1766             // drawing a background for this row.
1767             if (layoutParams instanceof LinearLayout.LayoutParams) {
1768                 final LinearLayout.LayoutParams linearLayoutParams =
1769                         ((LinearLayout.LayoutParams) layoutParams);
1770                 linearLayoutParams.setMarginStart(0);
1771                 linearLayoutParams.setMarginEnd(0);
1772                 lastVisibleChild.setBackgroundColor(Color.TRANSPARENT);
1773             }
1774         }
1775 
1776         updateBackgroundForDrawerClosedAmount();
1777         Trace.endSection();
1778     }
1779 
1780     protected void updateRingerH() {
1781         if (mRinger != null && mState != null) {
1782             final StreamState ss = mState.states.get(AudioManager.STREAM_RING);
1783             if (ss == null) {
1784                 return;
1785             }
1786 
1787             boolean isZenMuted = mState.zenMode == Global.ZEN_MODE_ALARMS
1788                     || mState.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS
1789                     || (mState.zenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
1790                     && mState.disallowRinger);
1791             enableRingerViewsH(!isZenMuted);
1792             switch (mState.ringerModeInternal) {
1793                 case AudioManager.RINGER_MODE_VIBRATE:
1794                     mRingerIcon.setImageResource(R.drawable.ic_legacy_volume_ringer_vibrate);
1795                     mSelectedRingerIcon.setImageResource(
1796                             R.drawable.ic_legacy_volume_ringer_vibrate);
1797                     addAccessibilityDescription(mRingerIcon, RINGER_MODE_VIBRATE,
1798                             mContext.getString(R.string.volume_ringer_hint_mute));
1799                     mRingerIcon.setTag(Events.ICON_STATE_VIBRATE);
1800                     break;
1801                 case AudioManager.RINGER_MODE_SILENT:
1802                     mRingerIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
1803                     mSelectedRingerIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
1804                     mRingerIcon.setTag(Events.ICON_STATE_MUTE);
1805                     addAccessibilityDescription(mRingerIcon, RINGER_MODE_SILENT,
1806                             mContext.getString(R.string.volume_ringer_hint_unmute));
1807                     break;
1808                 case AudioManager.RINGER_MODE_NORMAL:
1809                 default:
1810                     boolean muted = (mAutomute && ss.level == 0) || ss.muted;
1811                     if (!isZenMuted && muted) {
1812                         mRingerIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
1813                         mSelectedRingerIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
1814                         addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL,
1815                                 mContext.getString(R.string.volume_ringer_hint_unmute));
1816                         mRingerIcon.setTag(Events.ICON_STATE_MUTE);
1817                     } else {
1818                         mRingerIcon.setImageResource(mVolumeRingerIconDrawableId);
1819                         mSelectedRingerIcon.setImageResource(mVolumeRingerIconDrawableId);
1820                         if (mController.hasVibrator()) {
1821                             addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL,
1822                                     mContext.getString(R.string.volume_ringer_hint_vibrate));
1823                         } else {
1824                             addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL,
1825                                     mContext.getString(R.string.volume_ringer_hint_mute));
1826                         }
1827                         mRingerIcon.setTag(Events.ICON_STATE_UNMUTE);
1828                     }
1829                     break;
1830             }
1831         }
1832     }
1833 
1834     private void addAccessibilityDescription(View view, int currState, String hintLabel) {
1835         view.setContentDescription(
1836                 mContext.getString(getStringDescriptionResourceForRingerMode(currState)));
1837         view.setAccessibilityDelegate(new AccessibilityDelegate() {
1838             public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
1839                 super.onInitializeAccessibilityNodeInfo(host, info);
1840                 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
1841                         AccessibilityNodeInfo.ACTION_CLICK, hintLabel));
1842             }
1843         });
1844     }
1845 
1846     @VisibleForTesting int getStringDescriptionResourceForRingerMode(int mode) {
1847         switch (mode) {
1848             case RINGER_MODE_SILENT:
1849                 return R.string.volume_ringer_status_silent;
1850             case RINGER_MODE_VIBRATE:
1851                 return R.string.volume_ringer_status_vibrate;
1852             case RINGER_MODE_NORMAL:
1853             default:
1854                 return R.string.volume_ringer_status_normal;
1855         }
1856     }
1857 
1858     /**
1859      * Toggles enable state of footer/ringer views
1860      * @param enable whether to enable ringer views
1861      */
1862     private void enableRingerViewsH(boolean enable) {
1863         if (mRingerIcon != null) {
1864             mRingerIcon.setEnabled(enable);
1865         }
1866     }
1867 
1868     private void trimObsoleteH() {
1869         if (D.BUG) Log.d(TAG, "trimObsoleteH");
1870         for (int i = mRows.size() - 1; i >= 0; i--) {
1871             final VolumeRow row = mRows.get(i);
1872             if (row.ss == null || !row.ss.dynamic) continue;
1873             if (!mDynamic.get(row.stream)) {
1874                 mRows.remove(i);
1875                 mDialogRowsView.removeView(row.view);
1876                 mConfigurableTexts.remove(row.header);
1877             }
1878         }
1879     }
1880 
1881     protected void onStateChangedH(State state) {
1882         if (D.BUG) Log.d(TAG, "onStateChangedH() state: " + state.toString());
1883         if (mState != null && state != null
1884                 && mState.ringerModeInternal != -1
1885                 && mState.ringerModeInternal != state.ringerModeInternal
1886                 && state.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) {
1887             mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK));
1888         }
1889         mState = state;
1890         mDynamic.clear();
1891         // add any new dynamic rows
1892         for (int i = 0; i < state.states.size(); i++) {
1893             final int stream = state.states.keyAt(i);
1894             final StreamState ss = state.states.valueAt(i);
1895             if (!ss.dynamic) continue;
1896             mDynamic.put(stream, true);
1897             if (findRow(stream) == null) {
1898                 if ((volumeDialogAudioSharingFix() || audioSharingDeveloperOption())
1899                         && (mContext.getString(R.string.audio_sharing_description)
1900                         .equals(ss.remoteLabel))) {
1901                     addRow(
1902                             stream,
1903                             R.drawable.ic_volume_media_bt,
1904                             R.drawable.ic_volume_media_bt_mute,
1905                             true,
1906                             false,
1907                             true);
1908                 } else {
1909                     addRow(
1910                             stream,
1911                             com.android.settingslib.R.drawable.ic_volume_remote,
1912                             com.android.settingslib.R.drawable.ic_volume_remote_mute,
1913                             true,
1914                             false,
1915                             true);
1916                 }
1917             }
1918         }
1919 
1920         if (mActiveStream != state.activeStream) {
1921             mPrevActiveStream = mActiveStream;
1922             mActiveStream = state.activeStream;
1923             VolumeRow activeRow = getActiveRow();
1924             updateRowsH(activeRow);
1925             if (mShowing) rescheduleTimeoutH();
1926         }
1927         for (VolumeRow row : mRows) {
1928             updateVolumeRowH(row);
1929         }
1930         updateRingerH();
1931         updateSelectedRingerContainerDescription(mIsRingerDrawerOpen);
1932         mWindow.setTitle(composeWindowTitle());
1933     }
1934 
1935     CharSequence composeWindowTitle() {
1936         return mContext.getString(R.string.volume_dialog_title, getStreamLabelH(getActiveRow().ss));
1937     }
1938 
1939     private void updateVolumeRowH(VolumeRow row) {
1940         if (D.BUG) Log.i(TAG, "updateVolumeRowH s=" + row.stream);
1941         if (mState == null) return;
1942         final StreamState ss = mState.states.get(row.stream);
1943         if (ss == null) return;
1944         row.ss = ss;
1945         if (ss.level > 0) {
1946             row.lastAudibleLevel = ss.level;
1947         }
1948         if (ss.level == row.requestedLevel) {
1949             row.requestedLevel = -1;
1950         }
1951         final boolean isVoiceCallStream = row.stream == AudioManager.STREAM_VOICE_CALL;
1952         final boolean isA11yStream = row.stream == STREAM_ACCESSIBILITY;
1953         final boolean isRingStream = row.stream == AudioManager.STREAM_RING;
1954         final boolean isSystemStream = row.stream == AudioManager.STREAM_SYSTEM;
1955         final boolean isAlarmStream = row.stream == STREAM_ALARM;
1956         final boolean isMusicStream = row.stream == AudioManager.STREAM_MUSIC;
1957         final boolean isRingVibrate = isRingStream
1958                 && mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE;
1959         final boolean isRingSilent = isRingStream
1960                 && mState.ringerModeInternal == AudioManager.RINGER_MODE_SILENT;
1961         final boolean isZenPriorityOnly = mState.zenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
1962         final boolean isZenAlarms = mState.zenMode == Global.ZEN_MODE_ALARMS;
1963         final boolean isZenNone = mState.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
1964         final boolean zenMuted = isZenAlarms ? (isRingStream || isSystemStream)
1965                 : isZenNone ? (isRingStream || isSystemStream || isAlarmStream || isMusicStream)
1966                 : isZenPriorityOnly ? ((isAlarmStream && mState.disallowAlarms) ||
1967                         (isMusicStream && mState.disallowMedia) ||
1968                         (isRingStream && mState.disallowRinger) ||
1969                         (isSystemStream && mState.disallowSystem))
1970                 : false;
1971 
1972         // update slider max
1973         final int max = ss.levelMax * DISPLAY_RANGE_MULTIPLIER;
1974         if (max != row.slider.getMax()) {
1975             row.slider.setMax(max);
1976         }
1977         // update slider min
1978         final int min = ss.levelMin * DISPLAY_RANGE_MULTIPLIER;
1979         if (min != row.slider.getMin()) {
1980             row.slider.setMin(min);
1981         }
1982 
1983         // update header text
1984         Util.setText(row.header, getStreamLabelH(ss));
1985         row.slider.setContentDescription(row.header.getText());
1986         mConfigurableTexts.add(row.header, ss.name);
1987 
1988         // update icon
1989         final boolean iconEnabled = (mAutomute || ss.muteSupported) && !zenMuted;
1990         final int iconRes;
1991         if (zenMuted) {
1992             iconRes = com.android.internal.R.drawable.ic_qs_dnd;
1993         } else if (isRingVibrate) {
1994             iconRes = R.drawable.ic_legacy_volume_ringer_vibrate;
1995         } else if (isRingSilent) {
1996             iconRes = row.iconMuteRes;
1997         } else if (ss.routedToBluetooth) {
1998             if (isVoiceCallStream) {
1999                 iconRes = R.drawable.ic_volume_bt_sco;
2000             } else {
2001                 iconRes = isStreamMuted(ss) ? R.drawable.ic_volume_media_bt_mute
2002                                             : R.drawable.ic_volume_media_bt;
2003             }
2004         } else if (isStreamMuted(ss)) {
2005             iconRes = row.iconMuteRes;
2006         } else {
2007             iconRes = mShowLowMediaVolumeIcon && ss.level * 2 < (ss.levelMax + ss.levelMin)
2008                       ? R.drawable.ic_volume_media_low : row.iconRes;
2009         }
2010 
2011         row.setIcon(iconRes, mContext.getTheme());
2012         row.iconState =
2013                 iconRes == R.drawable.ic_legacy_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE
2014                 : (iconRes == R.drawable.ic_volume_media_bt_mute || iconRes == row.iconMuteRes)
2015                         ? Events.ICON_STATE_MUTE
2016                 : (iconRes == R.drawable.ic_volume_media_bt || iconRes == row.iconRes
2017                         || iconRes == R.drawable.ic_volume_media_low)
2018                         ? Events.ICON_STATE_UNMUTE
2019                 : Events.ICON_STATE_UNKNOWN;
2020 
2021         if (row.icon != null) {
2022             if (iconEnabled) {
2023                 if (isRingStream) {
2024                     if (isRingVibrate) {
2025                         row.icon.setContentDescription(mContext.getString(
2026                                 R.string.volume_stream_content_description_unmute,
2027                                 getStreamLabelH(ss)));
2028                     } else {
2029                         if (mController.hasVibrator()) {
2030                             row.icon.setContentDescription(mContext.getString(
2031                                     mShowA11yStream
2032                                             ? R.string.volume_stream_content_description_vibrate_a11y
2033                                             : R.string.volume_stream_content_description_vibrate,
2034                                     getStreamLabelH(ss)));
2035                         } else {
2036                             row.icon.setContentDescription(mContext.getString(
2037                                     mShowA11yStream
2038                                             ? R.string.volume_stream_content_description_mute_a11y
2039                                             : R.string.volume_stream_content_description_mute,
2040                                     getStreamLabelH(ss)));
2041                         }
2042                     }
2043                 } else if (isA11yStream) {
2044                     row.icon.setContentDescription(getStreamLabelH(ss));
2045                 } else {
2046                     if (ss.muted || mAutomute && ss.level == 0) {
2047                         row.icon.setContentDescription(mContext.getString(
2048                                 R.string.volume_stream_content_description_unmute,
2049                                 getStreamLabelH(ss)));
2050                     } else {
2051                         row.icon.setContentDescription(mContext.getString(
2052                                 mShowA11yStream
2053                                         ? R.string.volume_stream_content_description_mute_a11y
2054                                         : R.string.volume_stream_content_description_mute,
2055                                 getStreamLabelH(ss)));
2056                     }
2057                 }
2058             } else {
2059                 row.icon.setContentDescription(getStreamLabelH(ss));
2060             }
2061         }
2062 
2063         // ensure tracking is disabled if zenMuted
2064         if (zenMuted) {
2065             row.tracking = false;
2066         }
2067 
2068         // update slider
2069         final boolean enableSlider = !zenMuted;
2070         final int vlevel = row.ss.muted && (!isRingStream && !zenMuted) ? 0
2071                 : row.ss.level;
2072         Trace.beginSection("VolumeDialogImpl#updateVolumeRowSliderH");
2073         updateVolumeRowSliderH(row, enableSlider, vlevel);
2074         Trace.endSection();
2075         if (row.number != null) row.number.setText(Integer.toString(vlevel));
2076     }
2077 
2078     private boolean isStreamMuted(final StreamState streamState) {
2079         return (mAutomute && streamState.level == 0) || streamState.muted;
2080     }
2081 
2082     private void updateVolumeRowTintH(VolumeRow row, boolean isActive) {
2083         if (isActive) {
2084             row.slider.requestFocus();
2085         }
2086         boolean useActiveColoring = isActive && row.slider.isEnabled();
2087         if (!useActiveColoring && !mChangeVolumeRowTintWhenInactive) {
2088             return;
2089         }
2090         final ColorStateList colorTint = useActiveColoring
2091                 ? Utils.getColorAccent(mContext)
2092                 : Utils.getColorAttr(mContext, com.android.internal.R.attr.colorAccentSecondary);
2093         final int alpha = useActiveColoring
2094                 ? Color.alpha(colorTint.getDefaultColor())
2095                 : getAlphaAttr(android.R.attr.secondaryContentAlpha);
2096 
2097         final ColorStateList bgTint = Utils.getColorAttr(
2098                 mContext, android.R.attr.colorBackgroundFloating);
2099 
2100         final ColorStateList inverseTextTint = Utils.getColorAttr(
2101                 mContext, com.android.internal.R.attr.textColorOnAccent);
2102 
2103         row.sliderProgressSolid.setTintList(colorTint);
2104         if (row.sliderProgressIcon != null) {
2105             row.sliderProgressIcon.setTintList(bgTint);
2106         }
2107 
2108         if (row.icon != null) {
2109             row.icon.setImageTintList(inverseTextTint);
2110             row.icon.setImageAlpha(alpha);
2111         }
2112 
2113         if (row.number != null) {
2114             row.number.setTextColor(colorTint);
2115             row.number.setAlpha(alpha);
2116         }
2117     }
2118 
2119     private void updateVolumeRowSliderH(VolumeRow row, boolean enable, int vlevel) {
2120         row.slider.setEnabled(enable);
2121         updateVolumeRowTintH(row, row.stream == mActiveStream);
2122         if (row.tracking) {
2123             return;  // don't update if user is sliding
2124         }
2125         final int progress = row.slider.getProgress();
2126         final int level = getVolumeFromProgress(row.ss, row.slider, progress);
2127         final boolean rowVisible = row.view.getVisibility() == VISIBLE;
2128         final boolean inGracePeriod = (SystemClock.uptimeMillis() - row.userAttempt)
2129                 < USER_ATTEMPT_GRACE_PERIOD;
2130         mHandler.removeMessages(H.RECHECK, row);
2131         if (mShowing && rowVisible && inGracePeriod) {
2132             if (D.BUG) Log.d(TAG, "inGracePeriod");
2133             mHandler.sendMessageAtTime(mHandler.obtainMessage(H.RECHECK, row),
2134                     row.userAttempt + USER_ATTEMPT_GRACE_PERIOD);
2135             return;  // don't update if visible and in grace period
2136         }
2137         if (vlevel == level) {
2138             if (mShowing && rowVisible) {
2139                 return;  // don't clamp if visible
2140             }
2141         }
2142         final int newProgress = getProgressFromVolume(row.ss, row.slider, vlevel);
2143         if (progress != newProgress) {
2144             if (mIsTv) {
2145                 // don't animate slider on TVs
2146                 row.slider.setProgress(newProgress, false);
2147                 return;
2148             }
2149             if (mShowing && rowVisible) {
2150                 // animate!
2151                 if (row.anim != null && row.anim.isRunning()
2152                         && row.animTargetProgress == newProgress) {
2153                     return;  // already animating to the target progress
2154                 }
2155                 // start/update animation
2156                 if (row.anim == null) {
2157                     row.anim = ObjectAnimator.ofInt(row.slider, "progress", progress, newProgress);
2158                     row.anim.setInterpolator(new DecelerateInterpolator());
2159                     Animator.AnimatorListener listener =
2160                             getJankListener(row.view, TYPE_UPDATE, UPDATE_ANIMATION_DURATION);
2161                     if (listener != null) {
2162                         row.anim.addListener(listener);
2163                     }
2164                 } else {
2165                     row.anim.cancel();
2166                     row.anim.setIntValues(progress, newProgress);
2167                     // The animator can't keep up with the volume changes so haptics need to be
2168                     // triggered here. This happens when the volume keys are continuously pressed.
2169                     row.deliverOnProgressChangedHaptics(false, newProgress);
2170                 }
2171                 row.animTargetProgress = newProgress;
2172                 row.anim.setDuration(UPDATE_ANIMATION_DURATION);
2173                 row.anim.start();
2174             } else {
2175                 // update slider directly to clamped value
2176                 if (row.anim != null) {
2177                     row.anim.cancel();
2178                 }
2179                 row.slider.setProgress(newProgress, true);
2180             }
2181         }
2182     }
2183 
2184     @VisibleForTesting
2185     boolean canDeliverProgressHapticsToStream(int stream, boolean fromUser, int progress) {
2186         for (VolumeRow row: mRows) {
2187             if (row.stream == stream) {
2188                 return row.deliverOnProgressChangedHaptics(fromUser, progress);
2189             }
2190         }
2191         return false;
2192     }
2193 
2194     private void recheckH(VolumeRow row) {
2195         if (row == null) {
2196             if (D.BUG) Log.d(TAG, "recheckH ALL");
2197             trimObsoleteH();
2198             for (VolumeRow r : mRows) {
2199                 updateVolumeRowH(r);
2200             }
2201         } else {
2202             if (D.BUG) Log.d(TAG, "recheckH " + row.stream);
2203             updateVolumeRowH(row);
2204         }
2205     }
2206 
2207     private void setStreamImportantH(int stream, boolean important) {
2208         for (VolumeRow row : mRows) {
2209             if (row.stream == stream) {
2210                 row.important = important;
2211                 return;
2212             }
2213         }
2214     }
2215 
2216     private void showSafetyWarningH(int flags) {
2217         if ((flags & (AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_SHOW_UI_WARNINGS)) != 0
2218                 || mShowing) {
2219             synchronized (mSafetyWarningLock) {
2220                 if (mSafetyWarning != null) {
2221                     return;
2222                 }
2223                 mSafetyWarning = new SafetyWarningDialog(mContext, mController.getAudioManager()) {
2224                     @Override
2225                     protected void cleanUp() {
2226                         synchronized (mSafetyWarningLock) {
2227                             mSafetyWarning = null;
2228                         }
2229                         recheckH(null);
2230                     }
2231                 };
2232                 mSafetyWarning.show();
2233             }
2234             recheckH(null);
2235         }
2236         rescheduleTimeoutH();
2237     }
2238 
2239     public void setCsdWarningNotificationActionIntents(
2240             List<CsdWarningAction> actionIntent) {
2241         mCsdWarningNotificationActions = Optional.of(actionIntent);
2242     }
2243 
2244     @VisibleForTesting void showCsdWarningH(int csdWarning, int durationMs) {
2245         synchronized (mSafetyWarningLock) {
2246 
2247             if (mCsdDialog != null) {
2248                 return;
2249             }
2250 
2251             final Runnable cleanUp = () -> {
2252                 synchronized (mSafetyWarningLock) {
2253                     mCsdDialog = null;
2254                 }
2255                 recheckH(null);
2256             };
2257 
2258             mCsdDialog = mCsdWarningDialogFactory.create(
2259                     csdWarning, cleanUp, mCsdWarningNotificationActions);
2260             mCsdDialog.show();
2261         }
2262         recheckH(null);
2263         if (durationMs > 0) {
2264             scheduleCsdTimeoutH(durationMs);
2265         }
2266         rescheduleTimeoutH();
2267     }
2268 
2269     private String getStreamLabelH(StreamState ss) {
2270         if (ss == null) {
2271             return "";
2272         }
2273         if (ss.remoteLabel != null) {
2274             return ss.remoteLabel;
2275         }
2276         try {
2277             return mContext.getResources().getString(ss.name);
2278         } catch (Resources.NotFoundException e) {
2279             Slog.e(TAG, "Can't find translation for stream " + ss);
2280             return "";
2281         }
2282     }
2283 
2284     private Runnable getSinglePressFor(ImageButton button) {
2285         return () -> {
2286             if (button != null) {
2287                 button.setPressed(true);
2288                 button.postOnAnimationDelayed(getSingleUnpressFor(button), 200);
2289             }
2290         };
2291     }
2292 
2293     private Runnable getSingleUnpressFor(ImageButton button) {
2294         return () -> {
2295             if (button != null) {
2296                 button.setPressed(false);
2297             }
2298         };
2299     }
2300 
2301     /**
2302      * Return the size of the 1-2 extra ringer options that are made visible when the ringer drawer
2303      * is opened. The drawer options are square so this can be used for height calculations (when in
2304      * portrait, and the drawer opens upward) or for width (when opening sideways in landscape).
2305      */
2306     private int getRingerDrawerOpenExtraSize() {
2307         return (mRingerCount - 1) * mRingerDrawerItemSize;
2308     }
2309 
2310     /**
2311      * Return the size of the additionally visible rows next to the default stream.
2312      * An additional row is visible for example while receiving a voice call.
2313      */
2314     private int getVisibleRowsExtraSize() {
2315         VolumeRow activeRow = getActiveRow();
2316         int visibleRows = 0;
2317         for (final VolumeRow row : mRows) {
2318             if (shouldBeVisibleH(row, activeRow)) {
2319                 visibleRows++;
2320             }
2321         }
2322         return (visibleRows - 1) * (mDialogWidth + mRingerRowsPadding);
2323     }
2324 
2325     private void updateBackgroundForDrawerClosedAmount() {
2326         if (mRingerAndDrawerContainerBackground == null) {
2327             return;
2328         }
2329 
2330         final Rect bounds = mRingerAndDrawerContainerBackground.copyBounds();
2331         if (!isLandscape()) {
2332             bounds.top = (int) (mRingerDrawerClosedAmount * getRingerDrawerOpenExtraSize());
2333         } else {
2334             bounds.left = (int) (mRingerDrawerClosedAmount * getRingerDrawerOpenExtraSize());
2335         }
2336         mRingerAndDrawerContainerBackground.setBounds(bounds);
2337     }
2338 
2339     /*
2340      * The top container is responsible for drawing the solid color background behind the rightmost
2341      * (primary) volume row. This is because the volume drawer animates in from below, initially
2342      * overlapping the primary row. We need the drawer to draw below the row's SeekBar, since it
2343      * looks strange to overlap it, but above the row's background color, since otherwise it will be
2344      * clipped.
2345      *
2346      * Since we can't be both above and below the volume row view, we'll be below it, and render the
2347      * background color in the container since they're both above that.
2348      */
2349     private void setTopContainerBackgroundDrawable() {
2350         if (mTopContainer == null) {
2351             return;
2352         }
2353 
2354         LayerDrawable background;
2355         // mRingerAndDrawerContainer has rounded corner.
2356         // But when it's not visible, mTopContainer needs to have rounded corner.
2357         if (Flags.hideRingerButtonInSingleVolumeMode()
2358                 && mRingerAndDrawerContainer.getVisibility() != VISIBLE
2359         ) {
2360             float[] radius = new float[] {
2361                 mDialogCornerRadius, mDialogCornerRadius,  // Top-left corner
2362                 mDialogCornerRadius, mDialogCornerRadius,  // Top-right corner
2363                 0, 0,  // Bottom-right corner
2364                 0, 0   // Bottom-left corner
2365             };
2366 
2367             ShapeDrawable roundedDrawable = new ShapeDrawable(
2368                     new RoundRectShape(radius, null, null));
2369             roundedDrawable.getPaint().setColor(Utils.getColorAttrDefaultColor(
2370                     mContext, com.android.internal.R.attr.colorSurface));
2371 
2372             background = new LayerDrawable(new Drawable[] { roundedDrawable });
2373         } else {
2374             final ColorDrawable solidDrawable = new ColorDrawable(
2375                 Utils.getColorAttrDefaultColor(mContext, com.android.internal.R.attr.colorSurface));
2376 
2377             background = new LayerDrawable(new Drawable[] { solidDrawable });
2378         }
2379 
2380         // Size the solid color to match the primary volume row. In landscape, extend it upwards
2381         // slightly so that it fills in the bottom corners of the ringer icon, whose background is
2382         // rounded on all sides so that it can expand to the left, outside the dialog's background.
2383         background.setLayerSize(0, mDialogWidth,
2384                 !isLandscape()
2385                         ? mDialogRowsView.getHeight()
2386                         : mDialogRowsView.getHeight() + mDialogCornerRadius);
2387         // Inset the top so that the color only renders below the ringer drawer, which has its own
2388         // background. In landscape, reduce the inset slightly since we are using the background to
2389         // fill in the corners of the closed ringer drawer.
2390         background.setLayerInsetTop(0,
2391                 !isLandscape()
2392                         ? mDialogRowsViewContainer.getTop()
2393                         : mDialogRowsViewContainer.getTop() - mDialogCornerRadius);
2394 
2395         // Set gravity to top-right, since additional rows will be added on the left.
2396         background.setLayerGravity(0, Gravity.TOP | Gravity.RIGHT);
2397 
2398         // In landscape, the ringer drawer animates out to the left (instead of down). Since the
2399         // drawer comes from the right (beyond the bounds of the dialog), we should clip it so it
2400         // doesn't draw outside the dialog background. This isn't an issue in portrait, since the
2401         // drawer animates downward, below the volume row.
2402         if (isLandscape()) {
2403             mRingerAndDrawerContainer.setOutlineProvider(new ViewOutlineProvider() {
2404                 @Override
2405                 public void getOutline(View view, Outline outline) {
2406                     outline.setRoundRect(
2407                             0, 0, view.getWidth(), view.getHeight(), mDialogCornerRadius);
2408                 }
2409             });
2410             mRingerAndDrawerContainer.setClipToOutline(true);
2411         }
2412 
2413         mTopContainer.setBackground(background);
2414     }
2415 
2416     @Override
2417     public void onConfigChanged(Configuration config) {
2418         mOrientation = config.orientation;
2419     }
2420 
2421     private final VolumeDialogController.Callbacks mControllerCallbackH
2422             = new VolumeDialogController.Callbacks() {
2423         @Override
2424         public void onShowRequested(int reason, boolean keyguardLocked, int lockTaskModeState) {
2425             showH(reason, keyguardLocked, lockTaskModeState);
2426         }
2427 
2428         @Override
2429         public void onDismissRequested(int reason) {
2430             dismissH(reason);
2431         }
2432 
2433         @Override
2434         public void onScreenOff() {
2435             dismissH(Events.DISMISS_REASON_SCREEN_OFF);
2436         }
2437 
2438         @Override
2439         public void onStateChanged(State state) {
2440             onStateChangedH(state);
2441         }
2442 
2443         @Override
2444         public void onLayoutDirectionChanged(int layoutDirection) {
2445             mDialogView.setLayoutDirection(layoutDirection);
2446         }
2447 
2448         @Override
2449         public void onConfigurationChanged() {
2450             mDialog.dismiss();
2451             mConfigChanged = true;
2452         }
2453 
2454         @Override
2455         public void onShowVibrateHint() {
2456             if (mSilentMode) {
2457                 mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false);
2458             }
2459         }
2460 
2461         @Override
2462         public void onShowSilentHint() {
2463             if (mSilentMode) {
2464                 mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
2465             }
2466         }
2467 
2468         @Override
2469         public void onShowSafetyWarning(int flags) {
2470             showSafetyWarningH(flags);
2471         }
2472 
2473         @Override
2474         public void onShowCsdWarning(int csdWarning, int durationMs) {
2475             showCsdWarningH(csdWarning, durationMs);
2476         }
2477 
2478         @Override
2479         public void onAccessibilityModeChanged(Boolean showA11yStream) {
2480             mShowA11yStream = showA11yStream == null ? false : showA11yStream;
2481             VolumeRow activeRow = getActiveRow();
2482             if (!mShowA11yStream && STREAM_ACCESSIBILITY == activeRow.stream) {
2483                 dismissH(Events.DISMISS_STREAM_GONE);
2484             } else {
2485                 updateRowsH(activeRow);
2486             }
2487         }
2488 
2489         @Override
2490         public void onCaptionComponentStateChanged(
2491                 Boolean isComponentEnabled, Boolean fromTooltip) {
2492             updateODICaptionsH(isComponentEnabled, fromTooltip);
2493         }
2494 
2495         @Override
2496         public void onCaptionEnabledStateChanged(Boolean isEnabled, Boolean checkForSwitchState) {
2497             updateCaptionsEnabledH(isEnabled, checkForSwitchState);
2498         }
2499 
2500         @Override
2501         public void onVolumeChangedFromKey() {
2502             VolumeRow activeRow = getActiveRow();
2503             if (activeRow.mHapticPlugin != null) {
2504                 activeRow.mHapticPlugin.onKeyDown();
2505             }
2506         }
2507     };
2508 
2509     @VisibleForTesting void onPostureChanged(int posture) {
2510         dismiss(DISMISS_REASON_POSTURE_CHANGED);
2511         mDevicePosture = posture;
2512     }
2513 
2514     private final class H extends Handler {
2515         private static final int SHOW = 1;
2516         private static final int DISMISS = 2;
2517         private static final int RECHECK = 3;
2518         private static final int RECHECK_ALL = 4;
2519         private static final int SET_STREAM_IMPORTANT = 5;
2520         private static final int RESCHEDULE_TIMEOUT = 6;
2521         private static final int STATE_CHANGED = 7;
2522         private static final int CSD_TIMEOUT = 8;
2523 
2524         H(Looper looper) {
2525             super(looper);
2526         }
2527 
2528         @Override
2529         public void handleMessage(Message msg) {
2530             switch (msg.what) {
2531                 case SHOW: showH(msg.arg1, VolumeDialogImpl.this.mKeyguard.isKeyguardLocked(),
2532                         VolumeDialogImpl.this.mActivityManager.getLockTaskModeState()); break;
2533                 case DISMISS: dismissH(msg.arg1); break;
2534                 case RECHECK: recheckH((VolumeRow) msg.obj); break;
2535                 case RECHECK_ALL: recheckH(null); break;
2536                 case SET_STREAM_IMPORTANT: setStreamImportantH(msg.arg1, msg.arg2 != 0); break;
2537                 case RESCHEDULE_TIMEOUT: rescheduleTimeoutH(); break;
2538                 case STATE_CHANGED: onStateChangedH(mState); break;
2539                 case CSD_TIMEOUT: onCsdTimeoutH(); break;
2540             }
2541         }
2542     }
2543 
2544     @VisibleForTesting
2545     void clearInternalHandlerAfterTest() {
2546         if (mHandler != null) {
2547             mHandler.removeCallbacksAndMessages(null);
2548         }
2549     }
2550 
2551     private final class CustomDialog extends Dialog implements DialogInterface {
2552         public CustomDialog(Context context) {
2553             super(context, R.style.volume_dialog_theme);
2554         }
2555 
2556         /**
2557          * NOTE: This will only be called for touches within the touchable region of the volume
2558          * dialog, as returned by {@link #onComputeInternalInsets}. Other touches, even if they are
2559          * within the bounds of the volume dialog, will fall through to the window below.
2560          */
2561         @Override
2562         public boolean dispatchTouchEvent(@NonNull MotionEvent ev) {
2563             rescheduleTimeoutH();
2564             return super.dispatchTouchEvent(ev);
2565         }
2566 
2567         @Override
2568         protected void onStart() {
2569             super.setCanceledOnTouchOutside(true);
2570             super.onStart();
2571             adjustPositionOnScreen();
2572         }
2573 
2574         @Override
2575         protected void onStop() {
2576             super.onStop();
2577             mHandler.sendEmptyMessage(H.RECHECK_ALL);
2578         }
2579 
2580         /**
2581          * NOTE: This will be called with ACTION_OUTSIDE MotionEvents for touches that occur outside
2582          * of the touchable region of the volume dialog (as returned by
2583          * {@link #onComputeInternalInsets}) even if those touches occurred within the bounds of the
2584          * volume dialog.
2585          */
2586         @Override
2587         public boolean onTouchEvent(@NonNull MotionEvent event) {
2588             if (mShowing) {
2589                 if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
2590                     dismissH(Events.DISMISS_REASON_TOUCH_OUTSIDE);
2591                     return true;
2592                 }
2593             }
2594             return false;
2595         }
2596     }
2597 
2598     private final class VolumeSeekBarChangeListener implements OnSeekBarChangeListener {
2599         private final VolumeRow mRow;
2600 
2601         private VolumeSeekBarChangeListener(VolumeRow row) {
2602             mRow = row;
2603         }
2604 
2605         @Override
2606         public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
2607             if (mRow.ss == null) return;
2608             if (getActiveRow().equals(mRow) && mRow.slider.getVisibility() == VISIBLE) {
2609                 if (fromUser || mRow.animTargetProgress == progress) {
2610                     // Deliver user-generated slider haptics immediately, or when the animation
2611                     // completes
2612                     mRow.deliverOnProgressChangedHaptics(fromUser, progress);
2613                 }
2614             }
2615             if (D.BUG) Log.d(TAG, AudioSystem.streamToString(mRow.stream)
2616                     + " onProgressChanged " + progress + " fromUser=" + fromUser);
2617             if (!fromUser) return;
2618             if (mRow.ss.levelMin > 0) {
2619                 final int minProgress = getProgressFromVolume(mRow.ss, seekBar, mRow.ss.levelMin);
2620                 if (progress < minProgress) {
2621                     seekBar.setProgress(minProgress);
2622                     progress = minProgress;
2623                 }
2624             }
2625             final int userLevel = getVolumeFromProgress(mRow.ss, seekBar, progress);
2626             if (mRow.ss.level != userLevel || mRow.ss.muted && userLevel > 0) {
2627                 mRow.userAttempt = SystemClock.uptimeMillis();
2628                 if (mRow.requestedLevel != userLevel) {
2629                     mController.setActiveStream(mRow.stream);
2630                     mController.setStreamVolume(mRow.stream, userLevel);
2631                     mRow.requestedLevel = userLevel;
2632                     Events.writeEvent(Events.EVENT_TOUCH_LEVEL_CHANGED, mRow.stream,
2633                             userLevel);
2634                 }
2635             }
2636         }
2637 
2638         @Override
2639         public void onStartTrackingTouch(SeekBar seekBar) {
2640             if (D.BUG) Log.d(TAG, "onStartTrackingTouch"+ " " + mRow.stream);
2641             Events.writeEvent(Events.EVENT_SLIDER_TOUCH_TRACKING, /* startedTracking= */true);
2642             if (mRow.mHapticPlugin != null) {
2643                 mRow.mHapticPlugin.onStartTrackingTouch();
2644             }
2645             mController.setActiveStream(mRow.stream);
2646             mRow.tracking = true;
2647         }
2648 
2649         @Override
2650         public void onStopTrackingTouch(SeekBar seekBar) {
2651             if (D.BUG) Log.d(TAG, "onStopTrackingTouch"+ " " + mRow.stream);
2652             Events.writeEvent(Events.EVENT_SLIDER_TOUCH_TRACKING, /* startedTracking= */false);
2653             if (mRow.mHapticPlugin != null) {
2654                 mRow.mHapticPlugin.onStopTrackingTouch();
2655             }
2656             mRow.tracking = false;
2657             mRow.userAttempt = SystemClock.uptimeMillis();
2658             final int userLevel = getVolumeFromProgress(mRow.ss, seekBar, seekBar.getProgress());
2659             Events.writeEvent(Events.EVENT_TOUCH_LEVEL_DONE, mRow.stream, userLevel);
2660             if (mRow.ss.level != userLevel) {
2661                 mHandler.sendMessageDelayed(mHandler.obtainMessage(H.RECHECK, mRow),
2662                         USER_ATTEMPT_GRACE_PERIOD);
2663             }
2664         }
2665     }
2666 
2667     private final class Accessibility extends AccessibilityDelegate {
2668         public void init() {
2669             mDialogView.setAccessibilityDelegate(this);
2670         }
2671 
2672         @Override
2673         public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
2674             // Activities populate their title here. Follow that example.
2675             event.getText().add(composeWindowTitle());
2676             return true;
2677         }
2678 
2679         @Override
2680         public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
2681                 AccessibilityEvent event) {
2682             rescheduleTimeoutH();
2683             return super.onRequestSendAccessibilityEvent(host, child, event);
2684         }
2685     }
2686 
2687     private static class VolumeRow {
2688         private static final SliderHapticFeedbackConfig sSliderHapticFeedbackConfig =
2689                 new SliderHapticFeedbackConfig(
2690                 /* velocityInterpolatorFactor= */ 1f,
2691                 /* progressInterpolatorFactor= */ 1f,
2692                 /* progressBasedDragMinScale= */ 0f,
2693                 /* progressBasedDragMaxScale= */ 0.2f,
2694                 /* additionalVelocityMaxBump= */ 0.25f,
2695                 /* deltaMillisForDragInterval= */ 0f,
2696                 /* deltaProgressForDragThreshold= */ 0.05f,
2697                 /* numberOfLowTicks= */ 4,
2698                 /* maxVelocityToScale= */ 200,
2699                 /* velocityAxis= */ MotionEvent.AXIS_Y,
2700                 /* upperBookendScale= */ 1f,
2701                 /* lowerBookendScale= */ 0.05f,
2702                 /* exponent= */ 1f / 0.89f,
2703                 /* sliderStepSize = */ 0f,
2704                 /* filter =*/new SliderHapticFeedbackFilter());
2705         private static final SeekableSliderTrackerConfig sSliderTrackerConfig =
2706                 new SeekableSliderTrackerConfig(
2707                         /* waitTimeMillis= */100,
2708                         /* jumpThreshold= */0.02f,
2709                         /* lowerBookendThreshold= */0.01f,
2710                         /* upperBookendThreshold= */0.99f
2711                 );
2712 
2713         private View view;
2714         private TextView header;
2715         private ImageButton icon;
2716         private Drawable sliderProgressSolid;
2717         private AlphaTintDrawableWrapper sliderProgressIcon;
2718         private SeekBar slider;
2719         private TextView number;
2720         private int stream;
2721         private StreamState ss;
2722         private long userAttempt;  // last user-driven slider change
2723         private boolean tracking;  // tracking slider touch
2724         private int requestedLevel = -1;  // pending user-requested level via progress changed
2725         private int iconRes;
2726         private int iconMuteRes;
2727         private boolean important;
2728         private boolean defaultStream;
2729         private int iconState;  // from Events
2730         private ObjectAnimator anim;  // slider progress animation for non-touch-related updates
2731         private int animTargetProgress;
2732         private int lastAudibleLevel = 1;
2733         private HapticSliderPlugin mHapticPlugin;
2734 
2735         void setIcon(int iconRes, Resources.Theme theme) {
2736             if (icon != null) {
2737                 icon.setImageResource(iconRes);
2738             }
2739 
2740             if (sliderProgressIcon != null) {
2741                 sliderProgressIcon.setDrawable(view.getResources().getDrawable(iconRes, theme));
2742             }
2743         }
2744 
2745         void createPlugin(
2746                 SeekBar seekBar,
2747                 VibratorHelper vibratorHelper,
2748                 MSDLPlayer msdlPlayer,
2749                 com.android.systemui.util.time.SystemClock systemClock) {
2750             if (mHapticPlugin != null) return;
2751 
2752             mHapticPlugin = new HapticSliderPlugin(
2753                 vibratorHelper,
2754                 msdlPlayer,
2755                 systemClock,
2756                 new HapticSlider.SeekBar(seekBar),
2757                 sSliderHapticFeedbackConfig,
2758                 sSliderTrackerConfig);
2759         }
2760 
2761 
2762         @SuppressLint("ClickableViewAccessibility")
2763         void addTouchListener() {
2764             slider.setOnTouchListener(new View.OnTouchListener() {
2765                 @Override
2766                 public boolean onTouch(View view, MotionEvent motionEvent) {
2767                     if (mHapticPlugin != null) {
2768                         mHapticPlugin.onTouchEvent(motionEvent);
2769                     }
2770                     return false;
2771                 }
2772             });
2773         }
2774 
2775         @SuppressLint("ClickableViewAccessibility")
2776         void removeHaptics() {
2777             slider.setOnTouchListener(null);
2778         }
2779 
2780         /**
2781          * Deliver haptics when the progress of the slider has changed.
2782          *
2783          * @param fromUser True if the progress changed was caused by the user.
2784          * @param progress The progress value of the slider.
2785          * @return True if haptics were successfully delivered. False otherwise. This will happen
2786          *   if mHapticPlugin is null
2787          */
2788         boolean deliverOnProgressChangedHaptics(boolean fromUser, int progress) {
2789             if (mHapticPlugin == null) return false;
2790 
2791             mHapticPlugin.onProgressChanged(progress, fromUser);
2792             if (!fromUser) {
2793                 // Consider a change from program as the volume key being continuously pressed
2794                 mHapticPlugin.onKeyDown();
2795             }
2796             return true;
2797         }
2798     }
2799 
2800     /**
2801      * Click listener added to each ringer option in the drawer. This will initiate the animation to
2802      * select and then close the ringer drawer, and actually change the ringer mode.
2803      */
2804     private class RingerDrawerItemClickListener implements View.OnClickListener {
2805         private final int mClickedRingerMode;
2806 
2807         RingerDrawerItemClickListener(int clickedRingerMode) {
2808             mClickedRingerMode = clickedRingerMode;
2809         }
2810 
2811         @Override
2812         public void onClick(View view) {
2813             // If the ringer drawer isn't open, don't let anything in it be clicked.
2814             if (!mIsRingerDrawerOpen) {
2815                 return;
2816             }
2817 
2818             setRingerMode(mClickedRingerMode);
2819 
2820             mRingerDrawerIconAnimatingSelected = getDrawerIconViewForMode(mClickedRingerMode);
2821             mRingerDrawerIconAnimatingDeselected = getDrawerIconViewForMode(
2822                     mState.ringerModeInternal);
2823 
2824             // Begin switching the selected icon and deselected icon colors since the background is
2825             // going to animate behind the new selection.
2826             mRingerDrawerIconColorAnimator.start();
2827 
2828             mSelectedRingerContainer.setVisibility(View.INVISIBLE);
2829             mRingerDrawerNewSelectionBg.setAlpha(1f);
2830             mRingerDrawerNewSelectionBg.animate()
2831                     .setInterpolator(Interpolators.ACCELERATE_DECELERATE)
2832                     .setDuration(DRAWER_ANIMATION_DURATION_SHORT)
2833                     .withEndAction(() -> {
2834                         mRingerDrawerNewSelectionBg.setAlpha(0f);
2835 
2836                         if (!isLandscape()) {
2837                             mSelectedRingerContainer.setTranslationY(
2838                                     getTranslationInDrawerForRingerMode(mClickedRingerMode));
2839                         } else {
2840                             mSelectedRingerContainer.setTranslationX(
2841                                     getTranslationInDrawerForRingerMode(mClickedRingerMode));
2842                         }
2843 
2844                         mSelectedRingerContainer.setVisibility(VISIBLE);
2845                         hideRingerDrawer();
2846                     });
2847 
2848             if (!isLandscape()) {
2849                 mRingerDrawerNewSelectionBg.animate()
2850                         .translationY(getTranslationInDrawerForRingerMode(mClickedRingerMode))
2851                         .start();
2852             } else {
2853                 mRingerDrawerNewSelectionBg.animate()
2854                         .translationX(getTranslationInDrawerForRingerMode(mClickedRingerMode))
2855                         .start();
2856             }
2857         }
2858     }
2859 }
2860