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.app.ActivityManager.LOCK_TASK_MODE_NONE; 20 import static android.media.AudioManager.RINGER_MODE_NORMAL; 21 import static android.media.AudioManager.RINGER_MODE_SILENT; 22 import static android.media.AudioManager.RINGER_MODE_VIBRATE; 23 import static android.media.AudioManager.STREAM_ACCESSIBILITY; 24 import static android.media.AudioManager.STREAM_ALARM; 25 import static android.media.AudioManager.STREAM_MUSIC; 26 import static android.media.AudioManager.STREAM_RING; 27 import static android.media.AudioManager.STREAM_VOICE_CALL; 28 import static android.view.View.ACCESSIBILITY_LIVE_REGION_POLITE; 29 import static android.view.View.GONE; 30 import static android.view.View.INVISIBLE; 31 import static android.view.View.LAYOUT_DIRECTION_RTL; 32 import static android.view.View.VISIBLE; 33 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 34 35 import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED; 36 37 import android.animation.Animator; 38 import android.animation.AnimatorListenerAdapter; 39 import android.animation.ArgbEvaluator; 40 import android.animation.ObjectAnimator; 41 import android.animation.ValueAnimator; 42 import android.annotation.SuppressLint; 43 import android.app.ActivityManager; 44 import android.app.Dialog; 45 import android.app.KeyguardManager; 46 import android.content.ContentResolver; 47 import android.content.Context; 48 import android.content.DialogInterface; 49 import android.content.Intent; 50 import android.content.pm.PackageManager; 51 import android.content.res.ColorStateList; 52 import android.content.res.Configuration; 53 import android.content.res.Resources; 54 import android.content.res.TypedArray; 55 import android.graphics.Color; 56 import android.graphics.Outline; 57 import android.graphics.PixelFormat; 58 import android.graphics.Rect; 59 import android.graphics.Region; 60 import android.graphics.drawable.ColorDrawable; 61 import android.graphics.drawable.Drawable; 62 import android.graphics.drawable.LayerDrawable; 63 import android.graphics.drawable.RotateDrawable; 64 import android.media.AudioManager; 65 import android.media.AudioSystem; 66 import android.os.Debug; 67 import android.os.Handler; 68 import android.os.Looper; 69 import android.os.Message; 70 import android.os.SystemClock; 71 import android.os.VibrationEffect; 72 import android.provider.Settings; 73 import android.provider.Settings.Global; 74 import android.text.InputFilter; 75 import android.util.Log; 76 import android.util.Slog; 77 import android.util.SparseBooleanArray; 78 import android.view.ContextThemeWrapper; 79 import android.view.Gravity; 80 import android.view.MotionEvent; 81 import android.view.View; 82 import android.view.View.AccessibilityDelegate; 83 import android.view.View.OnAttachStateChangeListener; 84 import android.view.ViewGroup; 85 import android.view.ViewOutlineProvider; 86 import android.view.ViewPropertyAnimator; 87 import android.view.ViewStub; 88 import android.view.ViewTreeObserver; 89 import android.view.Window; 90 import android.view.WindowManager; 91 import android.view.accessibility.AccessibilityEvent; 92 import android.view.accessibility.AccessibilityManager; 93 import android.view.accessibility.AccessibilityNodeInfo; 94 import android.view.animation.DecelerateInterpolator; 95 import android.widget.FrameLayout; 96 import android.widget.ImageButton; 97 import android.widget.ImageView; 98 import android.widget.LinearLayout; 99 import android.widget.SeekBar; 100 import android.widget.SeekBar.OnSeekBarChangeListener; 101 import android.widget.TextView; 102 import android.widget.Toast; 103 104 import androidx.annotation.Nullable; 105 106 import com.android.internal.graphics.drawable.BackgroundBlurDrawable; 107 import com.android.settingslib.Utils; 108 import com.android.systemui.Dependency; 109 import com.android.systemui.Prefs; 110 import com.android.systemui.R; 111 import com.android.systemui.animation.Interpolators; 112 import com.android.systemui.media.dialog.MediaOutputDialogFactory; 113 import com.android.systemui.plugins.ActivityStarter; 114 import com.android.systemui.plugins.VolumeDialog; 115 import com.android.systemui.plugins.VolumeDialogController; 116 import com.android.systemui.plugins.VolumeDialogController.State; 117 import com.android.systemui.plugins.VolumeDialogController.StreamState; 118 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; 119 import com.android.systemui.statusbar.policy.ConfigurationController; 120 import com.android.systemui.statusbar.policy.DeviceProvisionedController; 121 import com.android.systemui.util.AlphaTintDrawableWrapper; 122 import com.android.systemui.util.RoundedCornerProgressDrawable; 123 124 import java.io.PrintWriter; 125 import java.util.ArrayList; 126 import java.util.List; 127 import java.util.function.Consumer; 128 129 /** 130 * Visual presentation of the volume dialog. 131 * 132 * A client of VolumeDialogControllerImpl and its state model. 133 * 134 * Methods ending in "H" must be called on the (ui) handler. 135 */ 136 public class VolumeDialogImpl implements VolumeDialog, 137 ConfigurationController.ConfigurationListener, 138 ViewTreeObserver.OnComputeInternalInsetsListener { 139 private static final String TAG = Util.logTag(VolumeDialogImpl.class); 140 141 private static final long USER_ATTEMPT_GRACE_PERIOD = 1000; 142 private static final int UPDATE_ANIMATION_DURATION = 80; 143 144 static final int DIALOG_TIMEOUT_MILLIS = 3000; 145 static final int DIALOG_SAFETYWARNING_TIMEOUT_MILLIS = 5000; 146 static final int DIALOG_ODI_CAPTIONS_TOOLTIP_TIMEOUT_MILLIS = 5000; 147 static final int DIALOG_HOVERING_TIMEOUT_MILLIS = 16000; 148 149 private static final int DRAWER_ANIMATION_DURATION_SHORT = 175; 150 private static final int DRAWER_ANIMATION_DURATION = 250; 151 152 private final int mDialogShowAnimationDurationMs; 153 private final int mDialogHideAnimationDurationMs; 154 private int mDialogWidth; 155 private int mDialogCornerRadius; 156 private int mRingerDrawerItemSize; 157 private int mRingerRowsPadding; 158 private boolean mShowVibrate; 159 private int mRingerCount; 160 private final boolean mShowLowMediaVolumeIcon; 161 private final boolean mChangeVolumeRowTintWhenInactive; 162 163 private final Context mContext; 164 private final H mHandler = new H(); 165 private final VolumeDialogController mController; 166 private final DeviceProvisionedController mDeviceProvisionedController; 167 private final Region mTouchableRegion = new Region(); 168 169 private Window mWindow; 170 private CustomDialog mDialog; 171 private ViewGroup mDialogView; 172 private ViewGroup mDialogRowsViewContainer; 173 private ViewGroup mDialogRowsView; 174 private ViewGroup mRinger; 175 176 /** 177 * Container for the top part of the dialog, which contains the ringer, the ringer drawer, the 178 * volume rows, and the ellipsis button. This does not include the live caption button. 179 */ 180 @Nullable private View mTopContainer; 181 182 /** Container for the ringer icon, and for the (initially hidden) ringer drawer view. */ 183 @Nullable private View mRingerAndDrawerContainer; 184 185 /** 186 * Background drawable for the ringer and drawer container. The background's top bound is 187 * initially inset by the height of the (hidden) ringer drawer. When the drawer is animated in, 188 * this top bound is animated to accommodate it. 189 */ 190 @Nullable private Drawable mRingerAndDrawerContainerBackground; 191 192 private ViewGroup mSelectedRingerContainer; 193 private ImageView mSelectedRingerIcon; 194 195 private ViewGroup mRingerDrawerContainer; 196 private ViewGroup mRingerDrawerMute; 197 private ViewGroup mRingerDrawerVibrate; 198 private ViewGroup mRingerDrawerNormal; 199 private ImageView mRingerDrawerMuteIcon; 200 private ImageView mRingerDrawerVibrateIcon; 201 private ImageView mRingerDrawerNormalIcon; 202 203 /** 204 * View that draws the 'selected' background behind one of the three ringer choices in the 205 * drawer. 206 */ 207 private ViewGroup mRingerDrawerNewSelectionBg; 208 209 private final ValueAnimator mRingerDrawerIconColorAnimator = ValueAnimator.ofFloat(0f, 1f); 210 private ImageView mRingerDrawerIconAnimatingSelected; 211 private ImageView mRingerDrawerIconAnimatingDeselected; 212 213 /** 214 * Animates the volume dialog's background drawable bounds upwards, to match the height of the 215 * expanded ringer drawer. 216 */ 217 private final ValueAnimator mAnimateUpBackgroundToMatchDrawer = ValueAnimator.ofFloat(1f, 0f); 218 219 private boolean mIsRingerDrawerOpen = false; 220 private float mRingerDrawerClosedAmount = 1f; 221 222 private ImageButton mRingerIcon; 223 private ViewGroup mODICaptionsView; 224 private CaptionsToggleImageButton mODICaptionsIcon; 225 private View mSettingsView; 226 private ImageButton mSettingsIcon; 227 private FrameLayout mZenIcon; 228 private final List<VolumeRow> mRows = new ArrayList<>(); 229 private ConfigurableTexts mConfigurableTexts; 230 private final SparseBooleanArray mDynamic = new SparseBooleanArray(); 231 private final KeyguardManager mKeyguard; 232 private final ActivityManager mActivityManager; 233 private final AccessibilityManagerWrapper mAccessibilityMgr; 234 private final Object mSafetyWarningLock = new Object(); 235 private final Accessibility mAccessibility = new Accessibility(); 236 237 private boolean mShowing; 238 private boolean mShowA11yStream; 239 240 private int mActiveStream; 241 private int mPrevActiveStream; 242 private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE; 243 private boolean mSilentMode = VolumePrefs.DEFAULT_ENABLE_SILENT_MODE; 244 private State mState; 245 private SafetyWarningDialog mSafetyWarning; 246 private boolean mHovering = false; 247 private boolean mShowActiveStreamOnly; 248 private boolean mConfigChanged = false; 249 private boolean mIsAnimatingDismiss = false; 250 private boolean mHasSeenODICaptionsTooltip; 251 private ViewStub mODICaptionsTooltipViewStub; 252 private View mODICaptionsTooltipView = null; 253 254 private final boolean mUseBackgroundBlur; 255 private Consumer<Boolean> mCrossWindowBlurEnabledListener; 256 private BackgroundBlurDrawable mDialogRowsViewBackground; 257 VolumeDialogImpl(Context context)258 public VolumeDialogImpl(Context context) { 259 mContext = 260 new ContextThemeWrapper(context, R.style.volume_dialog_theme); 261 mController = Dependency.get(VolumeDialogController.class); 262 mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); 263 mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); 264 mAccessibilityMgr = Dependency.get(AccessibilityManagerWrapper.class); 265 mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); 266 mShowActiveStreamOnly = showActiveStreamOnly(); 267 mHasSeenODICaptionsTooltip = 268 Prefs.getBoolean(context, Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, false); 269 mShowLowMediaVolumeIcon = 270 mContext.getResources().getBoolean(R.bool.config_showLowMediaVolumeIcon); 271 mChangeVolumeRowTintWhenInactive = 272 mContext.getResources().getBoolean(R.bool.config_changeVolumeRowTintWhenInactive); 273 mDialogShowAnimationDurationMs = 274 mContext.getResources().getInteger(R.integer.config_dialogShowAnimationDurationMs); 275 mDialogHideAnimationDurationMs = 276 mContext.getResources().getInteger(R.integer.config_dialogHideAnimationDurationMs); 277 mUseBackgroundBlur = 278 mContext.getResources().getBoolean(R.bool.config_volumeDialogUseBackgroundBlur); 279 280 if (mUseBackgroundBlur) { 281 final int dialogRowsViewColorAboveBlur = mContext.getColor( 282 R.color.volume_dialog_background_color_above_blur); 283 final int dialogRowsViewColorNoBlur = mContext.getColor( 284 R.color.volume_dialog_background_color); 285 mCrossWindowBlurEnabledListener = (enabled) -> { 286 mDialogRowsViewBackground.setColor( 287 enabled ? dialogRowsViewColorAboveBlur : dialogRowsViewColorNoBlur); 288 mDialogRowsView.invalidate(); 289 }; 290 } 291 292 initDimens(); 293 } 294 295 @Override onUiModeChanged()296 public void onUiModeChanged() { 297 mContext.getTheme().applyStyle(mContext.getThemeResId(), true); 298 } 299 init(int windowType, Callback callback)300 public void init(int windowType, Callback callback) { 301 initDialog(); 302 303 mAccessibility.init(); 304 305 mController.addCallback(mControllerCallbackH, mHandler); 306 mController.getState(); 307 308 Dependency.get(ConfigurationController.class).addCallback(this); 309 } 310 311 @Override destroy()312 public void destroy() { 313 mController.removeCallback(mControllerCallbackH); 314 mHandler.removeCallbacksAndMessages(null); 315 Dependency.get(ConfigurationController.class).removeCallback(this); 316 } 317 318 @Override onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo internalInsetsInfo)319 public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo internalInsetsInfo) { 320 // Set touchable region insets on the root dialog view. This tells WindowManager that 321 // touches outside of this region should not be delivered to the volume window, and instead 322 // go to the window below. This is the only way to do this - returning false in 323 // onDispatchTouchEvent results in the event being ignored entirely, rather than passed to 324 // the next window. 325 internalInsetsInfo.setTouchableInsets( 326 ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); 327 328 mTouchableRegion.setEmpty(); 329 330 // Set the touchable region to the union of all child view bounds and the live caption 331 // tooltip. We don't use touches on the volume dialog container itself, so this is fine. 332 for (int i = 0; i < mDialogView.getChildCount(); i++) { 333 unionViewBoundstoTouchableRegion(mDialogView.getChildAt(i)); 334 } 335 336 if (mODICaptionsTooltipView != null && mODICaptionsTooltipView.getVisibility() == VISIBLE) { 337 unionViewBoundstoTouchableRegion(mODICaptionsTooltipView); 338 } 339 340 internalInsetsInfo.touchableRegion.set(mTouchableRegion); 341 } 342 unionViewBoundstoTouchableRegion(final View view)343 private void unionViewBoundstoTouchableRegion(final View view) { 344 final int[] locInWindow = new int[2]; 345 view.getLocationInWindow(locInWindow); 346 347 float x = locInWindow[0]; 348 float y = locInWindow[1]; 349 350 // The ringer and rows container has extra height at the top to fit the expanded ringer 351 // drawer. This area should not be touchable unless the ringer drawer is open. 352 if (view == mTopContainer && !mIsRingerDrawerOpen) { 353 if (!isLandscape()) { 354 y += getRingerDrawerOpenExtraSize(); 355 } else { 356 x += getRingerDrawerOpenExtraSize(); 357 } 358 } 359 360 mTouchableRegion.op( 361 (int) x, 362 (int) y, 363 locInWindow[0] + view.getWidth(), 364 locInWindow[1] + view.getHeight(), 365 Region.Op.UNION); 366 } 367 initDialog()368 private void initDialog() { 369 mDialog = new CustomDialog(mContext); 370 371 initDimens(); 372 373 mConfigurableTexts = new ConfigurableTexts(mContext); 374 mHovering = false; 375 mShowing = false; 376 mWindow = mDialog.getWindow(); 377 mWindow.requestFeature(Window.FEATURE_NO_TITLE); 378 mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); 379 mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND 380 | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); 381 mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 382 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 383 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED 384 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH 385 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); 386 mWindow.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY); 387 mWindow.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY); 388 mWindow.setWindowAnimations(com.android.internal.R.style.Animation_Toast); 389 WindowManager.LayoutParams lp = mWindow.getAttributes(); 390 lp.format = PixelFormat.TRANSLUCENT; 391 lp.setTitle(VolumeDialogImpl.class.getSimpleName()); 392 lp.windowAnimations = -1; 393 lp.gravity = mContext.getResources().getInteger(R.integer.volume_dialog_gravity); 394 mWindow.setAttributes(lp); 395 mWindow.setLayout(WRAP_CONTENT, WRAP_CONTENT); 396 397 mDialog.setContentView(R.layout.volume_dialog); 398 mDialogView = mDialog.findViewById(R.id.volume_dialog); 399 mDialogView.setAlpha(0); 400 mDialog.setCanceledOnTouchOutside(true); 401 mDialog.setOnShowListener(dialog -> { 402 mDialogView.getViewTreeObserver().addOnComputeInternalInsetsListener(this); 403 if (!isLandscape()) mDialogView.setTranslationX(mDialogView.getWidth() / 2.0f); 404 mDialogView.setAlpha(0); 405 mDialogView.animate() 406 .alpha(1) 407 .translationX(0) 408 .setDuration(mDialogShowAnimationDurationMs) 409 .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator()) 410 .withEndAction(() -> { 411 if (!Prefs.getBoolean(mContext, Prefs.Key.TOUCHED_RINGER_TOGGLE, false)) { 412 if (mRingerIcon != null) { 413 mRingerIcon.postOnAnimationDelayed( 414 getSinglePressFor(mRingerIcon), 1500); 415 } 416 } 417 }) 418 .start(); 419 }); 420 421 mDialog.setOnDismissListener(dialogInterface -> 422 mDialogView 423 .getViewTreeObserver() 424 .removeOnComputeInternalInsetsListener(VolumeDialogImpl.this)); 425 426 mDialogView.setOnHoverListener((v, event) -> { 427 int action = event.getActionMasked(); 428 mHovering = (action == MotionEvent.ACTION_HOVER_ENTER) 429 || (action == MotionEvent.ACTION_HOVER_MOVE); 430 rescheduleTimeoutH(); 431 return true; 432 }); 433 434 mDialogRowsView = mDialog.findViewById(R.id.volume_dialog_rows); 435 if (mUseBackgroundBlur) { 436 mDialogView.addOnAttachStateChangeListener(new OnAttachStateChangeListener() { 437 @Override 438 public void onViewAttachedToWindow(View v) { 439 mWindow.getWindowManager().addCrossWindowBlurEnabledListener( 440 mCrossWindowBlurEnabledListener); 441 442 mDialogRowsViewBackground = v.getViewRootImpl().createBackgroundBlurDrawable(); 443 444 final Resources resources = mContext.getResources(); 445 mDialogRowsViewBackground.setCornerRadius( 446 mContext.getResources().getDimensionPixelSize(Utils.getThemeAttr( 447 mContext, android.R.attr.dialogCornerRadius))); 448 mDialogRowsViewBackground.setBlurRadius(resources.getDimensionPixelSize( 449 R.dimen.volume_dialog_background_blur_radius)); 450 mDialogRowsView.setBackground(mDialogRowsViewBackground); 451 } 452 453 @Override 454 public void onViewDetachedFromWindow(View v) { 455 mWindow.getWindowManager().removeCrossWindowBlurEnabledListener( 456 mCrossWindowBlurEnabledListener); 457 } 458 }); 459 } 460 461 mDialogRowsViewContainer = mDialogView.findViewById(R.id.volume_dialog_rows_container); 462 mTopContainer = mDialogView.findViewById(R.id.volume_dialog_top_container); 463 mRingerAndDrawerContainer = mDialogView.findViewById( 464 R.id.volume_ringer_and_drawer_container); 465 466 if (mRingerAndDrawerContainer != null) { 467 if (isLandscape()) { 468 // In landscape, we need to add padding to the bottom of the ringer drawer so that 469 // when it expands to the left, it doesn't overlap any additional volume rows. 470 mRingerAndDrawerContainer.setPadding( 471 mRingerAndDrawerContainer.getPaddingLeft(), 472 mRingerAndDrawerContainer.getPaddingTop(), 473 mRingerAndDrawerContainer.getPaddingRight(), 474 mRingerRowsPadding); 475 476 // Since the ringer drawer is expanding to the left, outside of the background of 477 // the dialog, it needs its own rounded background drawable. We also need that 478 // background to be rounded on all sides. We'll use a background rounded on all four 479 // corners, and then extend the container's background later to fill in the bottom 480 // corners when the drawer is closed. 481 mRingerAndDrawerContainer.setBackgroundDrawable( 482 mContext.getDrawable(R.drawable.volume_background_top_rounded)); 483 } 484 485 // Post to wait for layout so that the background bounds are set. 486 mRingerAndDrawerContainer.post(() -> { 487 final LayerDrawable ringerAndDrawerBg = 488 (LayerDrawable) mRingerAndDrawerContainer.getBackground(); 489 490 // Retrieve the ShapeDrawable from within the background - this is what we will 491 // animate up and down when the drawer is opened/closed. 492 if (ringerAndDrawerBg != null && ringerAndDrawerBg.getNumberOfLayers() > 0) { 493 mRingerAndDrawerContainerBackground = ringerAndDrawerBg.getDrawable(0); 494 495 updateBackgroundForDrawerClosedAmount(); 496 setTopContainerBackgroundDrawable(); 497 } 498 }); 499 } 500 501 mRinger = mDialog.findViewById(R.id.ringer); 502 if (mRinger != null) { 503 mRingerIcon = mRinger.findViewById(R.id.ringer_icon); 504 mZenIcon = mRinger.findViewById(R.id.dnd_icon); 505 } 506 507 mSelectedRingerIcon = mDialog.findViewById(R.id.volume_new_ringer_active_icon); 508 mSelectedRingerContainer = mDialog.findViewById( 509 R.id.volume_new_ringer_active_icon_container); 510 511 mRingerDrawerMute = mDialog.findViewById(R.id.volume_drawer_mute); 512 mRingerDrawerNormal = mDialog.findViewById(R.id.volume_drawer_normal); 513 mRingerDrawerVibrate = mDialog.findViewById(R.id.volume_drawer_vibrate); 514 mRingerDrawerMuteIcon = mDialog.findViewById(R.id.volume_drawer_mute_icon); 515 mRingerDrawerVibrateIcon = mDialog.findViewById(R.id.volume_drawer_vibrate_icon); 516 mRingerDrawerNormalIcon = mDialog.findViewById(R.id.volume_drawer_normal_icon); 517 mRingerDrawerNewSelectionBg = mDialog.findViewById(R.id.volume_drawer_selection_background); 518 519 setupRingerDrawer(); 520 521 mODICaptionsView = mDialog.findViewById(R.id.odi_captions); 522 if (mODICaptionsView != null) { 523 mODICaptionsIcon = mODICaptionsView.findViewById(R.id.odi_captions_icon); 524 } 525 mODICaptionsTooltipViewStub = mDialog.findViewById(R.id.odi_captions_tooltip_stub); 526 if (mHasSeenODICaptionsTooltip && mODICaptionsTooltipViewStub != null) { 527 mDialogView.removeView(mODICaptionsTooltipViewStub); 528 mODICaptionsTooltipViewStub = null; 529 } 530 531 mSettingsView = mDialog.findViewById(R.id.settings_container); 532 mSettingsIcon = mDialog.findViewById(R.id.settings); 533 534 if (mRows.isEmpty()) { 535 if (!AudioSystem.isSingleVolume(mContext)) { 536 addRow(STREAM_ACCESSIBILITY, R.drawable.ic_volume_accessibility, 537 R.drawable.ic_volume_accessibility, true, false); 538 } 539 addRow(AudioManager.STREAM_MUSIC, 540 R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true, true); 541 if (!AudioSystem.isSingleVolume(mContext)) { 542 addRow(AudioManager.STREAM_RING, 543 R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true, false); 544 addRow(STREAM_ALARM, 545 R.drawable.ic_alarm, R.drawable.ic_volume_alarm_mute, true, false); 546 addRow(AudioManager.STREAM_VOICE_CALL, 547 com.android.internal.R.drawable.ic_phone, 548 com.android.internal.R.drawable.ic_phone, false, false); 549 addRow(AudioManager.STREAM_BLUETOOTH_SCO, 550 R.drawable.ic_volume_bt_sco, R.drawable.ic_volume_bt_sco, false, false); 551 addRow(AudioManager.STREAM_SYSTEM, R.drawable.ic_volume_system, 552 R.drawable.ic_volume_system_mute, false, false); 553 } 554 } else { 555 addExistingRows(); 556 } 557 558 updateRowsH(getActiveRow()); 559 initRingerH(); 560 initSettingsH(); 561 initODICaptionsH(); 562 } 563 initDimens()564 private void initDimens() { 565 mDialogWidth = mContext.getResources().getDimensionPixelSize( 566 R.dimen.volume_dialog_panel_width); 567 mDialogCornerRadius = mContext.getResources().getDimensionPixelSize( 568 R.dimen.volume_dialog_panel_width_half); 569 mRingerDrawerItemSize = mContext.getResources().getDimensionPixelSize( 570 R.dimen.volume_ringer_drawer_item_size); 571 mRingerRowsPadding = mContext.getResources().getDimensionPixelSize( 572 R.dimen.volume_dialog_ringer_rows_padding); 573 mShowVibrate = mController.hasVibrator(); 574 575 // Normal, mute, and possibly vibrate. 576 mRingerCount = mShowVibrate ? 3 : 2; 577 } 578 getDialogView()579 protected ViewGroup getDialogView() { 580 return mDialogView; 581 } 582 getAlphaAttr(int attr)583 private int getAlphaAttr(int attr) { 584 TypedArray ta = mContext.obtainStyledAttributes(new int[]{attr}); 585 float alpha = ta.getFloat(0, 0); 586 ta.recycle(); 587 return (int) (alpha * 255); 588 } 589 isLandscape()590 private boolean isLandscape() { 591 return mContext.getResources().getConfiguration().orientation == 592 Configuration.ORIENTATION_LANDSCAPE; 593 } 594 isRtl()595 private boolean isRtl() { 596 return mContext.getResources().getConfiguration().getLayoutDirection() 597 == LAYOUT_DIRECTION_RTL; 598 } 599 setStreamImportant(int stream, boolean important)600 public void setStreamImportant(int stream, boolean important) { 601 mHandler.obtainMessage(H.SET_STREAM_IMPORTANT, stream, important ? 1 : 0).sendToTarget(); 602 } 603 setAutomute(boolean automute)604 public void setAutomute(boolean automute) { 605 if (mAutomute == automute) return; 606 mAutomute = automute; 607 mHandler.sendEmptyMessage(H.RECHECK_ALL); 608 } 609 setSilentMode(boolean silentMode)610 public void setSilentMode(boolean silentMode) { 611 if (mSilentMode == silentMode) return; 612 mSilentMode = silentMode; 613 mHandler.sendEmptyMessage(H.RECHECK_ALL); 614 } 615 addRow(int stream, int iconRes, int iconMuteRes, boolean important, boolean defaultStream)616 private void addRow(int stream, int iconRes, int iconMuteRes, boolean important, 617 boolean defaultStream) { 618 addRow(stream, iconRes, iconMuteRes, important, defaultStream, false); 619 } 620 addRow(int stream, int iconRes, int iconMuteRes, boolean important, boolean defaultStream, boolean dynamic)621 private void addRow(int stream, int iconRes, int iconMuteRes, boolean important, 622 boolean defaultStream, boolean dynamic) { 623 if (D.BUG) Slog.d(TAG, "Adding row for stream " + stream); 624 VolumeRow row = new VolumeRow(); 625 initRow(row, stream, iconRes, iconMuteRes, important, defaultStream); 626 mDialogRowsView.addView(row.view); 627 mRows.add(row); 628 } 629 addExistingRows()630 private void addExistingRows() { 631 int N = mRows.size(); 632 for (int i = 0; i < N; i++) { 633 final VolumeRow row = mRows.get(i); 634 initRow(row, row.stream, row.iconRes, row.iconMuteRes, row.important, 635 row.defaultStream); 636 mDialogRowsView.addView(row.view); 637 updateVolumeRowH(row); 638 } 639 } 640 getActiveRow()641 private VolumeRow getActiveRow() { 642 for (VolumeRow row : mRows) { 643 if (row.stream == mActiveStream) { 644 return row; 645 } 646 } 647 for (VolumeRow row : mRows) { 648 if (row.stream == STREAM_MUSIC) { 649 return row; 650 } 651 } 652 return mRows.get(0); 653 } 654 findRow(int stream)655 private VolumeRow findRow(int stream) { 656 for (VolumeRow row : mRows) { 657 if (row.stream == stream) return row; 658 } 659 return null; 660 } 661 dump(PrintWriter writer)662 public void dump(PrintWriter writer) { 663 writer.println(VolumeDialogImpl.class.getSimpleName() + " state:"); 664 writer.print(" mShowing: "); writer.println(mShowing); 665 writer.print(" mActiveStream: "); writer.println(mActiveStream); 666 writer.print(" mDynamic: "); writer.println(mDynamic); 667 writer.print(" mAutomute: "); writer.println(mAutomute); 668 writer.print(" mSilentMode: "); writer.println(mSilentMode); 669 } 670 getImpliedLevel(SeekBar seekBar, int progress)671 private static int getImpliedLevel(SeekBar seekBar, int progress) { 672 final int m = seekBar.getMax(); 673 final int n = m / 100 - 1; 674 final int level = progress == 0 ? 0 675 : progress == m ? (m / 100) : (1 + (int)((progress / (float) m) * n)); 676 return level; 677 } 678 679 @SuppressLint("InflateParams") initRow(final VolumeRow row, final int stream, int iconRes, int iconMuteRes, boolean important, boolean defaultStream)680 private void initRow(final VolumeRow row, final int stream, int iconRes, int iconMuteRes, 681 boolean important, boolean defaultStream) { 682 row.stream = stream; 683 row.iconRes = iconRes; 684 row.iconMuteRes = iconMuteRes; 685 row.important = important; 686 row.defaultStream = defaultStream; 687 row.view = mDialog.getLayoutInflater().inflate(R.layout.volume_dialog_row, null); 688 row.view.setId(row.stream); 689 row.view.setTag(row); 690 row.header = row.view.findViewById(R.id.volume_row_header); 691 row.header.setId(20 * row.stream); 692 if (stream == STREAM_ACCESSIBILITY) { 693 row.header.setFilters(new InputFilter[] {new InputFilter.LengthFilter(13)}); 694 } 695 row.dndIcon = row.view.findViewById(R.id.dnd_icon); 696 row.slider = row.view.findViewById(R.id.volume_row_slider); 697 row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row)); 698 row.number = row.view.findViewById(R.id.volume_number); 699 700 row.anim = null; 701 702 final LayerDrawable seekbarDrawable = 703 (LayerDrawable) mContext.getDrawable(R.drawable.volume_row_seekbar); 704 705 final LayerDrawable seekbarProgressDrawable = (LayerDrawable) 706 ((RoundedCornerProgressDrawable) seekbarDrawable.findDrawableByLayerId( 707 android.R.id.progress)).getDrawable(); 708 709 row.sliderProgressSolid = seekbarProgressDrawable.findDrawableByLayerId( 710 R.id.volume_seekbar_progress_solid); 711 final Drawable sliderProgressIcon = seekbarProgressDrawable.findDrawableByLayerId( 712 R.id.volume_seekbar_progress_icon); 713 row.sliderProgressIcon = sliderProgressIcon != null ? (AlphaTintDrawableWrapper) 714 ((RotateDrawable) sliderProgressIcon).getDrawable() : null; 715 716 row.slider.setProgressDrawable(seekbarDrawable); 717 718 row.icon = row.view.findViewById(R.id.volume_row_icon); 719 720 row.setIcon(iconRes, mContext.getTheme()); 721 722 if (row.icon != null) { 723 if (row.stream != AudioSystem.STREAM_ACCESSIBILITY) { 724 row.icon.setOnClickListener(v -> { 725 Events.writeEvent(Events.EVENT_ICON_CLICK, row.stream, row.iconState); 726 mController.setActiveStream(row.stream); 727 if (row.stream == AudioManager.STREAM_RING) { 728 final boolean hasVibrator = mController.hasVibrator(); 729 if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) { 730 if (hasVibrator) { 731 mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false); 732 } else { 733 final boolean wasZero = row.ss.level == 0; 734 mController.setStreamVolume(stream, 735 wasZero ? row.lastAudibleLevel : 0); 736 } 737 } else { 738 mController.setRingerMode( 739 AudioManager.RINGER_MODE_NORMAL, false); 740 if (row.ss.level == 0) { 741 mController.setStreamVolume(stream, 1); 742 } 743 } 744 } else { 745 final boolean vmute = row.ss.level == row.ss.levelMin; 746 mController.setStreamVolume(stream, 747 vmute ? row.lastAudibleLevel : row.ss.levelMin); 748 } 749 row.userAttempt = 0; // reset the grace period, slider updates immediately 750 }); 751 } else { 752 row.icon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); 753 } 754 } 755 } 756 setRingerMode(int newRingerMode)757 private void setRingerMode(int newRingerMode) { 758 Events.writeEvent(Events.EVENT_RINGER_TOGGLE, newRingerMode); 759 incrementManualToggleCount(); 760 updateRingerH(); 761 provideTouchFeedbackH(newRingerMode); 762 mController.setRingerMode(newRingerMode, false); 763 maybeShowToastH(newRingerMode); 764 } 765 setupRingerDrawer()766 private void setupRingerDrawer() { 767 mRingerDrawerContainer = mDialog.findViewById(R.id.volume_drawer_container); 768 769 if (mRingerDrawerContainer == null) { 770 return; 771 } 772 773 if (!mShowVibrate) { 774 mRingerDrawerVibrate.setVisibility(GONE); 775 } 776 777 // In portrait, add padding to the bottom to account for the height of the open ringer 778 // drawer. 779 if (!isLandscape()) { 780 mDialogView.setPadding( 781 mDialogView.getPaddingLeft(), 782 mDialogView.getPaddingTop(), 783 mDialogView.getPaddingRight(), 784 mDialogView.getPaddingBottom() + getRingerDrawerOpenExtraSize()); 785 } else { 786 mDialogView.setPadding( 787 mDialogView.getPaddingLeft() + getRingerDrawerOpenExtraSize(), 788 mDialogView.getPaddingTop(), 789 mDialogView.getPaddingRight(), 790 mDialogView.getPaddingBottom()); 791 } 792 793 ((LinearLayout) mRingerDrawerContainer.findViewById(R.id.volume_drawer_options)) 794 .setOrientation(isLandscape() ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL); 795 796 mSelectedRingerContainer.setOnClickListener(view -> { 797 if (mIsRingerDrawerOpen) { 798 hideRingerDrawer(); 799 } else { 800 showRingerDrawer(); 801 } 802 }); 803 804 mRingerDrawerVibrate.setOnClickListener( 805 new RingerDrawerItemClickListener(RINGER_MODE_VIBRATE)); 806 mRingerDrawerMute.setOnClickListener( 807 new RingerDrawerItemClickListener(RINGER_MODE_SILENT)); 808 mRingerDrawerNormal.setOnClickListener( 809 new RingerDrawerItemClickListener(RINGER_MODE_NORMAL)); 810 811 final int unselectedColor = Utils.getColorAccentDefaultColor(mContext); 812 final int selectedColor = Utils.getColorAttrDefaultColor( 813 mContext, android.R.attr.colorBackgroundFloating); 814 815 // Add an update listener that animates the deselected icon to the unselected color, and the 816 // selected icon to the selected color. 817 mRingerDrawerIconColorAnimator.addUpdateListener( 818 anim -> { 819 final float currentValue = (float) anim.getAnimatedValue(); 820 final int curUnselectedColor = (int) ArgbEvaluator.getInstance().evaluate( 821 currentValue, selectedColor, unselectedColor); 822 final int curSelectedColor = (int) ArgbEvaluator.getInstance().evaluate( 823 currentValue, unselectedColor, selectedColor); 824 825 mRingerDrawerIconAnimatingDeselected.setColorFilter(curUnselectedColor); 826 mRingerDrawerIconAnimatingSelected.setColorFilter(curSelectedColor); 827 }); 828 mRingerDrawerIconColorAnimator.addListener(new AnimatorListenerAdapter() { 829 @Override 830 public void onAnimationEnd(Animator animation) { 831 mRingerDrawerIconAnimatingDeselected.clearColorFilter(); 832 mRingerDrawerIconAnimatingSelected.clearColorFilter(); 833 } 834 }); 835 mRingerDrawerIconColorAnimator.setDuration(DRAWER_ANIMATION_DURATION_SHORT); 836 837 mAnimateUpBackgroundToMatchDrawer.addUpdateListener(valueAnimator -> { 838 mRingerDrawerClosedAmount = (float) valueAnimator.getAnimatedValue(); 839 updateBackgroundForDrawerClosedAmount(); 840 }); 841 } 842 getDrawerIconViewForMode(int mode)843 private ImageView getDrawerIconViewForMode(int mode) { 844 if (mode == RINGER_MODE_VIBRATE) { 845 return mRingerDrawerVibrateIcon; 846 } else if (mode == RINGER_MODE_SILENT) { 847 return mRingerDrawerMuteIcon; 848 } else { 849 return mRingerDrawerNormalIcon; 850 } 851 } 852 853 /** 854 * Translation to apply form the origin (either top or left) to overlap the selection background 855 * with the given mode in the drawer. 856 */ getTranslationInDrawerForRingerMode(int mode)857 private float getTranslationInDrawerForRingerMode(int mode) { 858 return mode == RINGER_MODE_VIBRATE 859 ? -mRingerDrawerItemSize * 2 860 : mode == RINGER_MODE_SILENT 861 ? -mRingerDrawerItemSize 862 : 0; 863 } 864 865 /** Animates in the ringer drawer. */ showRingerDrawer()866 private void showRingerDrawer() { 867 if (mIsRingerDrawerOpen) { 868 return; 869 } 870 871 // Show all ringer icons except the currently selected one, since we're going to animate the 872 // ringer button to that position. 873 mRingerDrawerVibrateIcon.setVisibility( 874 mState.ringerModeInternal == RINGER_MODE_VIBRATE ? INVISIBLE : VISIBLE); 875 mRingerDrawerMuteIcon.setVisibility( 876 mState.ringerModeInternal == RINGER_MODE_SILENT ? INVISIBLE : VISIBLE); 877 mRingerDrawerNormalIcon.setVisibility( 878 mState.ringerModeInternal == RINGER_MODE_NORMAL ? INVISIBLE : VISIBLE); 879 880 // Hide the selection background - we use this to show a selection when one is 881 // tapped, so it should be invisible until that happens. However, position it below 882 // the currently selected ringer so that it's ready to animate. 883 mRingerDrawerNewSelectionBg.setAlpha(0f); 884 885 if (!isLandscape()) { 886 mRingerDrawerNewSelectionBg.setTranslationY( 887 getTranslationInDrawerForRingerMode(mState.ringerModeInternal)); 888 } else { 889 mRingerDrawerNewSelectionBg.setTranslationX( 890 getTranslationInDrawerForRingerMode(mState.ringerModeInternal)); 891 } 892 893 // Move the drawer so that the top/rightmost ringer choice overlaps with the selected ringer 894 // icon. 895 if (!isLandscape()) { 896 mRingerDrawerContainer.setTranslationY(mRingerDrawerItemSize * (mRingerCount - 1)); 897 } else { 898 mRingerDrawerContainer.setTranslationX(mRingerDrawerItemSize * (mRingerCount - 1)); 899 } 900 mRingerDrawerContainer.setAlpha(0f); 901 mRingerDrawerContainer.setVisibility(VISIBLE); 902 903 final int ringerDrawerAnimationDuration = mState.ringerModeInternal == RINGER_MODE_VIBRATE 904 ? DRAWER_ANIMATION_DURATION_SHORT 905 : DRAWER_ANIMATION_DURATION; 906 907 // Animate the drawer up and visible. 908 mRingerDrawerContainer.animate() 909 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) 910 // Vibrate is way farther up, so give the selected ringer icon a head start if 911 // vibrate is selected. 912 .setDuration(ringerDrawerAnimationDuration) 913 .setStartDelay(mState.ringerModeInternal == RINGER_MODE_VIBRATE 914 ? DRAWER_ANIMATION_DURATION - DRAWER_ANIMATION_DURATION_SHORT 915 : 0) 916 .alpha(1f) 917 .translationX(0f) 918 .translationY(0f) 919 .start(); 920 921 // Animate the selected ringer view up to that ringer's position in the drawer. 922 mSelectedRingerContainer.animate() 923 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) 924 .setDuration(DRAWER_ANIMATION_DURATION) 925 .withEndAction(() -> 926 getDrawerIconViewForMode(mState.ringerModeInternal).setVisibility(VISIBLE)); 927 928 mAnimateUpBackgroundToMatchDrawer.setDuration(ringerDrawerAnimationDuration); 929 mAnimateUpBackgroundToMatchDrawer.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); 930 mAnimateUpBackgroundToMatchDrawer.start(); 931 932 if (!isLandscape()) { 933 mSelectedRingerContainer.animate() 934 .translationY(getTranslationInDrawerForRingerMode(mState.ringerModeInternal)) 935 .start(); 936 } else { 937 mSelectedRingerContainer.animate() 938 .translationX(getTranslationInDrawerForRingerMode(mState.ringerModeInternal)) 939 .start(); 940 } 941 942 // When the ringer drawer is open, tapping the currently selected ringer will set the ringer 943 // to the current ringer mode. Change the content description to that, instead of the 'tap 944 // to change ringer mode' default. 945 mSelectedRingerContainer.setContentDescription( 946 mContext.getString(getStringDescriptionResourceForRingerMode( 947 mState.ringerModeInternal))); 948 949 mIsRingerDrawerOpen = true; 950 } 951 952 /** Animates away the ringer drawer. */ hideRingerDrawer()953 private void hideRingerDrawer() { 954 955 // If the ringer drawer isn't present, don't try to hide it. 956 if (mRingerDrawerContainer == null) { 957 return; 958 } 959 960 if (!mIsRingerDrawerOpen) { 961 return; 962 } 963 964 // Hide the drawer icon for the selected ringer - it's visible in the ringer button and we 965 // don't want to be able to see it while it animates away. 966 getDrawerIconViewForMode(mState.ringerModeInternal).setVisibility(INVISIBLE); 967 968 mRingerDrawerContainer.animate() 969 .alpha(0f) 970 .setDuration(DRAWER_ANIMATION_DURATION) 971 .setStartDelay(0) 972 .withEndAction(() -> mRingerDrawerContainer.setVisibility(INVISIBLE)); 973 974 if (!isLandscape()) { 975 mRingerDrawerContainer.animate() 976 .translationY(mRingerDrawerItemSize * 2) 977 .start(); 978 } else { 979 mRingerDrawerContainer.animate() 980 .translationX(mRingerDrawerItemSize * 2) 981 .start(); 982 } 983 984 mAnimateUpBackgroundToMatchDrawer.setDuration(DRAWER_ANIMATION_DURATION); 985 mAnimateUpBackgroundToMatchDrawer.setInterpolator(Interpolators.FAST_OUT_SLOW_IN_REVERSE); 986 mAnimateUpBackgroundToMatchDrawer.reverse(); 987 988 mSelectedRingerContainer.animate() 989 .translationX(0f) 990 .translationY(0f) 991 .start(); 992 993 // When the drawer is closed, tapping the selected ringer drawer will open it, allowing the 994 // user to change the ringer. 995 mSelectedRingerContainer.setContentDescription( 996 mContext.getString(R.string.volume_ringer_change)); 997 998 mIsRingerDrawerOpen = false; 999 } 1000 initSettingsH()1001 public void initSettingsH() { 1002 if (mSettingsView != null) { 1003 mSettingsView.setVisibility( 1004 mDeviceProvisionedController.isCurrentUserSetup() && 1005 mActivityManager.getLockTaskModeState() == LOCK_TASK_MODE_NONE ? 1006 VISIBLE : GONE); 1007 } 1008 if (mSettingsIcon != null) { 1009 mSettingsIcon.setOnClickListener(v -> { 1010 Events.writeEvent(Events.EVENT_SETTINGS_CLICK); 1011 Intent intent = new Intent(Settings.Panel.ACTION_VOLUME); 1012 dismissH(DISMISS_REASON_SETTINGS_CLICKED); 1013 Dependency.get(MediaOutputDialogFactory.class).dismiss(); 1014 Dependency.get(ActivityStarter.class).startActivity(intent, 1015 true /* dismissShade */); 1016 }); 1017 } 1018 } 1019 initRingerH()1020 public void initRingerH() { 1021 if (mRingerIcon != null) { 1022 mRingerIcon.setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE); 1023 mRingerIcon.setOnClickListener(v -> { 1024 Prefs.putBoolean(mContext, Prefs.Key.TOUCHED_RINGER_TOGGLE, true); 1025 final StreamState ss = mState.states.get(AudioManager.STREAM_RING); 1026 if (ss == null) { 1027 return; 1028 } 1029 // normal -> vibrate -> silent -> normal (skip vibrate if device doesn't have 1030 // a vibrator. 1031 int newRingerMode; 1032 final boolean hasVibrator = mController.hasVibrator(); 1033 if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) { 1034 if (hasVibrator) { 1035 newRingerMode = AudioManager.RINGER_MODE_VIBRATE; 1036 } else { 1037 newRingerMode = AudioManager.RINGER_MODE_SILENT; 1038 } 1039 } else if (mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) { 1040 newRingerMode = AudioManager.RINGER_MODE_SILENT; 1041 } else { 1042 newRingerMode = AudioManager.RINGER_MODE_NORMAL; 1043 if (ss.level == 0) { 1044 mController.setStreamVolume(AudioManager.STREAM_RING, 1); 1045 } 1046 } 1047 1048 setRingerMode(newRingerMode); 1049 }); 1050 } 1051 updateRingerH(); 1052 } 1053 initODICaptionsH()1054 private void initODICaptionsH() { 1055 if (mODICaptionsIcon != null) { 1056 mODICaptionsIcon.setOnConfirmedTapListener(() -> { 1057 onCaptionIconClicked(); 1058 Events.writeEvent(Events.EVENT_ODI_CAPTIONS_CLICK); 1059 }, mHandler); 1060 } 1061 1062 mController.getCaptionsComponentState(false); 1063 } 1064 checkODICaptionsTooltip(boolean fromDismiss)1065 private void checkODICaptionsTooltip(boolean fromDismiss) { 1066 if (!mHasSeenODICaptionsTooltip && !fromDismiss && mODICaptionsTooltipViewStub != null) { 1067 mController.getCaptionsComponentState(true); 1068 } else { 1069 if (mHasSeenODICaptionsTooltip && fromDismiss && mODICaptionsTooltipView != null) { 1070 hideCaptionsTooltip(); 1071 } 1072 } 1073 } 1074 showCaptionsTooltip()1075 protected void showCaptionsTooltip() { 1076 if (!mHasSeenODICaptionsTooltip && mODICaptionsTooltipViewStub != null) { 1077 mODICaptionsTooltipView = mODICaptionsTooltipViewStub.inflate(); 1078 mODICaptionsTooltipView.findViewById(R.id.dismiss).setOnClickListener(v -> { 1079 hideCaptionsTooltip(); 1080 Events.writeEvent(Events.EVENT_ODI_CAPTIONS_TOOLTIP_CLICK); 1081 }); 1082 mODICaptionsTooltipViewStub = null; 1083 rescheduleTimeoutH(); 1084 } 1085 1086 if (mODICaptionsTooltipView != null) { 1087 mODICaptionsTooltipView.setAlpha(0.0f); 1088 1089 // We need to wait for layout and then center the caption view. Since the height of the 1090 // dialog is now dynamic (with the variable ringer drawer height changing the height of 1091 // the dialog), we need to do this here in code vs. in XML. 1092 mHandler.post(() -> { 1093 final int[] odiTooltipLocation = mODICaptionsTooltipView.getLocationOnScreen(); 1094 final int[] odiButtonLocation = mODICaptionsIcon.getLocationOnScreen(); 1095 1096 final float heightDiffForCentering = 1097 (mODICaptionsTooltipView.getHeight() - mODICaptionsIcon.getHeight()) / 2f; 1098 1099 mODICaptionsTooltipView.setTranslationY( 1100 odiButtonLocation[1] - odiTooltipLocation[1] - heightDiffForCentering); 1101 1102 mODICaptionsTooltipView.animate() 1103 .alpha(1.0f) 1104 .setStartDelay(mDialogShowAnimationDurationMs) 1105 .withEndAction(() -> { 1106 if (D.BUG) { 1107 Log.d(TAG, "tool:checkODICaptionsTooltip() putBoolean true"); 1108 } 1109 Prefs.putBoolean(mContext, 1110 Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, true); 1111 mHasSeenODICaptionsTooltip = true; 1112 if (mODICaptionsIcon != null) { 1113 mODICaptionsIcon 1114 .postOnAnimation(getSinglePressFor(mODICaptionsIcon)); 1115 } 1116 }) 1117 .start(); 1118 }); 1119 } 1120 } 1121 hideCaptionsTooltip()1122 private void hideCaptionsTooltip() { 1123 if (mODICaptionsTooltipView != null && mODICaptionsTooltipView.getVisibility() == VISIBLE) { 1124 mODICaptionsTooltipView.animate().cancel(); 1125 mODICaptionsTooltipView.setAlpha(1.f); 1126 mODICaptionsTooltipView.animate() 1127 .alpha(0.f) 1128 .setStartDelay(0) 1129 .setDuration(mDialogHideAnimationDurationMs) 1130 .withEndAction(() -> { 1131 // It might have been nulled out by tryToRemoveCaptionsTooltip. 1132 if (mODICaptionsTooltipView != null) { 1133 mODICaptionsTooltipView.setVisibility(INVISIBLE); 1134 } 1135 }) 1136 .start(); 1137 } 1138 } 1139 tryToRemoveCaptionsTooltip()1140 protected void tryToRemoveCaptionsTooltip() { 1141 if (mHasSeenODICaptionsTooltip && mODICaptionsTooltipView != null) { 1142 ViewGroup container = mDialog.findViewById(R.id.volume_dialog_container); 1143 container.removeView(mODICaptionsTooltipView); 1144 mODICaptionsTooltipView = null; 1145 } 1146 } 1147 updateODICaptionsH(boolean isServiceComponentEnabled, boolean fromTooltip)1148 private void updateODICaptionsH(boolean isServiceComponentEnabled, boolean fromTooltip) { 1149 if (mODICaptionsView != null) { 1150 mODICaptionsView.setVisibility(isServiceComponentEnabled ? VISIBLE : GONE); 1151 } 1152 1153 if (!isServiceComponentEnabled) return; 1154 1155 updateCaptionsIcon(); 1156 if (fromTooltip) showCaptionsTooltip(); 1157 } 1158 updateCaptionsIcon()1159 private void updateCaptionsIcon() { 1160 boolean captionsEnabled = mController.areCaptionsEnabled(); 1161 if (mODICaptionsIcon.getCaptionsEnabled() != captionsEnabled) { 1162 mHandler.post(mODICaptionsIcon.setCaptionsEnabled(captionsEnabled)); 1163 } 1164 1165 boolean isOptedOut = mController.isCaptionStreamOptedOut(); 1166 if (mODICaptionsIcon.getOptedOut() != isOptedOut) { 1167 mHandler.post(() -> mODICaptionsIcon.setOptedOut(isOptedOut)); 1168 } 1169 } 1170 onCaptionIconClicked()1171 private void onCaptionIconClicked() { 1172 boolean isEnabled = mController.areCaptionsEnabled(); 1173 mController.setCaptionsEnabled(!isEnabled); 1174 updateCaptionsIcon(); 1175 } 1176 incrementManualToggleCount()1177 private void incrementManualToggleCount() { 1178 ContentResolver cr = mContext.getContentResolver(); 1179 int ringerCount = Settings.Secure.getInt(cr, Settings.Secure.MANUAL_RINGER_TOGGLE_COUNT, 0); 1180 Settings.Secure.putInt(cr, Settings.Secure.MANUAL_RINGER_TOGGLE_COUNT, ringerCount + 1); 1181 } 1182 provideTouchFeedbackH(int newRingerMode)1183 private void provideTouchFeedbackH(int newRingerMode) { 1184 VibrationEffect effect = null; 1185 switch (newRingerMode) { 1186 case RINGER_MODE_NORMAL: 1187 mController.scheduleTouchFeedback(); 1188 break; 1189 case RINGER_MODE_SILENT: 1190 effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); 1191 break; 1192 case RINGER_MODE_VIBRATE: 1193 default: 1194 effect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK); 1195 } 1196 if (effect != null) { 1197 mController.vibrate(effect); 1198 } 1199 } 1200 maybeShowToastH(int newRingerMode)1201 private void maybeShowToastH(int newRingerMode) { 1202 int seenToastCount = Prefs.getInt(mContext, Prefs.Key.SEEN_RINGER_GUIDANCE_COUNT, 0); 1203 1204 if (seenToastCount > VolumePrefs.SHOW_RINGER_TOAST_COUNT) { 1205 return; 1206 } 1207 CharSequence toastText = null; 1208 switch (newRingerMode) { 1209 case RINGER_MODE_NORMAL: 1210 final StreamState ss = mState.states.get(AudioManager.STREAM_RING); 1211 if (ss != null) { 1212 toastText = mContext.getString( 1213 R.string.volume_dialog_ringer_guidance_ring, 1214 Utils.formatPercentage(ss.level, ss.levelMax)); 1215 } 1216 break; 1217 case RINGER_MODE_SILENT: 1218 toastText = mContext.getString( 1219 com.android.internal.R.string.volume_dialog_ringer_guidance_silent); 1220 break; 1221 case RINGER_MODE_VIBRATE: 1222 default: 1223 toastText = mContext.getString( 1224 com.android.internal.R.string.volume_dialog_ringer_guidance_vibrate); 1225 } 1226 1227 Toast.makeText(mContext, toastText, Toast.LENGTH_SHORT).show(); 1228 seenToastCount++; 1229 Prefs.putInt(mContext, Prefs.Key.SEEN_RINGER_GUIDANCE_COUNT, seenToastCount); 1230 } 1231 show(int reason)1232 public void show(int reason) { 1233 mHandler.obtainMessage(H.SHOW, reason, 0).sendToTarget(); 1234 } 1235 dismiss(int reason)1236 public void dismiss(int reason) { 1237 mHandler.obtainMessage(H.DISMISS, reason, 0).sendToTarget(); 1238 } 1239 showH(int reason)1240 private void showH(int reason) { 1241 if (D.BUG) Log.d(TAG, "showH r=" + Events.SHOW_REASONS[reason]); 1242 mHandler.removeMessages(H.SHOW); 1243 mHandler.removeMessages(H.DISMISS); 1244 rescheduleTimeoutH(); 1245 1246 if (mConfigChanged) { 1247 initDialog(); // resets mShowing to false 1248 mConfigurableTexts.update(); 1249 mConfigChanged = false; 1250 } 1251 1252 initSettingsH(); 1253 mShowing = true; 1254 mIsAnimatingDismiss = false; 1255 mDialog.show(); 1256 Events.writeEvent(Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked()); 1257 mController.notifyVisible(true); 1258 mController.getCaptionsComponentState(false); 1259 checkODICaptionsTooltip(false); 1260 updateBackgroundForDrawerClosedAmount(); 1261 } 1262 rescheduleTimeoutH()1263 protected void rescheduleTimeoutH() { 1264 mHandler.removeMessages(H.DISMISS); 1265 final int timeout = computeTimeoutH(); 1266 mHandler.sendMessageDelayed(mHandler 1267 .obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT, 0), timeout); 1268 if (D.BUG) Log.d(TAG, "rescheduleTimeout " + timeout + " " + Debug.getCaller()); 1269 mController.userActivity(); 1270 } 1271 computeTimeoutH()1272 private int computeTimeoutH() { 1273 if (mHovering) { 1274 return mAccessibilityMgr.getRecommendedTimeoutMillis(DIALOG_HOVERING_TIMEOUT_MILLIS, 1275 AccessibilityManager.FLAG_CONTENT_CONTROLS); 1276 } 1277 if (mSafetyWarning != null) { 1278 return mAccessibilityMgr.getRecommendedTimeoutMillis( 1279 DIALOG_SAFETYWARNING_TIMEOUT_MILLIS, 1280 AccessibilityManager.FLAG_CONTENT_TEXT 1281 | AccessibilityManager.FLAG_CONTENT_CONTROLS); 1282 } 1283 if (!mHasSeenODICaptionsTooltip && mODICaptionsTooltipView != null) { 1284 return mAccessibilityMgr.getRecommendedTimeoutMillis( 1285 DIALOG_ODI_CAPTIONS_TOOLTIP_TIMEOUT_MILLIS, 1286 AccessibilityManager.FLAG_CONTENT_TEXT 1287 | AccessibilityManager.FLAG_CONTENT_CONTROLS); 1288 } 1289 return mAccessibilityMgr.getRecommendedTimeoutMillis(DIALOG_TIMEOUT_MILLIS, 1290 AccessibilityManager.FLAG_CONTENT_CONTROLS); 1291 } 1292 dismissH(int reason)1293 protected void dismissH(int reason) { 1294 if (D.BUG) { 1295 Log.d(TAG, "mDialog.dismiss() reason: " + Events.DISMISS_REASONS[reason] 1296 + " from: " + Debug.getCaller()); 1297 } 1298 mHandler.removeMessages(H.DISMISS); 1299 mHandler.removeMessages(H.SHOW); 1300 if (mIsAnimatingDismiss) { 1301 return; 1302 } 1303 mIsAnimatingDismiss = true; 1304 mDialogView.animate().cancel(); 1305 if (mShowing) { 1306 mShowing = false; 1307 // Only logs when the volume dialog visibility is changed. 1308 Events.writeEvent(Events.EVENT_DISMISS_DIALOG, reason); 1309 } 1310 mDialogView.setTranslationX(0); 1311 mDialogView.setAlpha(1); 1312 ViewPropertyAnimator animator = mDialogView.animate() 1313 .alpha(0) 1314 .setDuration(mDialogHideAnimationDurationMs) 1315 .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator()) 1316 .withEndAction(() -> mHandler.postDelayed(() -> { 1317 mDialog.dismiss(); 1318 tryToRemoveCaptionsTooltip(); 1319 mIsAnimatingDismiss = false; 1320 1321 hideRingerDrawer(); 1322 }, 50)); 1323 if (!isLandscape()) animator.translationX(mDialogView.getWidth() / 2.0f); 1324 animator.start(); 1325 checkODICaptionsTooltip(true); 1326 mController.notifyVisible(false); 1327 synchronized (mSafetyWarningLock) { 1328 if (mSafetyWarning != null) { 1329 if (D.BUG) Log.d(TAG, "SafetyWarning dismissed"); 1330 mSafetyWarning.dismiss(); 1331 } 1332 } 1333 } 1334 showActiveStreamOnly()1335 private boolean showActiveStreamOnly() { 1336 return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK) 1337 || mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION); 1338 } 1339 shouldBeVisibleH(VolumeRow row, VolumeRow activeRow)1340 private boolean shouldBeVisibleH(VolumeRow row, VolumeRow activeRow) { 1341 boolean isActive = row.stream == activeRow.stream; 1342 1343 if (isActive) { 1344 return true; 1345 } 1346 1347 if (!mShowActiveStreamOnly) { 1348 if (row.stream == AudioSystem.STREAM_ACCESSIBILITY) { 1349 return mShowA11yStream; 1350 } 1351 1352 // if the active row is accessibility, then continue to display previous 1353 // active row since accessibility is displayed under it 1354 if (activeRow.stream == AudioSystem.STREAM_ACCESSIBILITY && 1355 row.stream == mPrevActiveStream) { 1356 return true; 1357 } 1358 1359 if (row.defaultStream) { 1360 return activeRow.stream == STREAM_RING 1361 || activeRow.stream == STREAM_ALARM 1362 || activeRow.stream == STREAM_VOICE_CALL 1363 || activeRow.stream == STREAM_ACCESSIBILITY 1364 || mDynamic.get(activeRow.stream); 1365 } 1366 } 1367 1368 return false; 1369 } 1370 updateRowsH(final VolumeRow activeRow)1371 private void updateRowsH(final VolumeRow activeRow) { 1372 if (D.BUG) Log.d(TAG, "updateRowsH"); 1373 if (!mShowing) { 1374 trimObsoleteH(); 1375 } 1376 1377 // Index of the last row that is actually visible. 1378 int rightmostVisibleRowIndex = !isRtl() ? -1 : Short.MAX_VALUE; 1379 1380 // apply changes to all rows 1381 for (final VolumeRow row : mRows) { 1382 final boolean isActive = row == activeRow; 1383 final boolean shouldBeVisible = shouldBeVisibleH(row, activeRow); 1384 Util.setVisOrGone(row.view, shouldBeVisible); 1385 1386 if (shouldBeVisible && mRingerAndDrawerContainerBackground != null) { 1387 // For RTL, the rightmost row has the lowest index since child views are laid out 1388 // from right to left. 1389 rightmostVisibleRowIndex = 1390 !isRtl() 1391 ? Math.max(rightmostVisibleRowIndex, 1392 mDialogRowsView.indexOfChild(row.view)) 1393 : Math.min(rightmostVisibleRowIndex, 1394 mDialogRowsView.indexOfChild(row.view)); 1395 1396 // Add spacing between each of the visible rows - we'll remove the spacing from the 1397 // last row after the loop. 1398 final ViewGroup.LayoutParams layoutParams = row.view.getLayoutParams(); 1399 if (layoutParams instanceof LinearLayout.LayoutParams) { 1400 final LinearLayout.LayoutParams linearLayoutParams = 1401 ((LinearLayout.LayoutParams) layoutParams); 1402 if (!isRtl()) { 1403 linearLayoutParams.setMarginEnd(mRingerRowsPadding); 1404 } else { 1405 linearLayoutParams.setMarginStart(mRingerRowsPadding); 1406 } 1407 } 1408 1409 // Set the background on each of the rows. We'll remove this from the last row after 1410 // the loop, since the last row's background is drawn by the main volume container. 1411 row.view.setBackgroundDrawable( 1412 mContext.getDrawable(R.drawable.volume_row_rounded_background)); 1413 } 1414 1415 if (row.view.isShown()) { 1416 updateVolumeRowTintH(row, isActive); 1417 } 1418 } 1419 1420 if (rightmostVisibleRowIndex > -1 && rightmostVisibleRowIndex < Short.MAX_VALUE) { 1421 final View lastVisibleChild = mDialogRowsView.getChildAt(rightmostVisibleRowIndex); 1422 final ViewGroup.LayoutParams layoutParams = lastVisibleChild.getLayoutParams(); 1423 // Remove the spacing on the last row, and remove its background since the container is 1424 // drawing a background for this row. 1425 if (layoutParams instanceof LinearLayout.LayoutParams) { 1426 final LinearLayout.LayoutParams linearLayoutParams = 1427 ((LinearLayout.LayoutParams) layoutParams); 1428 linearLayoutParams.setMarginStart(0); 1429 linearLayoutParams.setMarginEnd(0); 1430 lastVisibleChild.setBackgroundColor(Color.TRANSPARENT); 1431 } 1432 } 1433 1434 updateBackgroundForDrawerClosedAmount(); 1435 } 1436 updateRingerH()1437 protected void updateRingerH() { 1438 if (mRinger != null && mState != null) { 1439 final StreamState ss = mState.states.get(AudioManager.STREAM_RING); 1440 if (ss == null) { 1441 return; 1442 } 1443 1444 boolean isZenMuted = mState.zenMode == Global.ZEN_MODE_ALARMS 1445 || mState.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS 1446 || (mState.zenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS 1447 && mState.disallowRinger); 1448 enableRingerViewsH(!isZenMuted); 1449 switch (mState.ringerModeInternal) { 1450 case AudioManager.RINGER_MODE_VIBRATE: 1451 mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_vibrate); 1452 mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_vibrate); 1453 addAccessibilityDescription(mRingerIcon, RINGER_MODE_VIBRATE, 1454 mContext.getString(R.string.volume_ringer_hint_mute)); 1455 mRingerIcon.setTag(Events.ICON_STATE_VIBRATE); 1456 break; 1457 case AudioManager.RINGER_MODE_SILENT: 1458 mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute); 1459 mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute); 1460 mRingerIcon.setTag(Events.ICON_STATE_MUTE); 1461 addAccessibilityDescription(mRingerIcon, RINGER_MODE_SILENT, 1462 mContext.getString(R.string.volume_ringer_hint_unmute)); 1463 break; 1464 case AudioManager.RINGER_MODE_NORMAL: 1465 default: 1466 boolean muted = (mAutomute && ss.level == 0) || ss.muted; 1467 if (!isZenMuted && muted) { 1468 mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute); 1469 mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute); 1470 addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL, 1471 mContext.getString(R.string.volume_ringer_hint_unmute)); 1472 mRingerIcon.setTag(Events.ICON_STATE_MUTE); 1473 } else { 1474 mRingerIcon.setImageResource(R.drawable.ic_volume_ringer); 1475 mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer); 1476 if (mController.hasVibrator()) { 1477 addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL, 1478 mContext.getString(R.string.volume_ringer_hint_vibrate)); 1479 } else { 1480 addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL, 1481 mContext.getString(R.string.volume_ringer_hint_mute)); 1482 } 1483 mRingerIcon.setTag(Events.ICON_STATE_UNMUTE); 1484 } 1485 break; 1486 } 1487 } 1488 } 1489 addAccessibilityDescription(View view, int currState, String hintLabel)1490 private void addAccessibilityDescription(View view, int currState, String hintLabel) { 1491 view.setContentDescription( 1492 mContext.getString(getStringDescriptionResourceForRingerMode(currState))); 1493 view.setAccessibilityDelegate(new AccessibilityDelegate() { 1494 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { 1495 super.onInitializeAccessibilityNodeInfo(host, info); 1496 info.addAction(new AccessibilityNodeInfo.AccessibilityAction( 1497 AccessibilityNodeInfo.ACTION_CLICK, hintLabel)); 1498 } 1499 }); 1500 } 1501 getStringDescriptionResourceForRingerMode(int mode)1502 private int getStringDescriptionResourceForRingerMode(int mode) { 1503 switch (mode) { 1504 case RINGER_MODE_SILENT: 1505 return R.string.volume_ringer_status_silent; 1506 case RINGER_MODE_VIBRATE: 1507 return R.string.volume_ringer_status_vibrate; 1508 case RINGER_MODE_NORMAL: 1509 default: 1510 return R.string.volume_ringer_status_normal; 1511 } 1512 } 1513 1514 /** 1515 * Toggles enable state of views in a VolumeRow (not including seekbar or icon) 1516 * Hides/shows zen icon 1517 * @param enable whether to enable volume row views and hide dnd icon 1518 */ enableVolumeRowViewsH(VolumeRow row, boolean enable)1519 private void enableVolumeRowViewsH(VolumeRow row, boolean enable) { 1520 boolean showDndIcon = !enable; 1521 row.dndIcon.setVisibility(showDndIcon ? VISIBLE : GONE); 1522 } 1523 1524 /** 1525 * Toggles enable state of footer/ringer views 1526 * Hides/shows zen icon 1527 * @param enable whether to enable ringer views and hide dnd icon 1528 */ enableRingerViewsH(boolean enable)1529 private void enableRingerViewsH(boolean enable) { 1530 if (mRingerIcon != null) { 1531 mRingerIcon.setEnabled(enable); 1532 } 1533 if (mZenIcon != null) { 1534 mZenIcon.setVisibility(enable ? GONE : VISIBLE); 1535 } 1536 } 1537 trimObsoleteH()1538 private void trimObsoleteH() { 1539 if (D.BUG) Log.d(TAG, "trimObsoleteH"); 1540 for (int i = mRows.size() - 1; i >= 0; i--) { 1541 final VolumeRow row = mRows.get(i); 1542 if (row.ss == null || !row.ss.dynamic) continue; 1543 if (!mDynamic.get(row.stream)) { 1544 mRows.remove(i); 1545 mDialogRowsView.removeView(row.view); 1546 mConfigurableTexts.remove(row.header); 1547 } 1548 } 1549 } 1550 onStateChangedH(State state)1551 protected void onStateChangedH(State state) { 1552 if (D.BUG) Log.d(TAG, "onStateChangedH() state: " + state.toString()); 1553 if (mState != null && state != null 1554 && mState.ringerModeInternal != -1 1555 && mState.ringerModeInternal != state.ringerModeInternal 1556 && state.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) { 1557 mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK)); 1558 } 1559 1560 mState = state; 1561 mDynamic.clear(); 1562 // add any new dynamic rows 1563 for (int i = 0; i < state.states.size(); i++) { 1564 final int stream = state.states.keyAt(i); 1565 final StreamState ss = state.states.valueAt(i); 1566 if (!ss.dynamic) continue; 1567 mDynamic.put(stream, true); 1568 if (findRow(stream) == null) { 1569 addRow(stream, R.drawable.ic_volume_remote, R.drawable.ic_volume_remote_mute, true, 1570 false, true); 1571 } 1572 } 1573 1574 if (mActiveStream != state.activeStream) { 1575 mPrevActiveStream = mActiveStream; 1576 mActiveStream = state.activeStream; 1577 VolumeRow activeRow = getActiveRow(); 1578 updateRowsH(activeRow); 1579 if (mShowing) rescheduleTimeoutH(); 1580 } 1581 for (VolumeRow row : mRows) { 1582 updateVolumeRowH(row); 1583 } 1584 updateRingerH(); 1585 mWindow.setTitle(composeWindowTitle()); 1586 } 1587 composeWindowTitle()1588 CharSequence composeWindowTitle() { 1589 return mContext.getString(R.string.volume_dialog_title, getStreamLabelH(getActiveRow().ss)); 1590 } 1591 updateVolumeRowH(VolumeRow row)1592 private void updateVolumeRowH(VolumeRow row) { 1593 if (D.BUG) Log.i(TAG, "updateVolumeRowH s=" + row.stream); 1594 if (mState == null) return; 1595 final StreamState ss = mState.states.get(row.stream); 1596 if (ss == null) return; 1597 row.ss = ss; 1598 if (ss.level > 0) { 1599 row.lastAudibleLevel = ss.level; 1600 } 1601 if (ss.level == row.requestedLevel) { 1602 row.requestedLevel = -1; 1603 } 1604 final boolean isA11yStream = row.stream == STREAM_ACCESSIBILITY; 1605 final boolean isRingStream = row.stream == AudioManager.STREAM_RING; 1606 final boolean isSystemStream = row.stream == AudioManager.STREAM_SYSTEM; 1607 final boolean isAlarmStream = row.stream == STREAM_ALARM; 1608 final boolean isMusicStream = row.stream == AudioManager.STREAM_MUSIC; 1609 final boolean isRingVibrate = isRingStream 1610 && mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE; 1611 final boolean isRingSilent = isRingStream 1612 && mState.ringerModeInternal == AudioManager.RINGER_MODE_SILENT; 1613 final boolean isZenPriorityOnly = mState.zenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; 1614 final boolean isZenAlarms = mState.zenMode == Global.ZEN_MODE_ALARMS; 1615 final boolean isZenNone = mState.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS; 1616 final boolean zenMuted = isZenAlarms ? (isRingStream || isSystemStream) 1617 : isZenNone ? (isRingStream || isSystemStream || isAlarmStream || isMusicStream) 1618 : isZenPriorityOnly ? ((isAlarmStream && mState.disallowAlarms) || 1619 (isMusicStream && mState.disallowMedia) || 1620 (isRingStream && mState.disallowRinger) || 1621 (isSystemStream && mState.disallowSystem)) 1622 : false; 1623 1624 // update slider max 1625 final int max = ss.levelMax * 100; 1626 if (max != row.slider.getMax()) { 1627 row.slider.setMax(max); 1628 } 1629 // update slider min 1630 final int min = ss.levelMin * 100; 1631 if (min != row.slider.getMin()) { 1632 row.slider.setMin(min); 1633 } 1634 1635 // update header text 1636 Util.setText(row.header, getStreamLabelH(ss)); 1637 row.slider.setContentDescription(row.header.getText()); 1638 mConfigurableTexts.add(row.header, ss.name); 1639 1640 // update icon 1641 final boolean iconEnabled = (mAutomute || ss.muteSupported) && !zenMuted; 1642 final int iconRes; 1643 if (isRingVibrate) { 1644 iconRes = R.drawable.ic_volume_ringer_vibrate; 1645 } else if (isRingSilent || zenMuted) { 1646 iconRes = row.iconMuteRes; 1647 } else if (ss.routedToBluetooth) { 1648 iconRes = isStreamMuted(ss) ? R.drawable.ic_volume_media_bt_mute 1649 : R.drawable.ic_volume_media_bt; 1650 } else if (isStreamMuted(ss)) { 1651 iconRes = ss.muted ? R.drawable.ic_volume_media_off : row.iconMuteRes; 1652 } else { 1653 iconRes = mShowLowMediaVolumeIcon && ss.level * 2 < (ss.levelMax + ss.levelMin) 1654 ? R.drawable.ic_volume_media_low : row.iconRes; 1655 } 1656 1657 row.setIcon(iconRes, mContext.getTheme()); 1658 row.iconState = 1659 iconRes == R.drawable.ic_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE 1660 : (iconRes == R.drawable.ic_volume_media_bt_mute || iconRes == row.iconMuteRes) 1661 ? Events.ICON_STATE_MUTE 1662 : (iconRes == R.drawable.ic_volume_media_bt || iconRes == row.iconRes 1663 || iconRes == R.drawable.ic_volume_media_low) 1664 ? Events.ICON_STATE_UNMUTE 1665 : Events.ICON_STATE_UNKNOWN; 1666 1667 if (row.icon != null) { 1668 if (iconEnabled) { 1669 if (isRingStream) { 1670 if (isRingVibrate) { 1671 row.icon.setContentDescription(mContext.getString( 1672 R.string.volume_stream_content_description_unmute, 1673 getStreamLabelH(ss))); 1674 } else { 1675 if (mController.hasVibrator()) { 1676 row.icon.setContentDescription(mContext.getString( 1677 mShowA11yStream 1678 ? R.string.volume_stream_content_description_vibrate_a11y 1679 : R.string.volume_stream_content_description_vibrate, 1680 getStreamLabelH(ss))); 1681 } else { 1682 row.icon.setContentDescription(mContext.getString( 1683 mShowA11yStream 1684 ? R.string.volume_stream_content_description_mute_a11y 1685 : R.string.volume_stream_content_description_mute, 1686 getStreamLabelH(ss))); 1687 } 1688 } 1689 } else if (isA11yStream) { 1690 row.icon.setContentDescription(getStreamLabelH(ss)); 1691 } else { 1692 if (ss.muted || mAutomute && ss.level == 0) { 1693 row.icon.setContentDescription(mContext.getString( 1694 R.string.volume_stream_content_description_unmute, 1695 getStreamLabelH(ss))); 1696 } else { 1697 row.icon.setContentDescription(mContext.getString( 1698 mShowA11yStream 1699 ? R.string.volume_stream_content_description_mute_a11y 1700 : R.string.volume_stream_content_description_mute, 1701 getStreamLabelH(ss))); 1702 } 1703 } 1704 } else { 1705 row.icon.setContentDescription(getStreamLabelH(ss)); 1706 } 1707 } 1708 1709 // ensure tracking is disabled if zenMuted 1710 if (zenMuted) { 1711 row.tracking = false; 1712 } 1713 enableVolumeRowViewsH(row, !zenMuted); 1714 1715 // update slider 1716 final boolean enableSlider = !zenMuted; 1717 final int vlevel = row.ss.muted && (!isRingStream && !zenMuted) ? 0 1718 : row.ss.level; 1719 updateVolumeRowSliderH(row, enableSlider, vlevel); 1720 if (row.number != null) row.number.setText(Integer.toString(vlevel)); 1721 } 1722 1723 private boolean isStreamMuted(final StreamState streamState) { 1724 return (mAutomute && streamState.level == 0) || streamState.muted; 1725 } 1726 1727 private void updateVolumeRowTintH(VolumeRow row, boolean isActive) { 1728 if (isActive) { 1729 row.slider.requestFocus(); 1730 } 1731 boolean useActiveColoring = isActive && row.slider.isEnabled(); 1732 if (!useActiveColoring && !mChangeVolumeRowTintWhenInactive) { 1733 return; 1734 } 1735 final ColorStateList colorTint = useActiveColoring 1736 ? Utils.getColorAccent(mContext) 1737 : Utils.getColorAttr(mContext, com.android.internal.R.attr.colorAccentSecondary); 1738 final int alpha = useActiveColoring 1739 ? Color.alpha(colorTint.getDefaultColor()) 1740 : getAlphaAttr(android.R.attr.secondaryContentAlpha); 1741 1742 final ColorStateList bgTint = Utils.getColorAttr( 1743 mContext, android.R.attr.colorBackgroundFloating); 1744 1745 final ColorStateList inverseTextTint = Utils.getColorAttr( 1746 mContext, com.android.internal.R.attr.textColorOnAccent); 1747 1748 row.sliderProgressSolid.setTintList(colorTint); 1749 if (row.sliderBgIcon != null) { 1750 row.sliderBgIcon.setTintList(colorTint); 1751 } 1752 1753 if (row.sliderBgSolid != null) { 1754 row.sliderBgSolid.setTintList(bgTint); 1755 } 1756 1757 if (row.sliderProgressIcon != null) { 1758 row.sliderProgressIcon.setTintList(bgTint); 1759 } 1760 1761 if (row.icon != null) { 1762 row.icon.setImageTintList(inverseTextTint); 1763 row.icon.setImageAlpha(alpha); 1764 } 1765 1766 if (row.number != null) { 1767 row.number.setTextColor(colorTint); 1768 row.number.setAlpha(alpha); 1769 } 1770 } 1771 1772 private void updateVolumeRowSliderH(VolumeRow row, boolean enable, int vlevel) { 1773 row.slider.setEnabled(enable); 1774 updateVolumeRowTintH(row, row.stream == mActiveStream); 1775 if (row.tracking) { 1776 return; // don't update if user is sliding 1777 } 1778 final int progress = row.slider.getProgress(); 1779 final int level = getImpliedLevel(row.slider, progress); 1780 final boolean rowVisible = row.view.getVisibility() == VISIBLE; 1781 final boolean inGracePeriod = (SystemClock.uptimeMillis() - row.userAttempt) 1782 < USER_ATTEMPT_GRACE_PERIOD; 1783 mHandler.removeMessages(H.RECHECK, row); 1784 if (mShowing && rowVisible && inGracePeriod) { 1785 if (D.BUG) Log.d(TAG, "inGracePeriod"); 1786 mHandler.sendMessageAtTime(mHandler.obtainMessage(H.RECHECK, row), 1787 row.userAttempt + USER_ATTEMPT_GRACE_PERIOD); 1788 return; // don't update if visible and in grace period 1789 } 1790 if (vlevel == level) { 1791 if (mShowing && rowVisible) { 1792 return; // don't clamp if visible 1793 } 1794 } 1795 final int newProgress = vlevel * 100; 1796 if (progress != newProgress) { 1797 if (mShowing && rowVisible) { 1798 // animate! 1799 if (row.anim != null && row.anim.isRunning() 1800 && row.animTargetProgress == newProgress) { 1801 return; // already animating to the target progress 1802 } 1803 // start/update animation 1804 if (row.anim == null) { 1805 row.anim = ObjectAnimator.ofInt(row.slider, "progress", progress, newProgress); 1806 row.anim.setInterpolator(new DecelerateInterpolator()); 1807 } else { 1808 row.anim.cancel(); 1809 row.anim.setIntValues(progress, newProgress); 1810 } 1811 row.animTargetProgress = newProgress; 1812 row.anim.setDuration(UPDATE_ANIMATION_DURATION); 1813 row.anim.start(); 1814 } else { 1815 // update slider directly to clamped value 1816 if (row.anim != null) { 1817 row.anim.cancel(); 1818 } 1819 row.slider.setProgress(newProgress, true); 1820 } 1821 } 1822 } 1823 1824 private void recheckH(VolumeRow row) { 1825 if (row == null) { 1826 if (D.BUG) Log.d(TAG, "recheckH ALL"); 1827 trimObsoleteH(); 1828 for (VolumeRow r : mRows) { 1829 updateVolumeRowH(r); 1830 } 1831 } else { 1832 if (D.BUG) Log.d(TAG, "recheckH " + row.stream); 1833 updateVolumeRowH(row); 1834 } 1835 } 1836 1837 private void setStreamImportantH(int stream, boolean important) { 1838 for (VolumeRow row : mRows) { 1839 if (row.stream == stream) { 1840 row.important = important; 1841 return; 1842 } 1843 } 1844 } 1845 1846 private void showSafetyWarningH(int flags) { 1847 if ((flags & (AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_SHOW_UI_WARNINGS)) != 0 1848 || mShowing) { 1849 synchronized (mSafetyWarningLock) { 1850 if (mSafetyWarning != null) { 1851 return; 1852 } 1853 mSafetyWarning = new SafetyWarningDialog(mContext, mController.getAudioManager()) { 1854 @Override 1855 protected void cleanUp() { 1856 synchronized (mSafetyWarningLock) { 1857 mSafetyWarning = null; 1858 } 1859 recheckH(null); 1860 } 1861 }; 1862 mSafetyWarning.show(); 1863 } 1864 recheckH(null); 1865 } 1866 rescheduleTimeoutH(); 1867 } 1868 1869 private String getStreamLabelH(StreamState ss) { 1870 if (ss == null) { 1871 return ""; 1872 } 1873 if (ss.remoteLabel != null) { 1874 return ss.remoteLabel; 1875 } 1876 try { 1877 return mContext.getResources().getString(ss.name); 1878 } catch (Resources.NotFoundException e) { 1879 Slog.e(TAG, "Can't find translation for stream " + ss); 1880 return ""; 1881 } 1882 } 1883 1884 private Runnable getSinglePressFor(ImageButton button) { 1885 return () -> { 1886 if (button != null) { 1887 button.setPressed(true); 1888 button.postOnAnimationDelayed(getSingleUnpressFor(button), 200); 1889 } 1890 }; 1891 } 1892 1893 private Runnable getSingleUnpressFor(ImageButton button) { 1894 return () -> { 1895 if (button != null) { 1896 button.setPressed(false); 1897 } 1898 }; 1899 } 1900 1901 /** 1902 * Return the size of the 1-2 extra ringer options that are made visible when the ringer drawer 1903 * is opened. The drawer options are square so this can be used for height calculations (when in 1904 * portrait, and the drawer opens upward) or for width (when opening sideways in landscape). 1905 */ getRingerDrawerOpenExtraSize()1906 private int getRingerDrawerOpenExtraSize() { 1907 return (mRingerCount - 1) * mRingerDrawerItemSize; 1908 } 1909 updateBackgroundForDrawerClosedAmount()1910 private void updateBackgroundForDrawerClosedAmount() { 1911 if (mRingerAndDrawerContainerBackground == null) { 1912 return; 1913 } 1914 1915 final Rect bounds = mRingerAndDrawerContainerBackground.copyBounds(); 1916 if (!isLandscape()) { 1917 bounds.top = (int) (mRingerDrawerClosedAmount * getRingerDrawerOpenExtraSize()); 1918 } else { 1919 bounds.left = (int) (mRingerDrawerClosedAmount * getRingerDrawerOpenExtraSize()); 1920 } 1921 mRingerAndDrawerContainerBackground.setBounds(bounds); 1922 } 1923 1924 /* 1925 * The top container is responsible for drawing the solid color background behind the rightmost 1926 * (primary) volume row. This is because the volume drawer animates in from below, initially 1927 * overlapping the primary row. We need the drawer to draw below the row's SeekBar, since it 1928 * looks strange to overlap it, but above the row's background color, since otherwise it will be 1929 * clipped. 1930 * 1931 * Since we can't be both above and below the volume row view, we'll be below it, and render the 1932 * background color in the container since they're both above that. 1933 */ setTopContainerBackgroundDrawable()1934 private void setTopContainerBackgroundDrawable() { 1935 if (mTopContainer == null) { 1936 return; 1937 } 1938 1939 final ColorDrawable solidDrawable = new ColorDrawable( 1940 Utils.getColorAttrDefaultColor(mContext, com.android.internal.R.attr.colorSurface)); 1941 1942 final LayerDrawable background = new LayerDrawable(new Drawable[] { solidDrawable }); 1943 1944 // Size the solid color to match the primary volume row. In landscape, extend it upwards 1945 // slightly so that it fills in the bottom corners of the ringer icon, whose background is 1946 // rounded on all sides so that it can expand to the left, outside the dialog's background. 1947 background.setLayerSize(0, mDialogWidth, 1948 !isLandscape() 1949 ? mDialogRowsView.getHeight() 1950 : mDialogRowsView.getHeight() + mDialogCornerRadius); 1951 // Inset the top so that the color only renders below the ringer drawer, which has its own 1952 // background. In landscape, reduce the inset slightly since we are using the background to 1953 // fill in the corners of the closed ringer drawer. 1954 background.setLayerInsetTop(0, 1955 !isLandscape() 1956 ? mDialogRowsViewContainer.getTop() 1957 : mDialogRowsViewContainer.getTop() - mDialogCornerRadius); 1958 1959 // Set gravity to top-right, since additional rows will be added on the left. 1960 background.setLayerGravity(0, Gravity.TOP | Gravity.RIGHT); 1961 1962 // In landscape, the ringer drawer animates out to the left (instead of down). Since the 1963 // drawer comes from the right (beyond the bounds of the dialog), we should clip it so it 1964 // doesn't draw outside the dialog background. This isn't an issue in portrait, since the 1965 // drawer animates downward, below the volume row. 1966 if (isLandscape()) { 1967 mRingerAndDrawerContainer.setOutlineProvider(new ViewOutlineProvider() { 1968 @Override 1969 public void getOutline(View view, Outline outline) { 1970 outline.setRoundRect( 1971 0, 0, view.getWidth(), view.getHeight(), mDialogCornerRadius); 1972 } 1973 }); 1974 mRingerAndDrawerContainer.setClipToOutline(true); 1975 } 1976 1977 mTopContainer.setBackground(background); 1978 } 1979 1980 private final VolumeDialogController.Callbacks mControllerCallbackH 1981 = new VolumeDialogController.Callbacks() { 1982 @Override 1983 public void onShowRequested(int reason) { 1984 showH(reason); 1985 } 1986 1987 @Override 1988 public void onDismissRequested(int reason) { 1989 dismissH(reason); 1990 } 1991 1992 @Override 1993 public void onScreenOff() { 1994 dismissH(Events.DISMISS_REASON_SCREEN_OFF); 1995 } 1996 1997 @Override 1998 public void onStateChanged(State state) { 1999 onStateChangedH(state); 2000 } 2001 2002 @Override 2003 public void onLayoutDirectionChanged(int layoutDirection) { 2004 mDialogView.setLayoutDirection(layoutDirection); 2005 } 2006 2007 @Override 2008 public void onConfigurationChanged() { 2009 mDialog.dismiss(); 2010 mConfigChanged = true; 2011 } 2012 2013 @Override 2014 public void onShowVibrateHint() { 2015 if (mSilentMode) { 2016 mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false); 2017 } 2018 } 2019 2020 @Override 2021 public void onShowSilentHint() { 2022 if (mSilentMode) { 2023 mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false); 2024 } 2025 } 2026 2027 @Override 2028 public void onShowSafetyWarning(int flags) { 2029 showSafetyWarningH(flags); 2030 } 2031 2032 @Override 2033 public void onAccessibilityModeChanged(Boolean showA11yStream) { 2034 mShowA11yStream = showA11yStream == null ? false : showA11yStream; 2035 VolumeRow activeRow = getActiveRow(); 2036 if (!mShowA11yStream && STREAM_ACCESSIBILITY == activeRow.stream) { 2037 dismissH(Events.DISMISS_STREAM_GONE); 2038 } else { 2039 updateRowsH(activeRow); 2040 } 2041 2042 } 2043 2044 @Override 2045 public void onCaptionComponentStateChanged( 2046 Boolean isComponentEnabled, Boolean fromTooltip) { 2047 updateODICaptionsH(isComponentEnabled, fromTooltip); 2048 } 2049 }; 2050 2051 private final class H extends Handler { 2052 private static final int SHOW = 1; 2053 private static final int DISMISS = 2; 2054 private static final int RECHECK = 3; 2055 private static final int RECHECK_ALL = 4; 2056 private static final int SET_STREAM_IMPORTANT = 5; 2057 private static final int RESCHEDULE_TIMEOUT = 6; 2058 private static final int STATE_CHANGED = 7; 2059 H()2060 public H() { 2061 super(Looper.getMainLooper()); 2062 } 2063 2064 @Override handleMessage(Message msg)2065 public void handleMessage(Message msg) { 2066 switch (msg.what) { 2067 case SHOW: showH(msg.arg1); break; 2068 case DISMISS: dismissH(msg.arg1); break; 2069 case RECHECK: recheckH((VolumeRow) msg.obj); break; 2070 case RECHECK_ALL: recheckH(null); break; 2071 case SET_STREAM_IMPORTANT: setStreamImportantH(msg.arg1, msg.arg2 != 0); break; 2072 case RESCHEDULE_TIMEOUT: rescheduleTimeoutH(); break; 2073 case STATE_CHANGED: onStateChangedH(mState); break; 2074 } 2075 } 2076 } 2077 2078 private final class CustomDialog extends Dialog implements DialogInterface { CustomDialog(Context context)2079 public CustomDialog(Context context) { 2080 super(context, R.style.volume_dialog_theme); 2081 } 2082 2083 /** 2084 * NOTE: This will only be called for touches within the touchable region of the volume 2085 * dialog, as returned by {@link #onComputeInternalInsets}. Other touches, even if they are 2086 * within the bounds of the volume dialog, will fall through to the window below. 2087 */ 2088 @Override dispatchTouchEvent(MotionEvent ev)2089 public boolean dispatchTouchEvent(MotionEvent ev) { 2090 rescheduleTimeoutH(); 2091 return super.dispatchTouchEvent(ev); 2092 } 2093 2094 @Override onStart()2095 protected void onStart() { 2096 super.setCanceledOnTouchOutside(true); 2097 super.onStart(); 2098 } 2099 2100 @Override onStop()2101 protected void onStop() { 2102 super.onStop(); 2103 mHandler.sendEmptyMessage(H.RECHECK_ALL); 2104 } 2105 2106 /** 2107 * NOTE: This will be called with ACTION_OUTSIDE MotionEvents for touches that occur outside 2108 * of the touchable region of the volume dialog (as returned by 2109 * {@link #onComputeInternalInsets}) even if those touches occurred within the bounds of the 2110 * volume dialog. 2111 */ 2112 @Override onTouchEvent(MotionEvent event)2113 public boolean onTouchEvent(MotionEvent event) { 2114 if (mShowing) { 2115 if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { 2116 dismissH(Events.DISMISS_REASON_TOUCH_OUTSIDE); 2117 return true; 2118 } 2119 } 2120 return false; 2121 } 2122 } 2123 2124 private final class VolumeSeekBarChangeListener implements OnSeekBarChangeListener { 2125 private final VolumeRow mRow; 2126 VolumeSeekBarChangeListener(VolumeRow row)2127 private VolumeSeekBarChangeListener(VolumeRow row) { 2128 mRow = row; 2129 } 2130 2131 @Override onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)2132 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 2133 if (mRow.ss == null) return; 2134 if (D.BUG) Log.d(TAG, AudioSystem.streamToString(mRow.stream) 2135 + " onProgressChanged " + progress + " fromUser=" + fromUser); 2136 if (!fromUser) return; 2137 if (mRow.ss.levelMin > 0) { 2138 final int minProgress = mRow.ss.levelMin * 100; 2139 if (progress < minProgress) { 2140 seekBar.setProgress(minProgress); 2141 progress = minProgress; 2142 } 2143 } 2144 final int userLevel = getImpliedLevel(seekBar, progress); 2145 if (mRow.ss.level != userLevel || mRow.ss.muted && userLevel > 0) { 2146 mRow.userAttempt = SystemClock.uptimeMillis(); 2147 if (mRow.requestedLevel != userLevel) { 2148 mController.setActiveStream(mRow.stream); 2149 mController.setStreamVolume(mRow.stream, userLevel); 2150 mRow.requestedLevel = userLevel; 2151 Events.writeEvent(Events.EVENT_TOUCH_LEVEL_CHANGED, mRow.stream, 2152 userLevel); 2153 } 2154 } 2155 } 2156 2157 @Override onStartTrackingTouch(SeekBar seekBar)2158 public void onStartTrackingTouch(SeekBar seekBar) { 2159 if (D.BUG) Log.d(TAG, "onStartTrackingTouch"+ " " + mRow.stream); 2160 mController.setActiveStream(mRow.stream); 2161 mRow.tracking = true; 2162 } 2163 2164 @Override onStopTrackingTouch(SeekBar seekBar)2165 public void onStopTrackingTouch(SeekBar seekBar) { 2166 if (D.BUG) Log.d(TAG, "onStopTrackingTouch"+ " " + mRow.stream); 2167 mRow.tracking = false; 2168 mRow.userAttempt = SystemClock.uptimeMillis(); 2169 final int userLevel = getImpliedLevel(seekBar, seekBar.getProgress()); 2170 Events.writeEvent(Events.EVENT_TOUCH_LEVEL_DONE, mRow.stream, userLevel); 2171 if (mRow.ss.level != userLevel) { 2172 mHandler.sendMessageDelayed(mHandler.obtainMessage(H.RECHECK, mRow), 2173 USER_ATTEMPT_GRACE_PERIOD); 2174 } 2175 } 2176 } 2177 2178 private final class Accessibility extends AccessibilityDelegate { init()2179 public void init() { 2180 mDialogView.setAccessibilityDelegate(this); 2181 } 2182 2183 @Override dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event)2184 public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) { 2185 // Activities populate their title here. Follow that example. 2186 event.getText().add(composeWindowTitle()); 2187 return true; 2188 } 2189 2190 @Override onRequestSendAccessibilityEvent(ViewGroup host, View child, AccessibilityEvent event)2191 public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child, 2192 AccessibilityEvent event) { 2193 rescheduleTimeoutH(); 2194 return super.onRequestSendAccessibilityEvent(host, child, event); 2195 } 2196 } 2197 2198 private static class VolumeRow { 2199 private View view; 2200 private TextView header; 2201 private ImageButton icon; 2202 private Drawable sliderBgSolid; 2203 private AlphaTintDrawableWrapper sliderBgIcon; 2204 private Drawable sliderProgressSolid; 2205 private AlphaTintDrawableWrapper sliderProgressIcon; 2206 private SeekBar slider; 2207 private TextView number; 2208 private int stream; 2209 private StreamState ss; 2210 private long userAttempt; // last user-driven slider change 2211 private boolean tracking; // tracking slider touch 2212 private int requestedLevel = -1; // pending user-requested level via progress changed 2213 private int iconRes; 2214 private int iconMuteRes; 2215 private boolean important; 2216 private boolean defaultStream; 2217 private ColorStateList cachedTint; 2218 private int iconState; // from Events 2219 private ObjectAnimator anim; // slider progress animation for non-touch-related updates 2220 private int animTargetProgress; 2221 private int lastAudibleLevel = 1; 2222 private FrameLayout dndIcon; 2223 setIcon(int iconRes, Resources.Theme theme)2224 void setIcon(int iconRes, Resources.Theme theme) { 2225 if (icon != null) { 2226 icon.setImageResource(iconRes); 2227 } 2228 2229 if (sliderProgressIcon != null) { 2230 sliderProgressIcon.setDrawable(view.getResources().getDrawable(iconRes, theme)); 2231 } 2232 if (sliderBgIcon != null) { 2233 sliderBgIcon.setDrawable(view.getResources().getDrawable(iconRes, theme)); 2234 } 2235 } 2236 } 2237 2238 /** 2239 * Click listener added to each ringer option in the drawer. This will initiate the animation to 2240 * select and then close the ringer drawer, and actually change the ringer mode. 2241 */ 2242 private class RingerDrawerItemClickListener implements View.OnClickListener { 2243 private final int mClickedRingerMode; 2244 RingerDrawerItemClickListener(int clickedRingerMode)2245 RingerDrawerItemClickListener(int clickedRingerMode) { 2246 mClickedRingerMode = clickedRingerMode; 2247 } 2248 2249 @Override onClick(View view)2250 public void onClick(View view) { 2251 // If the ringer drawer isn't open, don't let anything in it be clicked. 2252 if (!mIsRingerDrawerOpen) { 2253 return; 2254 } 2255 2256 setRingerMode(mClickedRingerMode); 2257 2258 mRingerDrawerIconAnimatingSelected = getDrawerIconViewForMode(mClickedRingerMode); 2259 mRingerDrawerIconAnimatingDeselected = getDrawerIconViewForMode( 2260 mState.ringerModeInternal); 2261 2262 // Begin switching the selected icon and deselected icon colors since the background is 2263 // going to animate behind the new selection. 2264 mRingerDrawerIconColorAnimator.start(); 2265 2266 mSelectedRingerContainer.setVisibility(View.INVISIBLE); 2267 mRingerDrawerNewSelectionBg.setAlpha(1f); 2268 mRingerDrawerNewSelectionBg.animate() 2269 .setInterpolator(Interpolators.ACCELERATE_DECELERATE) 2270 .setDuration(DRAWER_ANIMATION_DURATION_SHORT) 2271 .withEndAction(() -> { 2272 mRingerDrawerNewSelectionBg.setAlpha(0f); 2273 2274 if (!isLandscape()) { 2275 mSelectedRingerContainer.setTranslationY( 2276 getTranslationInDrawerForRingerMode(mClickedRingerMode)); 2277 } else { 2278 mSelectedRingerContainer.setTranslationX( 2279 getTranslationInDrawerForRingerMode(mClickedRingerMode)); 2280 } 2281 2282 mSelectedRingerContainer.setVisibility(VISIBLE); 2283 hideRingerDrawer(); 2284 }); 2285 2286 if (!isLandscape()) { 2287 mRingerDrawerNewSelectionBg.animate() 2288 .translationY(getTranslationInDrawerForRingerMode(mClickedRingerMode)) 2289 .start(); 2290 } else { 2291 mRingerDrawerNewSelectionBg.animate() 2292 .translationX(getTranslationInDrawerForRingerMode(mClickedRingerMode)) 2293 .start(); 2294 } 2295 } 2296 } 2297 } 2298