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 VolumeDialog(Context context, int windowType, VolumeDialogController controller, ZenModeController zenModeController, Callback callback)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 initDialog()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 onTuningChanged(String key, String newValue)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 loadColorStateList(int colorResId)269 private ColorStateList loadColorStateList(int colorResId) { 270 return ColorStateList.valueOf(mContext.getColor(colorResId)); 271 } 272 updateWindowWidthH()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 setStreamImportant(int stream, boolean important)287 public void setStreamImportant(int stream, boolean important) { 288 mHandler.obtainMessage(H.SET_STREAM_IMPORTANT, stream, important ? 1 : 0).sendToTarget(); 289 } 290 setShowHeaders(boolean showHeaders)291 public void setShowHeaders(boolean showHeaders) { 292 if (showHeaders == mShowHeaders) return; 293 mShowHeaders = showHeaders; 294 mHandler.sendEmptyMessage(H.RECHECK_ALL); 295 } 296 setAutomute(boolean automute)297 public void setAutomute(boolean automute) { 298 if (mAutomute == automute) return; 299 mAutomute = automute; 300 mHandler.sendEmptyMessage(H.RECHECK_ALL); 301 } 302 setSilentMode(boolean silentMode)303 public void setSilentMode(boolean silentMode) { 304 if (mSilentMode == silentMode) return; 305 mSilentMode = silentMode; 306 mHandler.sendEmptyMessage(H.RECHECK_ALL); 307 } 308 addRow(int stream, int iconRes, int iconMuteRes, boolean important)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 addExistingRows()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 isAttached()326 private boolean isAttached() { 327 return mDialogContentView != null && mDialogContentView.isAttachedToWindow(); 328 } 329 getActiveRow()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 findRow(int stream)339 private VolumeRow findRow(int stream) { 340 for (VolumeRow row : mRows) { 341 if (row.stream == stream) return row; 342 } 343 return null; 344 } 345 dump(PrintWriter writer)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 getImpliedLevel(SeekBar seekBar, int progress)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") initRow(final VolumeRow row, final int stream, int iconRes, int iconMuteRes, boolean important)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 destroy()445 public void destroy() { 446 mController.removeCallback(mControllerCallbackH); 447 } 448 show(int reason)449 public void show(int reason) { 450 mHandler.obtainMessage(H.SHOW, reason, 0).sendToTarget(); 451 } 452 dismiss(int reason)453 public void dismiss(int reason) { 454 mHandler.obtainMessage(H.DISMISS, reason, 0).sendToTarget(); 455 } 456 showH(int reason)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 rescheduleTimeoutH()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 computeTimeoutH()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 dismissH(int reason)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 updateDialogBottomMarginH()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 onStateChangedH(State state)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 updateFooterH()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 updateVolumeRowH(VolumeRow row)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 updateVolumeRowHeaderVisibleH(VolumeRow row)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 updateVolumeRowSliderTintH(VolumeRow row, boolean isActive)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 updateVolumeRowSliderH(VolumeRow row, boolean enable, int vlevel)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 onStartTrackingTouch(SeekBar seekBar)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 onStopTrackingTouch(SeekBar seekBar)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 init()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 onRequestSendAccessibilityEvent(ViewGroup host, View child, AccessibilityEvent event)1206 public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child, 1207 AccessibilityEvent event) { 1208 rescheduleTimeoutH(); 1209 return super.onRequestSendAccessibilityEvent(host, child, event); 1210 } 1211 updateFeedbackEnabled()1212 private void updateFeedbackEnabled() { 1213 mFeedbackEnabled = computeFeedbackEnabled(); 1214 } 1215 computeFeedbackEnabled()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 { onZenSettingsClicked()1252 void onZenSettingsClicked(); onZenPrioritySettingsClicked()1253 void onZenPrioritySettingsClicked(); 1254 } 1255 } 1256