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