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