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