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