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