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