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