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.VISIBLE; 32 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 33 34 import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED; 35 36 import android.animation.ObjectAnimator; 37 import android.annotation.SuppressLint; 38 import android.app.ActivityManager; 39 import android.app.Dialog; 40 import android.app.KeyguardManager; 41 import android.content.ContentResolver; 42 import android.content.Context; 43 import android.content.DialogInterface; 44 import android.content.Intent; 45 import android.content.pm.PackageManager; 46 import android.content.res.ColorStateList; 47 import android.content.res.Configuration; 48 import android.content.res.Resources; 49 import android.content.res.TypedArray; 50 import android.graphics.Color; 51 import android.graphics.PixelFormat; 52 import android.graphics.drawable.ColorDrawable; 53 import android.media.AudioManager; 54 import android.media.AudioSystem; 55 import android.os.Debug; 56 import android.os.Handler; 57 import android.os.Looper; 58 import android.os.Message; 59 import android.os.SystemClock; 60 import android.os.VibrationEffect; 61 import android.provider.Settings; 62 import android.provider.Settings.Global; 63 import android.text.InputFilter; 64 import android.util.Log; 65 import android.util.Slog; 66 import android.util.SparseBooleanArray; 67 import android.view.ContextThemeWrapper; 68 import android.view.Gravity; 69 import android.view.MotionEvent; 70 import android.view.View; 71 import android.view.View.AccessibilityDelegate; 72 import android.view.ViewGroup; 73 import android.view.ViewPropertyAnimator; 74 import android.view.ViewStub; 75 import android.view.Window; 76 import android.view.WindowManager; 77 import android.view.accessibility.AccessibilityEvent; 78 import android.view.accessibility.AccessibilityManager; 79 import android.view.accessibility.AccessibilityNodeInfo; 80 import android.view.animation.DecelerateInterpolator; 81 import android.widget.FrameLayout; 82 import android.widget.ImageButton; 83 import android.widget.SeekBar; 84 import android.widget.SeekBar.OnSeekBarChangeListener; 85 import android.widget.TextView; 86 import android.widget.Toast; 87 88 import com.android.settingslib.Utils; 89 import com.android.systemui.Dependency; 90 import com.android.systemui.Prefs; 91 import com.android.systemui.R; 92 import com.android.systemui.plugins.ActivityStarter; 93 import com.android.systemui.plugins.VolumeDialog; 94 import com.android.systemui.plugins.VolumeDialogController; 95 import com.android.systemui.plugins.VolumeDialogController.State; 96 import com.android.systemui.plugins.VolumeDialogController.StreamState; 97 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; 98 import com.android.systemui.statusbar.policy.ConfigurationController; 99 import com.android.systemui.statusbar.policy.DeviceProvisionedController; 100 101 import java.io.PrintWriter; 102 import java.util.ArrayList; 103 import java.util.List; 104 105 /** 106 * Visual presentation of the volume dialog. 107 * 108 * A client of VolumeDialogControllerImpl and its state model. 109 * 110 * Methods ending in "H" must be called on the (ui) handler. 111 */ 112 public class VolumeDialogImpl implements VolumeDialog, 113 ConfigurationController.ConfigurationListener { 114 private static final String TAG = Util.logTag(VolumeDialogImpl.class); 115 116 private static final long USER_ATTEMPT_GRACE_PERIOD = 1000; 117 private static final int UPDATE_ANIMATION_DURATION = 80; 118 119 static final int DIALOG_TIMEOUT_MILLIS = 3000; 120 static final int DIALOG_SAFETYWARNING_TIMEOUT_MILLIS = 5000; 121 static final int DIALOG_ODI_CAPTIONS_TOOLTIP_TIMEOUT_MILLIS = 5000; 122 static final int DIALOG_HOVERING_TIMEOUT_MILLIS = 16000; 123 static final int DIALOG_SHOW_ANIMATION_DURATION = 300; 124 static final int DIALOG_HIDE_ANIMATION_DURATION = 250; 125 126 private final Context mContext; 127 private final H mHandler = new H(); 128 private final VolumeDialogController mController; 129 private final DeviceProvisionedController mDeviceProvisionedController; 130 131 private Window mWindow; 132 private CustomDialog mDialog; 133 private ViewGroup mDialogView; 134 private ViewGroup mDialogRowsView; 135 private ViewGroup mRinger; 136 private ImageButton mRingerIcon; 137 private ViewGroup mODICaptionsView; 138 private CaptionsToggleImageButton mODICaptionsIcon; 139 private View mSettingsView; 140 private ImageButton mSettingsIcon; 141 private FrameLayout mZenIcon; 142 private final List<VolumeRow> mRows = new ArrayList<>(); 143 private ConfigurableTexts mConfigurableTexts; 144 private final SparseBooleanArray mDynamic = new SparseBooleanArray(); 145 private final KeyguardManager mKeyguard; 146 private final ActivityManager mActivityManager; 147 private final AccessibilityManagerWrapper mAccessibilityMgr; 148 private final Object mSafetyWarningLock = new Object(); 149 private final Accessibility mAccessibility = new Accessibility(); 150 151 private boolean mShowing; 152 private boolean mShowA11yStream; 153 154 private int mActiveStream; 155 private int mPrevActiveStream; 156 private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE; 157 private boolean mSilentMode = VolumePrefs.DEFAULT_ENABLE_SILENT_MODE; 158 private State mState; 159 private SafetyWarningDialog mSafetyWarning; 160 private boolean mHovering = false; 161 private boolean mShowActiveStreamOnly; 162 private boolean mConfigChanged = false; 163 private boolean mHasSeenODICaptionsTooltip; 164 private ViewStub mODICaptionsTooltipViewStub; 165 private View mODICaptionsTooltipView = null; 166 167 public VolumeDialogImpl(Context context) { 168 mContext = 169 new ContextThemeWrapper(context, R.style.qs_theme); 170 mController = Dependency.get(VolumeDialogController.class); 171 mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); 172 mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); 173 mAccessibilityMgr = Dependency.get(AccessibilityManagerWrapper.class); 174 mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); 175 mShowActiveStreamOnly = showActiveStreamOnly(); 176 mHasSeenODICaptionsTooltip = 177 Prefs.getBoolean(context, Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, false); 178 } 179 180 @Override 181 public void onUiModeChanged() { 182 mContext.getTheme().applyStyle(mContext.getThemeResId(), true); 183 } 184 185 public void init(int windowType, Callback callback) { 186 initDialog(); 187 188 mAccessibility.init(); 189 190 mController.addCallback(mControllerCallbackH, mHandler); 191 mController.getState(); 192 193 Dependency.get(ConfigurationController.class).addCallback(this); 194 } 195 196 @Override 197 public void destroy() { 198 mController.removeCallback(mControllerCallbackH); 199 mHandler.removeCallbacksAndMessages(null); 200 Dependency.get(ConfigurationController.class).removeCallback(this); 201 } 202 203 private void initDialog() { 204 mDialog = new CustomDialog(mContext); 205 206 mConfigurableTexts = new ConfigurableTexts(mContext); 207 mHovering = false; 208 mShowing = false; 209 mWindow = mDialog.getWindow(); 210 mWindow.requestFeature(Window.FEATURE_NO_TITLE); 211 mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); 212 mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND 213 | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); 214 mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 215 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 216 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 217 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED 218 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH 219 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); 220 mWindow.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY); 221 mWindow.setWindowAnimations(com.android.internal.R.style.Animation_Toast); 222 WindowManager.LayoutParams lp = mWindow.getAttributes(); 223 lp.format = PixelFormat.TRANSLUCENT; 224 lp.setTitle(VolumeDialogImpl.class.getSimpleName()); 225 lp.windowAnimations = -1; 226 lp.gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL; 227 mWindow.setAttributes(lp); 228 mWindow.setLayout(WRAP_CONTENT, WRAP_CONTENT); 229 230 mDialog.setContentView(R.layout.volume_dialog); 231 mDialogView = mDialog.findViewById(R.id.volume_dialog); 232 mDialogView.setAlpha(0); 233 mDialog.setCanceledOnTouchOutside(true); 234 mDialog.setOnShowListener(dialog -> { 235 if (!isLandscape()) mDialogView.setTranslationX(mDialogView.getWidth() / 2.0f); 236 mDialogView.setAlpha(0); 237 mDialogView.animate() 238 .alpha(1) 239 .translationX(0) 240 .setDuration(DIALOG_SHOW_ANIMATION_DURATION) 241 .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator()) 242 .withEndAction(() -> { 243 if (!Prefs.getBoolean(mContext, Prefs.Key.TOUCHED_RINGER_TOGGLE, false)) { 244 if (mRingerIcon != null) { 245 mRingerIcon.postOnAnimationDelayed( 246 getSinglePressFor(mRingerIcon), 1500); 247 } 248 } 249 }) 250 .start(); 251 }); 252 253 mDialogView.setOnHoverListener((v, event) -> { 254 int action = event.getActionMasked(); 255 mHovering = (action == MotionEvent.ACTION_HOVER_ENTER) 256 || (action == MotionEvent.ACTION_HOVER_MOVE); 257 rescheduleTimeoutH(); 258 return true; 259 }); 260 261 mDialogRowsView = mDialog.findViewById(R.id.volume_dialog_rows); 262 mRinger = mDialog.findViewById(R.id.ringer); 263 if (mRinger != null) { 264 mRingerIcon = mRinger.findViewById(R.id.ringer_icon); 265 mZenIcon = mRinger.findViewById(R.id.dnd_icon); 266 } 267 268 mODICaptionsView = mDialog.findViewById(R.id.odi_captions); 269 if (mODICaptionsView != null) { 270 mODICaptionsIcon = mODICaptionsView.findViewById(R.id.odi_captions_icon); 271 } 272 mODICaptionsTooltipViewStub = mDialog.findViewById(R.id.odi_captions_tooltip_stub); 273 if (mHasSeenODICaptionsTooltip && mODICaptionsTooltipViewStub != null) { 274 mDialogView.removeView(mODICaptionsTooltipViewStub); 275 mODICaptionsTooltipViewStub = null; 276 } 277 278 mSettingsView = mDialog.findViewById(R.id.settings_container); 279 mSettingsIcon = mDialog.findViewById(R.id.settings); 280 281 if (mRows.isEmpty()) { 282 if (!AudioSystem.isSingleVolume(mContext)) { 283 addRow(STREAM_ACCESSIBILITY, R.drawable.ic_volume_accessibility, 284 R.drawable.ic_volume_accessibility, true, false); 285 } 286 addRow(AudioManager.STREAM_MUSIC, 287 R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true, true); 288 if (!AudioSystem.isSingleVolume(mContext)) { 289 addRow(AudioManager.STREAM_RING, 290 R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true, false); 291 addRow(STREAM_ALARM, 292 R.drawable.ic_alarm, R.drawable.ic_volume_alarm_mute, true, false); 293 addRow(AudioManager.STREAM_VOICE_CALL, 294 com.android.internal.R.drawable.ic_phone, 295 com.android.internal.R.drawable.ic_phone, false, false); 296 addRow(AudioManager.STREAM_BLUETOOTH_SCO, 297 R.drawable.ic_volume_bt_sco, R.drawable.ic_volume_bt_sco, false, false); 298 addRow(AudioManager.STREAM_SYSTEM, R.drawable.ic_volume_system, 299 R.drawable.ic_volume_system_mute, false, false); 300 } 301 } else { 302 addExistingRows(); 303 } 304 305 updateRowsH(getActiveRow()); 306 initRingerH(); 307 initSettingsH(); 308 initODICaptionsH(); 309 } 310 311 protected ViewGroup getDialogView() { 312 return mDialogView; 313 } 314 315 private int getAlphaAttr(int attr) { 316 TypedArray ta = mContext.obtainStyledAttributes(new int[]{attr}); 317 float alpha = ta.getFloat(0, 0); 318 ta.recycle(); 319 return (int) (alpha * 255); 320 } 321 322 private boolean isLandscape() { 323 return mContext.getResources().getConfiguration().orientation == 324 Configuration.ORIENTATION_LANDSCAPE; 325 } 326 327 public void setStreamImportant(int stream, boolean important) { 328 mHandler.obtainMessage(H.SET_STREAM_IMPORTANT, stream, important ? 1 : 0).sendToTarget(); 329 } 330 331 public void setAutomute(boolean automute) { 332 if (mAutomute == automute) return; 333 mAutomute = automute; 334 mHandler.sendEmptyMessage(H.RECHECK_ALL); 335 } 336 337 public void setSilentMode(boolean silentMode) { 338 if (mSilentMode == silentMode) return; 339 mSilentMode = silentMode; 340 mHandler.sendEmptyMessage(H.RECHECK_ALL); 341 } 342 343 private void addRow(int stream, int iconRes, int iconMuteRes, boolean important, 344 boolean defaultStream) { 345 addRow(stream, iconRes, iconMuteRes, important, defaultStream, false); 346 } 347 348 private void addRow(int stream, int iconRes, int iconMuteRes, boolean important, 349 boolean defaultStream, boolean dynamic) { 350 if (D.BUG) Slog.d(TAG, "Adding row for stream " + stream); 351 VolumeRow row = new VolumeRow(); 352 initRow(row, stream, iconRes, iconMuteRes, important, defaultStream); 353 mDialogRowsView.addView(row.view); 354 mRows.add(row); 355 } 356 357 private void addExistingRows() { 358 int N = mRows.size(); 359 for (int i = 0; i < N; i++) { 360 final VolumeRow row = mRows.get(i); 361 initRow(row, row.stream, row.iconRes, row.iconMuteRes, row.important, 362 row.defaultStream); 363 mDialogRowsView.addView(row.view); 364 updateVolumeRowH(row); 365 } 366 } 367 368 private VolumeRow getActiveRow() { 369 for (VolumeRow row : mRows) { 370 if (row.stream == mActiveStream) { 371 return row; 372 } 373 } 374 for (VolumeRow row : mRows) { 375 if (row.stream == STREAM_MUSIC) { 376 return row; 377 } 378 } 379 return mRows.get(0); 380 } 381 382 private VolumeRow findRow(int stream) { 383 for (VolumeRow row : mRows) { 384 if (row.stream == stream) return row; 385 } 386 return null; 387 } 388 389 public void dump(PrintWriter writer) { 390 writer.println(VolumeDialogImpl.class.getSimpleName() + " state:"); 391 writer.print(" mShowing: "); writer.println(mShowing); 392 writer.print(" mActiveStream: "); writer.println(mActiveStream); 393 writer.print(" mDynamic: "); writer.println(mDynamic); 394 writer.print(" mAutomute: "); writer.println(mAutomute); 395 writer.print(" mSilentMode: "); writer.println(mSilentMode); 396 } 397 398 private static int getImpliedLevel(SeekBar seekBar, int progress) { 399 final int m = seekBar.getMax(); 400 final int n = m / 100 - 1; 401 final int level = progress == 0 ? 0 402 : progress == m ? (m / 100) : (1 + (int)((progress / (float) m) * n)); 403 return level; 404 } 405 406 @SuppressLint("InflateParams") 407 private void initRow(final VolumeRow row, final int stream, int iconRes, int iconMuteRes, 408 boolean important, boolean defaultStream) { 409 row.stream = stream; 410 row.iconRes = iconRes; 411 row.iconMuteRes = iconMuteRes; 412 row.important = important; 413 row.defaultStream = defaultStream; 414 row.view = mDialog.getLayoutInflater().inflate(R.layout.volume_dialog_row, null); 415 row.view.setId(row.stream); 416 row.view.setTag(row); 417 row.header = row.view.findViewById(R.id.volume_row_header); 418 row.header.setId(20 * row.stream); 419 if (stream == STREAM_ACCESSIBILITY) { 420 row.header.setFilters(new InputFilter[] {new InputFilter.LengthFilter(13)}); 421 } 422 row.dndIcon = row.view.findViewById(R.id.dnd_icon); 423 row.slider = row.view.findViewById(R.id.volume_row_slider); 424 row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row)); 425 426 row.anim = null; 427 428 row.icon = row.view.findViewById(R.id.volume_row_icon); 429 row.icon.setImageResource(iconRes); 430 if (row.stream != AudioSystem.STREAM_ACCESSIBILITY) { 431 row.icon.setOnClickListener(v -> { 432 Events.writeEvent(mContext, Events.EVENT_ICON_CLICK, row.stream, row.iconState); 433 mController.setActiveStream(row.stream); 434 if (row.stream == AudioManager.STREAM_RING) { 435 final boolean hasVibrator = mController.hasVibrator(); 436 if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) { 437 if (hasVibrator) { 438 mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false); 439 } else { 440 final boolean wasZero = row.ss.level == 0; 441 mController.setStreamVolume(stream, 442 wasZero ? row.lastAudibleLevel : 0); 443 } 444 } else { 445 mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false); 446 if (row.ss.level == 0) { 447 mController.setStreamVolume(stream, 1); 448 } 449 } 450 } else { 451 final boolean vmute = row.ss.level == row.ss.levelMin; 452 mController.setStreamVolume(stream, 453 vmute ? row.lastAudibleLevel : row.ss.levelMin); 454 } 455 row.userAttempt = 0; // reset the grace period, slider updates immediately 456 }); 457 } else { 458 row.icon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); 459 } 460 } 461 462 public void initSettingsH() { 463 if (mSettingsView != null) { 464 mSettingsView.setVisibility( 465 mDeviceProvisionedController.isCurrentUserSetup() && 466 mActivityManager.getLockTaskModeState() == LOCK_TASK_MODE_NONE ? 467 VISIBLE : GONE); 468 } 469 if (mSettingsIcon != null) { 470 mSettingsIcon.setOnClickListener(v -> { 471 Events.writeEvent(mContext, Events.EVENT_SETTINGS_CLICK); 472 Intent intent = new Intent(Settings.Panel.ACTION_VOLUME); 473 dismissH(DISMISS_REASON_SETTINGS_CLICKED); 474 Dependency.get(ActivityStarter.class).startActivity(intent, 475 true /* dismissShade */); 476 }); 477 } 478 } 479 480 public void initRingerH() { 481 if (mRingerIcon != null) { 482 mRingerIcon.setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE); 483 mRingerIcon.setOnClickListener(v -> { 484 Prefs.putBoolean(mContext, Prefs.Key.TOUCHED_RINGER_TOGGLE, true); 485 final StreamState ss = mState.states.get(AudioManager.STREAM_RING); 486 if (ss == null) { 487 return; 488 } 489 // normal -> vibrate -> silent -> normal (skip vibrate if device doesn't have 490 // a vibrator. 491 int newRingerMode; 492 final boolean hasVibrator = mController.hasVibrator(); 493 if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) { 494 if (hasVibrator) { 495 newRingerMode = AudioManager.RINGER_MODE_VIBRATE; 496 } else { 497 newRingerMode = AudioManager.RINGER_MODE_SILENT; 498 } 499 } else if (mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) { 500 newRingerMode = AudioManager.RINGER_MODE_SILENT; 501 } else { 502 newRingerMode = AudioManager.RINGER_MODE_NORMAL; 503 if (ss.level == 0) { 504 mController.setStreamVolume(AudioManager.STREAM_RING, 1); 505 } 506 } 507 Events.writeEvent(mContext, Events.EVENT_RINGER_TOGGLE, newRingerMode); 508 incrementManualToggleCount(); 509 updateRingerH(); 510 provideTouchFeedbackH(newRingerMode); 511 mController.setRingerMode(newRingerMode, false); 512 maybeShowToastH(newRingerMode); 513 }); 514 } 515 updateRingerH(); 516 } 517 518 private void initODICaptionsH() { 519 if (mODICaptionsIcon != null) { 520 mODICaptionsIcon.setOnConfirmedTapListener(() -> { 521 onCaptionIconClicked(); 522 Events.writeEvent(mContext, Events.EVENT_ODI_CAPTIONS_CLICK); 523 }, mHandler); 524 } 525 526 mController.getCaptionsComponentState(false); 527 } 528 529 private void checkODICaptionsTooltip(boolean fromDismiss) { 530 if (!mHasSeenODICaptionsTooltip && !fromDismiss && mODICaptionsTooltipViewStub != null) { 531 mController.getCaptionsComponentState(true); 532 } else { 533 if (mHasSeenODICaptionsTooltip && fromDismiss && mODICaptionsTooltipView != null) { 534 hideCaptionsTooltip(); 535 } 536 } 537 } 538 539 protected void showCaptionsTooltip() { 540 if (!mHasSeenODICaptionsTooltip && mODICaptionsTooltipViewStub != null) { 541 mODICaptionsTooltipView = mODICaptionsTooltipViewStub.inflate(); 542 mODICaptionsTooltipView.findViewById(R.id.dismiss).setOnClickListener(v -> { 543 hideCaptionsTooltip(); 544 Events.writeEvent(mContext, Events.EVENT_ODI_CAPTIONS_TOOLTIP_CLICK); 545 }); 546 mODICaptionsTooltipViewStub = null; 547 rescheduleTimeoutH(); 548 } 549 550 if (mODICaptionsTooltipView != null) { 551 mODICaptionsTooltipView.setAlpha(0.f); 552 mODICaptionsTooltipView.animate() 553 .alpha(1.f) 554 .setStartDelay(DIALOG_SHOW_ANIMATION_DURATION) 555 .withEndAction(() -> { 556 if (D.BUG) Log.d(TAG, "tool:checkODICaptionsTooltip() putBoolean true"); 557 Prefs.putBoolean(mContext, 558 Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, true); 559 mHasSeenODICaptionsTooltip = true; 560 if (mODICaptionsIcon != null) { 561 mODICaptionsIcon 562 .postOnAnimation(getSinglePressFor(mODICaptionsIcon)); 563 } 564 }) 565 .start(); 566 } 567 } 568 569 private void hideCaptionsTooltip() { 570 if (mODICaptionsTooltipView != null && mODICaptionsTooltipView.getVisibility() == VISIBLE) { 571 mODICaptionsTooltipView.animate().cancel(); 572 mODICaptionsTooltipView.setAlpha(1.f); 573 mODICaptionsTooltipView.animate() 574 .alpha(0.f) 575 .setStartDelay(0) 576 .setDuration(DIALOG_HIDE_ANIMATION_DURATION) 577 .withEndAction(() -> mODICaptionsTooltipView.setVisibility(INVISIBLE)) 578 .start(); 579 } 580 } 581 582 protected void tryToRemoveCaptionsTooltip() { 583 if (mHasSeenODICaptionsTooltip && mODICaptionsTooltipView != null) { 584 ViewGroup container = mDialog.findViewById(R.id.volume_dialog_container); 585 container.removeView(mODICaptionsTooltipView); 586 mODICaptionsTooltipView = null; 587 } 588 } 589 590 private void updateODICaptionsH(boolean isServiceComponentEnabled, boolean fromTooltip) { 591 if (mODICaptionsView != null) { 592 mODICaptionsView.setVisibility(isServiceComponentEnabled ? VISIBLE : GONE); 593 } 594 595 if (!isServiceComponentEnabled) return; 596 597 updateCaptionsIcon(); 598 if (fromTooltip) showCaptionsTooltip(); 599 } 600 601 private void updateCaptionsIcon() { 602 boolean captionsEnabled = mController.areCaptionsEnabled(); 603 if (mODICaptionsIcon.getCaptionsEnabled() != captionsEnabled) { 604 mHandler.post(mODICaptionsIcon.setCaptionsEnabled(captionsEnabled)); 605 } 606 607 boolean isOptedOut = mController.isCaptionStreamOptedOut(); 608 if (mODICaptionsIcon.getOptedOut() != isOptedOut) { 609 mHandler.post(() -> mODICaptionsIcon.setOptedOut(isOptedOut)); 610 } 611 } 612 613 private void onCaptionIconClicked() { 614 boolean isEnabled = mController.areCaptionsEnabled(); 615 mController.setCaptionsEnabled(!isEnabled); 616 updateCaptionsIcon(); 617 } 618 619 private void incrementManualToggleCount() { 620 ContentResolver cr = mContext.getContentResolver(); 621 int ringerCount = Settings.Secure.getInt(cr, Settings.Secure.MANUAL_RINGER_TOGGLE_COUNT, 0); 622 Settings.Secure.putInt(cr, Settings.Secure.MANUAL_RINGER_TOGGLE_COUNT, ringerCount + 1); 623 } 624 625 private void provideTouchFeedbackH(int newRingerMode) { 626 VibrationEffect effect = null; 627 switch (newRingerMode) { 628 case RINGER_MODE_NORMAL: 629 mController.scheduleTouchFeedback(); 630 break; 631 case RINGER_MODE_SILENT: 632 effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); 633 break; 634 case RINGER_MODE_VIBRATE: 635 default: 636 effect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK); 637 } 638 if (effect != null) { 639 mController.vibrate(effect); 640 } 641 } 642 643 private void maybeShowToastH(int newRingerMode) { 644 int seenToastCount = Prefs.getInt(mContext, Prefs.Key.SEEN_RINGER_GUIDANCE_COUNT, 0); 645 646 if (seenToastCount > VolumePrefs.SHOW_RINGER_TOAST_COUNT) { 647 return; 648 } 649 CharSequence toastText = null; 650 switch (newRingerMode) { 651 case RINGER_MODE_NORMAL: 652 final StreamState ss = mState.states.get(AudioManager.STREAM_RING); 653 if (ss != null) { 654 toastText = mContext.getString( 655 R.string.volume_dialog_ringer_guidance_ring, 656 Utils.formatPercentage(ss.level, ss.levelMax)); 657 } 658 break; 659 case RINGER_MODE_SILENT: 660 toastText = mContext.getString( 661 com.android.internal.R.string.volume_dialog_ringer_guidance_silent); 662 break; 663 case RINGER_MODE_VIBRATE: 664 default: 665 toastText = mContext.getString( 666 com.android.internal.R.string.volume_dialog_ringer_guidance_vibrate); 667 } 668 669 Toast.makeText(mContext, toastText, Toast.LENGTH_SHORT).show(); 670 seenToastCount++; 671 Prefs.putInt(mContext, Prefs.Key.SEEN_RINGER_GUIDANCE_COUNT, seenToastCount); 672 } 673 674 public void show(int reason) { 675 mHandler.obtainMessage(H.SHOW, reason, 0).sendToTarget(); 676 } 677 678 public void dismiss(int reason) { 679 mHandler.obtainMessage(H.DISMISS, reason, 0).sendToTarget(); 680 } 681 682 private void showH(int reason) { 683 if (D.BUG) Log.d(TAG, "showH r=" + Events.SHOW_REASONS[reason]); 684 mHandler.removeMessages(H.SHOW); 685 mHandler.removeMessages(H.DISMISS); 686 rescheduleTimeoutH(); 687 688 if (mConfigChanged) { 689 initDialog(); // resets mShowing to false 690 mConfigurableTexts.update(); 691 mConfigChanged = false; 692 } 693 694 initSettingsH(); 695 mShowing = true; 696 mDialog.show(); 697 Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked()); 698 mController.notifyVisible(true); 699 mController.getCaptionsComponentState(false); 700 checkODICaptionsTooltip(false); 701 } 702 703 protected void rescheduleTimeoutH() { 704 mHandler.removeMessages(H.DISMISS); 705 final int timeout = computeTimeoutH(); 706 mHandler.sendMessageDelayed(mHandler 707 .obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT, 0), timeout); 708 if (D.BUG) Log.d(TAG, "rescheduleTimeout " + timeout + " " + Debug.getCaller()); 709 mController.userActivity(); 710 } 711 712 private int computeTimeoutH() { 713 if (mHovering) { 714 return mAccessibilityMgr.getRecommendedTimeoutMillis(DIALOG_HOVERING_TIMEOUT_MILLIS, 715 AccessibilityManager.FLAG_CONTENT_CONTROLS); 716 } 717 if (mSafetyWarning != null) { 718 return mAccessibilityMgr.getRecommendedTimeoutMillis( 719 DIALOG_SAFETYWARNING_TIMEOUT_MILLIS, 720 AccessibilityManager.FLAG_CONTENT_TEXT 721 | AccessibilityManager.FLAG_CONTENT_CONTROLS); 722 } 723 if (!mHasSeenODICaptionsTooltip && mODICaptionsTooltipView != null) { 724 return mAccessibilityMgr.getRecommendedTimeoutMillis( 725 DIALOG_ODI_CAPTIONS_TOOLTIP_TIMEOUT_MILLIS, 726 AccessibilityManager.FLAG_CONTENT_TEXT 727 | AccessibilityManager.FLAG_CONTENT_CONTROLS); 728 } 729 return mAccessibilityMgr.getRecommendedTimeoutMillis(DIALOG_TIMEOUT_MILLIS, 730 AccessibilityManager.FLAG_CONTENT_CONTROLS); 731 } 732 733 protected void dismissH(int reason) { 734 if (D.BUG) { 735 Log.d(TAG, "mDialog.dismiss() reason: " + Events.DISMISS_REASONS[reason] 736 + " from: " + Debug.getCaller()); 737 } 738 mHandler.removeMessages(H.DISMISS); 739 mHandler.removeMessages(H.SHOW); 740 mDialogView.animate().cancel(); 741 if (mShowing) { 742 mShowing = false; 743 // Only logs when the volume dialog visibility is changed. 744 Events.writeEvent(mContext, Events.EVENT_DISMISS_DIALOG, reason); 745 } 746 mDialogView.setTranslationX(0); 747 mDialogView.setAlpha(1); 748 ViewPropertyAnimator animator = mDialogView.animate() 749 .alpha(0) 750 .setDuration(DIALOG_HIDE_ANIMATION_DURATION) 751 .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator()) 752 .withEndAction(() -> mHandler.postDelayed(() -> { 753 mDialog.dismiss(); 754 tryToRemoveCaptionsTooltip(); 755 }, 50)); 756 if (!isLandscape()) animator.translationX(mDialogView.getWidth() / 2.0f); 757 animator.start(); 758 checkODICaptionsTooltip(true); 759 mController.notifyVisible(false); 760 synchronized (mSafetyWarningLock) { 761 if (mSafetyWarning != null) { 762 if (D.BUG) Log.d(TAG, "SafetyWarning dismissed"); 763 mSafetyWarning.dismiss(); 764 } 765 } 766 } 767 768 private boolean showActiveStreamOnly() { 769 return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK) 770 || mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION); 771 } 772 773 private boolean shouldBeVisibleH(VolumeRow row, VolumeRow activeRow) { 774 boolean isActive = row.stream == activeRow.stream; 775 776 if (isActive) { 777 return true; 778 } 779 780 if (!mShowActiveStreamOnly) { 781 if (row.stream == AudioSystem.STREAM_ACCESSIBILITY) { 782 return mShowA11yStream; 783 } 784 785 // if the active row is accessibility, then continue to display previous 786 // active row since accessibility is displayed under it 787 if (activeRow.stream == AudioSystem.STREAM_ACCESSIBILITY && 788 row.stream == mPrevActiveStream) { 789 return true; 790 } 791 792 if (row.defaultStream) { 793 return activeRow.stream == STREAM_RING 794 || activeRow.stream == STREAM_ALARM 795 || activeRow.stream == STREAM_VOICE_CALL 796 || activeRow.stream == STREAM_ACCESSIBILITY 797 || mDynamic.get(activeRow.stream); 798 } 799 } 800 801 return false; 802 } 803 804 private void updateRowsH(final VolumeRow activeRow) { 805 if (D.BUG) Log.d(TAG, "updateRowsH"); 806 if (!mShowing) { 807 trimObsoleteH(); 808 } 809 // apply changes to all rows 810 for (final VolumeRow row : mRows) { 811 final boolean isActive = row == activeRow; 812 final boolean shouldBeVisible = shouldBeVisibleH(row, activeRow); 813 Util.setVisOrGone(row.view, shouldBeVisible); 814 if (row.view.isShown()) { 815 updateVolumeRowTintH(row, isActive); 816 } 817 } 818 } 819 820 protected void updateRingerH() { 821 if (mState != null) { 822 final StreamState ss = mState.states.get(AudioManager.STREAM_RING); 823 if (ss == null) { 824 return; 825 } 826 827 boolean isZenMuted = mState.zenMode == Global.ZEN_MODE_ALARMS 828 || mState.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS 829 || (mState.zenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS 830 && mState.disallowRinger); 831 enableRingerViewsH(!isZenMuted); 832 switch (mState.ringerModeInternal) { 833 case AudioManager.RINGER_MODE_VIBRATE: 834 mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_vibrate); 835 addAccessibilityDescription(mRingerIcon, RINGER_MODE_VIBRATE, 836 mContext.getString(R.string.volume_ringer_hint_mute)); 837 mRingerIcon.setTag(Events.ICON_STATE_VIBRATE); 838 break; 839 case AudioManager.RINGER_MODE_SILENT: 840 mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute); 841 mRingerIcon.setTag(Events.ICON_STATE_MUTE); 842 addAccessibilityDescription(mRingerIcon, RINGER_MODE_SILENT, 843 mContext.getString(R.string.volume_ringer_hint_unmute)); 844 break; 845 case AudioManager.RINGER_MODE_NORMAL: 846 default: 847 boolean muted = (mAutomute && ss.level == 0) || ss.muted; 848 if (!isZenMuted && muted) { 849 mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute); 850 addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL, 851 mContext.getString(R.string.volume_ringer_hint_unmute)); 852 mRingerIcon.setTag(Events.ICON_STATE_MUTE); 853 } else { 854 mRingerIcon.setImageResource(R.drawable.ic_volume_ringer); 855 if (mController.hasVibrator()) { 856 addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL, 857 mContext.getString(R.string.volume_ringer_hint_vibrate)); 858 } else { 859 addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL, 860 mContext.getString(R.string.volume_ringer_hint_mute)); 861 } 862 mRingerIcon.setTag(Events.ICON_STATE_UNMUTE); 863 } 864 break; 865 } 866 } 867 } 868 869 private void addAccessibilityDescription(View view, int currState, String hintLabel) { 870 int currStateResId; 871 switch (currState) { 872 case RINGER_MODE_SILENT: 873 currStateResId = R.string.volume_ringer_status_silent; 874 break; 875 case RINGER_MODE_VIBRATE: 876 currStateResId = R.string.volume_ringer_status_vibrate; 877 break; 878 case RINGER_MODE_NORMAL: 879 default: 880 currStateResId = R.string.volume_ringer_status_normal; 881 } 882 883 view.setContentDescription(mContext.getString(currStateResId)); 884 view.setAccessibilityDelegate(new AccessibilityDelegate() { 885 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { 886 super.onInitializeAccessibilityNodeInfo(host, info); 887 info.addAction(new AccessibilityNodeInfo.AccessibilityAction( 888 AccessibilityNodeInfo.ACTION_CLICK, hintLabel)); 889 } 890 }); 891 } 892 893 /** 894 * Toggles enable state of views in a VolumeRow (not including seekbar or icon) 895 * Hides/shows zen icon 896 * @param enable whether to enable volume row views and hide dnd icon 897 */ 898 private void enableVolumeRowViewsH(VolumeRow row, boolean enable) { 899 boolean showDndIcon = !enable; 900 row.dndIcon.setVisibility(showDndIcon ? VISIBLE : GONE); 901 } 902 903 /** 904 * Toggles enable state of footer/ringer views 905 * Hides/shows zen icon 906 * @param enable whether to enable ringer views and hide dnd icon 907 */ 908 private void enableRingerViewsH(boolean enable) { 909 if (mRingerIcon != null) { 910 mRingerIcon.setEnabled(enable); 911 } 912 if (mZenIcon != null) { 913 mZenIcon.setVisibility(enable ? GONE : VISIBLE); 914 } 915 } 916 917 private void trimObsoleteH() { 918 if (D.BUG) Log.d(TAG, "trimObsoleteH"); 919 for (int i = mRows.size() - 1; i >= 0; i--) { 920 final VolumeRow row = mRows.get(i); 921 if (row.ss == null || !row.ss.dynamic) continue; 922 if (!mDynamic.get(row.stream)) { 923 mRows.remove(i); 924 mDialogRowsView.removeView(row.view); 925 } 926 } 927 } 928 929 protected void onStateChangedH(State state) { 930 if (D.BUG) Log.d(TAG, "onStateChangedH() state: " + state.toString()); 931 if (mState != null && state != null 932 && mState.ringerModeInternal != state.ringerModeInternal 933 && state.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) { 934 mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK)); 935 } 936 937 mState = state; 938 mDynamic.clear(); 939 // add any new dynamic rows 940 for (int i = 0; i < state.states.size(); i++) { 941 final int stream = state.states.keyAt(i); 942 final StreamState ss = state.states.valueAt(i); 943 if (!ss.dynamic) continue; 944 mDynamic.put(stream, true); 945 if (findRow(stream) == null) { 946 addRow(stream, R.drawable.ic_volume_remote, R.drawable.ic_volume_remote_mute, true, 947 false, true); 948 } 949 } 950 951 if (mActiveStream != state.activeStream) { 952 mPrevActiveStream = mActiveStream; 953 mActiveStream = state.activeStream; 954 VolumeRow activeRow = getActiveRow(); 955 updateRowsH(activeRow); 956 if (mShowing) rescheduleTimeoutH(); 957 } 958 for (VolumeRow row : mRows) { 959 updateVolumeRowH(row); 960 } 961 updateRingerH(); 962 mWindow.setTitle(composeWindowTitle()); 963 } 964 965 CharSequence composeWindowTitle() { 966 return mContext.getString(R.string.volume_dialog_title, getStreamLabelH(getActiveRow().ss)); 967 } 968 969 private void updateVolumeRowH(VolumeRow row) { 970 if (D.BUG) Log.i(TAG, "updateVolumeRowH s=" + row.stream); 971 if (mState == null) return; 972 final StreamState ss = mState.states.get(row.stream); 973 if (ss == null) return; 974 row.ss = ss; 975 if (ss.level > 0) { 976 row.lastAudibleLevel = ss.level; 977 } 978 if (ss.level == row.requestedLevel) { 979 row.requestedLevel = -1; 980 } 981 final boolean isA11yStream = row.stream == STREAM_ACCESSIBILITY; 982 final boolean isRingStream = row.stream == AudioManager.STREAM_RING; 983 final boolean isSystemStream = row.stream == AudioManager.STREAM_SYSTEM; 984 final boolean isAlarmStream = row.stream == STREAM_ALARM; 985 final boolean isMusicStream = row.stream == AudioManager.STREAM_MUSIC; 986 final boolean isRingVibrate = isRingStream 987 && mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE; 988 final boolean isRingSilent = isRingStream 989 && mState.ringerModeInternal == AudioManager.RINGER_MODE_SILENT; 990 final boolean isZenPriorityOnly = mState.zenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; 991 final boolean isZenAlarms = mState.zenMode == Global.ZEN_MODE_ALARMS; 992 final boolean isZenNone = mState.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS; 993 final boolean zenMuted = isZenAlarms ? (isRingStream || isSystemStream) 994 : isZenNone ? (isRingStream || isSystemStream || isAlarmStream || isMusicStream) 995 : isZenPriorityOnly ? ((isAlarmStream && mState.disallowAlarms) || 996 (isMusicStream && mState.disallowMedia) || 997 (isRingStream && mState.disallowRinger) || 998 (isSystemStream && mState.disallowSystem)) 999 : false; 1000 1001 // update slider max 1002 final int max = ss.levelMax * 100; 1003 if (max != row.slider.getMax()) { 1004 row.slider.setMax(max); 1005 } 1006 // update slider min 1007 final int min = ss.levelMin * 100; 1008 if (min != row.slider.getMin()) { 1009 row.slider.setMin(min); 1010 } 1011 1012 // update header text 1013 Util.setText(row.header, getStreamLabelH(ss)); 1014 row.slider.setContentDescription(row.header.getText()); 1015 mConfigurableTexts.add(row.header, ss.name); 1016 1017 // update icon 1018 final boolean iconEnabled = (mAutomute || ss.muteSupported) && !zenMuted; 1019 row.icon.setEnabled(iconEnabled); 1020 row.icon.setAlpha(iconEnabled ? 1 : 0.5f); 1021 final int iconRes = 1022 isRingVibrate ? R.drawable.ic_volume_ringer_vibrate 1023 : isRingSilent || zenMuted ? row.iconMuteRes 1024 : ss.routedToBluetooth ? 1025 (ss.muted ? R.drawable.ic_volume_media_bt_mute 1026 : R.drawable.ic_volume_media_bt) 1027 : mAutomute && ss.level == 0 ? row.iconMuteRes 1028 : (ss.muted ? row.iconMuteRes : row.iconRes); 1029 row.icon.setImageResource(iconRes); 1030 row.iconState = 1031 iconRes == R.drawable.ic_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE 1032 : (iconRes == R.drawable.ic_volume_media_bt_mute || iconRes == row.iconMuteRes) 1033 ? Events.ICON_STATE_MUTE 1034 : (iconRes == R.drawable.ic_volume_media_bt || iconRes == row.iconRes) 1035 ? Events.ICON_STATE_UNMUTE 1036 : Events.ICON_STATE_UNKNOWN; 1037 if (iconEnabled) { 1038 if (isRingStream) { 1039 if (isRingVibrate) { 1040 row.icon.setContentDescription(mContext.getString( 1041 R.string.volume_stream_content_description_unmute, 1042 getStreamLabelH(ss))); 1043 } else { 1044 if (mController.hasVibrator()) { 1045 row.icon.setContentDescription(mContext.getString( 1046 mShowA11yStream 1047 ? R.string.volume_stream_content_description_vibrate_a11y 1048 : R.string.volume_stream_content_description_vibrate, 1049 getStreamLabelH(ss))); 1050 } else { 1051 row.icon.setContentDescription(mContext.getString( 1052 mShowA11yStream 1053 ? R.string.volume_stream_content_description_mute_a11y 1054 : R.string.volume_stream_content_description_mute, 1055 getStreamLabelH(ss))); 1056 } 1057 } 1058 } else if (isA11yStream) { 1059 row.icon.setContentDescription(getStreamLabelH(ss)); 1060 } else { 1061 if (ss.muted || mAutomute && ss.level == 0) { 1062 row.icon.setContentDescription(mContext.getString( 1063 R.string.volume_stream_content_description_unmute, 1064 getStreamLabelH(ss))); 1065 } else { 1066 row.icon.setContentDescription(mContext.getString( 1067 mShowA11yStream 1068 ? R.string.volume_stream_content_description_mute_a11y 1069 : R.string.volume_stream_content_description_mute, 1070 getStreamLabelH(ss))); 1071 } 1072 } 1073 } else { 1074 row.icon.setContentDescription(getStreamLabelH(ss)); 1075 } 1076 1077 // ensure tracking is disabled if zenMuted 1078 if (zenMuted) { 1079 row.tracking = false; 1080 } 1081 enableVolumeRowViewsH(row, !zenMuted); 1082 1083 // update slider 1084 final boolean enableSlider = !zenMuted; 1085 final int vlevel = row.ss.muted && (!isRingStream && !zenMuted) ? 0 1086 : row.ss.level; 1087 updateVolumeRowSliderH(row, enableSlider, vlevel); 1088 } 1089 1090 private void updateVolumeRowTintH(VolumeRow row, boolean isActive) { 1091 if (isActive) { 1092 row.slider.requestFocus(); 1093 } 1094 boolean useActiveColoring = isActive && row.slider.isEnabled(); 1095 final ColorStateList tint = useActiveColoring 1096 ? Utils.getColorAccent(mContext) 1097 : Utils.getColorAttr(mContext, android.R.attr.colorForeground); 1098 final int alpha = useActiveColoring 1099 ? Color.alpha(tint.getDefaultColor()) 1100 : getAlphaAttr(android.R.attr.secondaryContentAlpha); 1101 if (tint == row.cachedTint) return; 1102 row.slider.setProgressTintList(tint); 1103 row.slider.setThumbTintList(tint); 1104 row.slider.setProgressBackgroundTintList(tint); 1105 row.slider.setAlpha(((float) alpha) / 255); 1106 row.icon.setImageTintList(tint); 1107 row.icon.setImageAlpha(alpha); 1108 row.cachedTint = tint; 1109 } 1110 1111 private void updateVolumeRowSliderH(VolumeRow row, boolean enable, int vlevel) { 1112 row.slider.setEnabled(enable); 1113 updateVolumeRowTintH(row, row.stream == mActiveStream); 1114 if (row.tracking) { 1115 return; // don't update if user is sliding 1116 } 1117 final int progress = row.slider.getProgress(); 1118 final int level = getImpliedLevel(row.slider, progress); 1119 final boolean rowVisible = row.view.getVisibility() == VISIBLE; 1120 final boolean inGracePeriod = (SystemClock.uptimeMillis() - row.userAttempt) 1121 < USER_ATTEMPT_GRACE_PERIOD; 1122 mHandler.removeMessages(H.RECHECK, row); 1123 if (mShowing && rowVisible && inGracePeriod) { 1124 if (D.BUG) Log.d(TAG, "inGracePeriod"); 1125 mHandler.sendMessageAtTime(mHandler.obtainMessage(H.RECHECK, row), 1126 row.userAttempt + USER_ATTEMPT_GRACE_PERIOD); 1127 return; // don't update if visible and in grace period 1128 } 1129 if (vlevel == level) { 1130 if (mShowing && rowVisible) { 1131 return; // don't clamp if visible 1132 } 1133 } 1134 final int newProgress = vlevel * 100; 1135 if (progress != newProgress) { 1136 if (mShowing && rowVisible) { 1137 // animate! 1138 if (row.anim != null && row.anim.isRunning() 1139 && row.animTargetProgress == newProgress) { 1140 return; // already animating to the target progress 1141 } 1142 // start/update animation 1143 if (row.anim == null) { 1144 row.anim = ObjectAnimator.ofInt(row.slider, "progress", progress, newProgress); 1145 row.anim.setInterpolator(new DecelerateInterpolator()); 1146 } else { 1147 row.anim.cancel(); 1148 row.anim.setIntValues(progress, newProgress); 1149 } 1150 row.animTargetProgress = newProgress; 1151 row.anim.setDuration(UPDATE_ANIMATION_DURATION); 1152 row.anim.start(); 1153 } else { 1154 // update slider directly to clamped value 1155 if (row.anim != null) { 1156 row.anim.cancel(); 1157 } 1158 row.slider.setProgress(newProgress, true); 1159 } 1160 } 1161 } 1162 1163 private void recheckH(VolumeRow row) { 1164 if (row == null) { 1165 if (D.BUG) Log.d(TAG, "recheckH ALL"); 1166 trimObsoleteH(); 1167 for (VolumeRow r : mRows) { 1168 updateVolumeRowH(r); 1169 } 1170 } else { 1171 if (D.BUG) Log.d(TAG, "recheckH " + row.stream); 1172 updateVolumeRowH(row); 1173 } 1174 } 1175 1176 private void setStreamImportantH(int stream, boolean important) { 1177 for (VolumeRow row : mRows) { 1178 if (row.stream == stream) { 1179 row.important = important; 1180 return; 1181 } 1182 } 1183 } 1184 1185 private void showSafetyWarningH(int flags) { 1186 if ((flags & (AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_SHOW_UI_WARNINGS)) != 0 1187 || mShowing) { 1188 synchronized (mSafetyWarningLock) { 1189 if (mSafetyWarning != null) { 1190 return; 1191 } 1192 mSafetyWarning = new SafetyWarningDialog(mContext, mController.getAudioManager()) { 1193 @Override 1194 protected void cleanUp() { 1195 synchronized (mSafetyWarningLock) { 1196 mSafetyWarning = null; 1197 } 1198 recheckH(null); 1199 } 1200 }; 1201 mSafetyWarning.show(); 1202 } 1203 recheckH(null); 1204 } 1205 rescheduleTimeoutH(); 1206 } 1207 1208 private String getStreamLabelH(StreamState ss) { 1209 if (ss == null) { 1210 return ""; 1211 } 1212 if (ss.remoteLabel != null) { 1213 return ss.remoteLabel; 1214 } 1215 try { 1216 return mContext.getResources().getString(ss.name); 1217 } catch (Resources.NotFoundException e) { 1218 Slog.e(TAG, "Can't find translation for stream " + ss); 1219 return ""; 1220 } 1221 } 1222 1223 private Runnable getSinglePressFor(ImageButton button) { 1224 return () -> { 1225 if (button != null) { 1226 button.setPressed(true); 1227 button.postOnAnimationDelayed(getSingleUnpressFor(button), 200); 1228 } 1229 }; 1230 } 1231 1232 private Runnable getSingleUnpressFor(ImageButton button) { 1233 return () -> { 1234 if (button != null) { 1235 button.setPressed(false); 1236 } 1237 }; 1238 } 1239 1240 private final VolumeDialogController.Callbacks mControllerCallbackH 1241 = new VolumeDialogController.Callbacks() { 1242 @Override 1243 public void onShowRequested(int reason) { 1244 showH(reason); 1245 } 1246 1247 @Override 1248 public void onDismissRequested(int reason) { 1249 dismissH(reason); 1250 } 1251 1252 @Override 1253 public void onScreenOff() { 1254 dismissH(Events.DISMISS_REASON_SCREEN_OFF); 1255 } 1256 1257 @Override 1258 public void onStateChanged(State state) { 1259 onStateChangedH(state); 1260 } 1261 1262 @Override 1263 public void onLayoutDirectionChanged(int layoutDirection) { 1264 mDialogView.setLayoutDirection(layoutDirection); 1265 } 1266 1267 @Override 1268 public void onConfigurationChanged() { 1269 mDialog.dismiss(); 1270 mConfigChanged = true; 1271 } 1272 1273 @Override 1274 public void onShowVibrateHint() { 1275 if (mSilentMode) { 1276 mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false); 1277 } 1278 } 1279 1280 @Override 1281 public void onShowSilentHint() { 1282 if (mSilentMode) { 1283 mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false); 1284 } 1285 } 1286 1287 @Override 1288 public void onShowSafetyWarning(int flags) { 1289 showSafetyWarningH(flags); 1290 } 1291 1292 @Override 1293 public void onAccessibilityModeChanged(Boolean showA11yStream) { 1294 mShowA11yStream = showA11yStream == null ? false : showA11yStream; 1295 VolumeRow activeRow = getActiveRow(); 1296 if (!mShowA11yStream && STREAM_ACCESSIBILITY == activeRow.stream) { 1297 dismissH(Events.DISMISS_STREAM_GONE); 1298 } else { 1299 updateRowsH(activeRow); 1300 } 1301 1302 } 1303 1304 @Override 1305 public void onCaptionComponentStateChanged( 1306 Boolean isComponentEnabled, Boolean fromTooltip) { 1307 updateODICaptionsH(isComponentEnabled, fromTooltip); 1308 } 1309 }; 1310 1311 private final class H extends Handler { 1312 private static final int SHOW = 1; 1313 private static final int DISMISS = 2; 1314 private static final int RECHECK = 3; 1315 private static final int RECHECK_ALL = 4; 1316 private static final int SET_STREAM_IMPORTANT = 5; 1317 private static final int RESCHEDULE_TIMEOUT = 6; 1318 private static final int STATE_CHANGED = 7; 1319 1320 public H() { 1321 super(Looper.getMainLooper()); 1322 } 1323 1324 @Override 1325 public void handleMessage(Message msg) { 1326 switch (msg.what) { 1327 case SHOW: showH(msg.arg1); break; 1328 case DISMISS: dismissH(msg.arg1); break; 1329 case RECHECK: recheckH((VolumeRow) msg.obj); break; 1330 case RECHECK_ALL: recheckH(null); break; 1331 case SET_STREAM_IMPORTANT: setStreamImportantH(msg.arg1, msg.arg2 != 0); break; 1332 case RESCHEDULE_TIMEOUT: rescheduleTimeoutH(); break; 1333 case STATE_CHANGED: onStateChangedH(mState); break; 1334 } 1335 } 1336 } 1337 1338 private final class CustomDialog extends Dialog implements DialogInterface { 1339 public CustomDialog(Context context) { 1340 super(context, R.style.qs_theme); 1341 } 1342 1343 @Override 1344 public boolean dispatchTouchEvent(MotionEvent ev) { 1345 rescheduleTimeoutH(); 1346 return super.dispatchTouchEvent(ev); 1347 } 1348 1349 @Override 1350 protected void onStart() { 1351 super.setCanceledOnTouchOutside(true); 1352 super.onStart(); 1353 } 1354 1355 @Override 1356 protected void onStop() { 1357 super.onStop(); 1358 mHandler.sendEmptyMessage(H.RECHECK_ALL); 1359 } 1360 1361 @Override 1362 public boolean onTouchEvent(MotionEvent event) { 1363 if (mShowing) { 1364 if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { 1365 dismissH(Events.DISMISS_REASON_TOUCH_OUTSIDE); 1366 return true; 1367 } 1368 } 1369 return false; 1370 } 1371 } 1372 1373 private final class VolumeSeekBarChangeListener implements OnSeekBarChangeListener { 1374 private final VolumeRow mRow; 1375 1376 private VolumeSeekBarChangeListener(VolumeRow row) { 1377 mRow = row; 1378 } 1379 1380 @Override 1381 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 1382 if (mRow.ss == null) return; 1383 if (D.BUG) Log.d(TAG, AudioSystem.streamToString(mRow.stream) 1384 + " onProgressChanged " + progress + " fromUser=" + fromUser); 1385 if (!fromUser) return; 1386 if (mRow.ss.levelMin > 0) { 1387 final int minProgress = mRow.ss.levelMin * 100; 1388 if (progress < minProgress) { 1389 seekBar.setProgress(minProgress); 1390 progress = minProgress; 1391 } 1392 } 1393 final int userLevel = getImpliedLevel(seekBar, progress); 1394 if (mRow.ss.level != userLevel || mRow.ss.muted && userLevel > 0) { 1395 mRow.userAttempt = SystemClock.uptimeMillis(); 1396 if (mRow.requestedLevel != userLevel) { 1397 mController.setActiveStream(mRow.stream); 1398 mController.setStreamVolume(mRow.stream, userLevel); 1399 mRow.requestedLevel = userLevel; 1400 Events.writeEvent(mContext, Events.EVENT_TOUCH_LEVEL_CHANGED, mRow.stream, 1401 userLevel); 1402 } 1403 } 1404 } 1405 1406 @Override 1407 public void onStartTrackingTouch(SeekBar seekBar) { 1408 if (D.BUG) Log.d(TAG, "onStartTrackingTouch"+ " " + mRow.stream); 1409 mController.setActiveStream(mRow.stream); 1410 mRow.tracking = true; 1411 } 1412 1413 @Override 1414 public void onStopTrackingTouch(SeekBar seekBar) { 1415 if (D.BUG) Log.d(TAG, "onStopTrackingTouch"+ " " + mRow.stream); 1416 mRow.tracking = false; 1417 mRow.userAttempt = SystemClock.uptimeMillis(); 1418 final int userLevel = getImpliedLevel(seekBar, seekBar.getProgress()); 1419 Events.writeEvent(mContext, Events.EVENT_TOUCH_LEVEL_DONE, mRow.stream, userLevel); 1420 if (mRow.ss.level != userLevel) { 1421 mHandler.sendMessageDelayed(mHandler.obtainMessage(H.RECHECK, mRow), 1422 USER_ATTEMPT_GRACE_PERIOD); 1423 } 1424 } 1425 } 1426 1427 private final class Accessibility extends AccessibilityDelegate { 1428 public void init() { 1429 mDialogView.setAccessibilityDelegate(this); 1430 } 1431 1432 @Override 1433 public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) { 1434 // Activities populate their title here. Follow that example. 1435 event.getText().add(composeWindowTitle()); 1436 return true; 1437 } 1438 1439 @Override 1440 public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child, 1441 AccessibilityEvent event) { 1442 rescheduleTimeoutH(); 1443 return super.onRequestSendAccessibilityEvent(host, child, event); 1444 } 1445 } 1446 1447 private static class VolumeRow { 1448 private View view; 1449 private TextView header; 1450 private ImageButton icon; 1451 private SeekBar slider; 1452 private int stream; 1453 private StreamState ss; 1454 private long userAttempt; // last user-driven slider change 1455 private boolean tracking; // tracking slider touch 1456 private int requestedLevel = -1; // pending user-requested level via progress changed 1457 private int iconRes; 1458 private int iconMuteRes; 1459 private boolean important; 1460 private boolean defaultStream; 1461 private ColorStateList cachedTint; 1462 private int iconState; // from Events 1463 private ObjectAnimator anim; // slider progress animation for non-touch-related updates 1464 private int animTargetProgress; 1465 private int lastAudibleLevel = 1; 1466 private FrameLayout dndIcon; 1467 } 1468 } 1469