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