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