blob: 41b094a326825a76333d0022c1e8b85988cee1e8 [file] [log] [blame]
Lujiang Xuee53f7492018-03-01 09:53:28 -08001/*
2 * Copyright (C) 2018 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
17package com.android.systemui.volume;
18
19import android.animation.ObjectAnimator;
20import android.annotation.SuppressLint;
21import android.app.Dialog;
22import android.app.KeyguardManager;
23import android.content.Context;
24import android.content.DialogInterface;
25import android.content.res.ColorStateList;
26import android.content.res.Resources;
27import android.graphics.Color;
28import android.graphics.PixelFormat;
29import android.graphics.drawable.ColorDrawable;
30import android.media.AudioManager;
31import android.media.AudioSystem;
32import android.os.Debug;
33import android.os.Handler;
34import android.os.Looper;
35import android.os.Message;
36import android.os.SystemClock;
37import android.provider.Settings.Global;
38import android.util.Log;
39import android.util.Slog;
40import android.util.SparseBooleanArray;
41import android.view.ContextThemeWrapper;
42import android.view.Gravity;
43import android.view.MotionEvent;
44import android.view.View;
45import android.view.ViewGroup;
46import android.view.Window;
47import android.view.WindowManager;
48import android.view.animation.DecelerateInterpolator;
49import android.widget.ImageButton;
50import android.widget.SeekBar;
51import android.widget.SeekBar.OnSeekBarChangeListener;
52
53import java.io.PrintWriter;
54import java.util.ArrayList;
55import java.util.List;
56
57import com.android.settingslib.Utils;
58import com.android.systemui.Dependency;
59import com.android.systemui.R;
60import com.android.systemui.plugins.VolumeDialog;
61import com.android.systemui.plugins.VolumeDialogController;
62import com.android.systemui.plugins.VolumeDialogController.State;
63import com.android.systemui.plugins.VolumeDialogController.StreamState;
64
65/**
66 * Car version of the volume dialog.
67 *
68 * A client of VolumeDialogControllerImpl and its state model.
69 *
70 * Methods ending in "H" must be called on the (ui) handler.
71 */
72public class CarVolumeDialogImpl implements VolumeDialog {
73 private static final String TAG = Util.logTag(CarVolumeDialogImpl.class);
74
75 private static final long USER_ATTEMPT_GRACE_PERIOD = 1000;
76 private static final int UPDATE_ANIMATION_DURATION = 80;
77
78 private final Context mContext;
79 private final H mHandler = new H();
80 private final VolumeDialogController mController;
81
82 private Window mWindow;
83 private CustomDialog mDialog;
84 private ViewGroup mDialogView;
85 private ViewGroup mDialogRowsView;
86 private final List<VolumeRow> mRows = new ArrayList<>();
87 private ConfigurableTexts mConfigurableTexts;
88 private final SparseBooleanArray mDynamic = new SparseBooleanArray();
89 private final KeyguardManager mKeyguard;
90 private final Object mSafetyWarningLock = new Object();
91 private final ColorStateList mActiveSliderTint;
92 private final ColorStateList mInactiveSliderTint;
93
94 private boolean mShowing;
95
96 private int mActiveStream;
97 private int mPrevActiveStream;
98 private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE;
99 private boolean mSilentMode = VolumePrefs.DEFAULT_ENABLE_SILENT_MODE;
100 private State mState;
101 private SafetyWarningDialog mSafetyWarning;
102 private boolean mHovering = false;
103 private boolean mExpanded = false;
104 private View mExpandBtn;
105
106 public CarVolumeDialogImpl(Context context) {
107 mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
108 mController = Dependency.get(VolumeDialogController.class);
109 mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
110 mActiveSliderTint = ColorStateList.valueOf(Utils.getColorAccent(mContext));
111 mInactiveSliderTint = loadColorStateList(R.color.volume_slider_inactive);
112 }
113
114 public void init(int windowType, Callback callback) {
115 initDialog();
116
117 mController.addCallback(mControllerCallbackH, mHandler);
118 mController.getState();
119 }
120
121 @Override
122 public void destroy() {
123 mController.removeCallback(mControllerCallbackH);
124 mHandler.removeCallbacksAndMessages(null);
125 }
126
127 private void initDialog() {
128 mDialog = new CustomDialog(mContext);
129
130 mConfigurableTexts = new ConfigurableTexts(mContext);
131 mHovering = false;
132 mShowing = false;
133 mWindow = mDialog.getWindow();
134 mWindow.requestFeature(Window.FEATURE_NO_TITLE);
135 mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
136 mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND
137 | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
138 mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
139 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
140 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
141 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
142 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
143 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
144 mWindow.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
145 mWindow.setWindowAnimations(com.android.internal.R.style.Animation_Toast);
146 final WindowManager.LayoutParams lp = mWindow.getAttributes();
147 lp.format = PixelFormat.TRANSLUCENT;
148 lp.setTitle(VolumeDialogImpl.class.getSimpleName());
149 lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
150 lp.windowAnimations = -1;
151 mWindow.setAttributes(lp);
152 mWindow.setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
153
154 mDialog.setCanceledOnTouchOutside(true);
155 mDialog.setContentView(R.layout.car_volume_dialog);
156 mDialog.setOnShowListener(dialog -> {
157 mDialogView.setTranslationY(-mDialogView.getHeight());
158 mDialogView.setAlpha(0);
159 mDialogView.animate()
160 .alpha(1)
161 .translationY(0)
162 .setDuration(300)
163 .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator())
164 .start();
165 });
166 mExpandBtn = mDialog.findViewById(R.id.expand);
167 mExpandBtn.setOnClickListener(v -> {
168 mExpanded = !mExpanded;
169 updateRowsH(getActiveRow());
170 });
171 mDialogView = mDialog.findViewById(R.id.volume_dialog);
172 mDialogView.setOnHoverListener((v, event) -> {
173 int action = event.getActionMasked();
174 mHovering = (action == MotionEvent.ACTION_HOVER_ENTER)
175 || (action == MotionEvent.ACTION_HOVER_MOVE);
176 rescheduleTimeoutH();
177 return true;
178 });
179
180 mDialogRowsView = mDialog.findViewById(R.id.car_volume_dialog_rows);
181
182 if (mRows.isEmpty()) {
183 addRow(AudioManager.STREAM_MUSIC,
184 R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true, true);
185 addRow(AudioManager.STREAM_RING,
186 R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true, false);
187 addRow(AudioManager.STREAM_ALARM,
188 R.drawable.ic_volume_alarm, R.drawable.ic_volume_alarm_mute, true, false);
189 } else {
190 addExistingRows();
191 }
192
193 updateRowsH(getActiveRow());
194 }
195
196 private ColorStateList loadColorStateList(int colorResId) {
197 return ColorStateList.valueOf(mContext.getColor(colorResId));
198 }
199
200 public void setStreamImportant(int stream, boolean important) {
201 mHandler.obtainMessage(H.SET_STREAM_IMPORTANT, stream, important ? 1 : 0).sendToTarget();
202 }
203
204 public void setAutomute(boolean automute) {
205 if (mAutomute == automute) return;
206 mAutomute = automute;
207 mHandler.sendEmptyMessage(H.RECHECK_ALL);
208 }
209
210 public void setSilentMode(boolean silentMode) {
211 if (mSilentMode == silentMode) return;
212 mSilentMode = silentMode;
213 mHandler.sendEmptyMessage(H.RECHECK_ALL);
214 }
215
216 private void addRow(int stream, int iconRes, int iconMuteRes, boolean important,
217 boolean defaultStream) {
218 addRow(stream, iconRes, iconMuteRes, important, defaultStream, false);
219 }
220
221 private void addRow(int stream, int iconRes, int iconMuteRes, boolean important,
222 boolean defaultStream, boolean dynamic) {
223 if (D.BUG) Slog.d(TAG, "Adding row for stream " + stream);
224 VolumeRow row = new VolumeRow();
225 initRow(row, stream, iconRes, iconMuteRes, important, defaultStream);
226 mDialogRowsView.addView(row.view);
227 mRows.add(row);
228 }
229
230 private void addExistingRows() {
231 int N = mRows.size();
232 for (int i = 0; i < N; i++) {
233 final VolumeRow row = mRows.get(i);
234 initRow(row, row.stream, row.iconRes, row.iconMuteRes, row.important,
235 row.defaultStream);
236 mDialogRowsView.addView(row.view);
237 updateVolumeRowH(row);
238 }
239 }
240
241 private VolumeRow getActiveRow() {
242 for (VolumeRow row : mRows) {
243 if (row.stream == mActiveStream) {
244 return row;
245 }
246 }
247 return mRows.get(0);
248 }
249
250 private VolumeRow findRow(int stream) {
251 for (VolumeRow row : mRows) {
252 if (row.stream == stream) return row;
253 }
254 return null;
255 }
256
257 public void dump(PrintWriter writer) {
258 writer.println(VolumeDialogImpl.class.getSimpleName() + " state:");
259 writer.print(" mShowing: "); writer.println(mShowing);
260 writer.print(" mActiveStream: "); writer.println(mActiveStream);
261 writer.print(" mDynamic: "); writer.println(mDynamic);
262 writer.print(" mAutomute: "); writer.println(mAutomute);
263 writer.print(" mSilentMode: "); writer.println(mSilentMode);
264 }
265
266 private static int getImpliedLevel(SeekBar seekBar, int progress) {
267 final int m = seekBar.getMax();
268 final int n = m / 100 - 1;
269 final int level = progress == 0 ? 0
270 : progress == m ? (m / 100) : (1 + (int)((progress / (float) m) * n));
271 return level;
272 }
273
274 @SuppressLint("InflateParams")
275 private void initRow(final VolumeRow row, final int stream, int iconRes, int iconMuteRes,
276 boolean important, boolean defaultStream) {
277 row.stream = stream;
278 row.iconRes = iconRes;
279 row.iconMuteRes = iconMuteRes;
280 row.important = important;
281 row.defaultStream = defaultStream;
282 row.view = mDialog.getLayoutInflater().inflate(R.layout.car_volume_dialog_row, null);
283 row.view.setId(row.stream);
284 row.view.setTag(row);
285 row.slider = row.view.findViewById(R.id.volume_row_slider);
286 row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
287 row.anim = null;
288
289 row.icon = row.view.findViewById(R.id.volume_row_icon);
290 row.icon.setImageResource(iconRes);
291 }
292
293 public void show(int reason) {
294 mHandler.obtainMessage(H.SHOW, reason, 0).sendToTarget();
295 }
296
297 public void dismiss(int reason) {
298 mHandler.obtainMessage(H.DISMISS, reason, 0).sendToTarget();
299 }
300
301 private void showH(int reason) {
302 if (D.BUG) Log.d(TAG, "showH r=" + Events.DISMISS_REASONS[reason]);
303 mHandler.removeMessages(H.SHOW);
304 mHandler.removeMessages(H.DISMISS);
305 rescheduleTimeoutH();
306 if (mShowing) return;
307 mShowing = true;
308
309 mDialog.show();
310 Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
311 mController.notifyVisible(true);
312 }
313
314 protected void rescheduleTimeoutH() {
315 mHandler.removeMessages(H.DISMISS);
316 final int timeout = computeTimeoutH();
317 mHandler.sendMessageDelayed(mHandler
318 .obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT, 0), timeout);
319 if (D.BUG) Log.d(TAG, "rescheduleTimeout " + timeout + " " + Debug.getCaller());
320 mController.userActivity();
321 }
322
323 private int computeTimeoutH() {
324 if (mHovering) return 16000;
325 if (mSafetyWarning != null) return 5000;
326 return 3000;
327 }
328
329 protected void dismissH(int reason) {
330 mHandler.removeMessages(H.DISMISS);
331 mHandler.removeMessages(H.SHOW);
332 if (!mShowing) return;
333 mDialogView.animate().cancel();
334 mShowing = false;
335
336 mDialogView.setTranslationY(0);
337 mDialogView.setAlpha(1);
338 mDialogView.animate()
339 .alpha(0)
340 .translationY(-mDialogView.getHeight())
341 .setDuration(250)
342 .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
343 .withEndAction(() -> mHandler.postDelayed(() -> {
344 if (D.BUG) Log.d(TAG, "mDialog.dismiss()");
345 mDialog.dismiss();
346 }, 50))
347 .start();
348
349 Events.writeEvent(mContext, Events.EVENT_DISMISS_DIALOG, reason);
350 mController.notifyVisible(false);
351 synchronized (mSafetyWarningLock) {
352 if (mSafetyWarning != null) {
353 if (D.BUG) Log.d(TAG, "SafetyWarning dismissed");
354 mSafetyWarning.dismiss();
355 }
356 }
357 }
358
359 private boolean shouldBeVisibleH(VolumeRow row) {
360 if (mExpanded) {
361 return true;
362 }
363 return row.defaultStream;
364 }
365
366 private void updateRowsH(final VolumeRow activeRow) {
367 if (D.BUG) Log.d(TAG, "updateRowsH");
368 if (!mShowing) {
369 trimObsoleteH();
370 }
371 // apply changes to all rows
372 for (final VolumeRow row : mRows) {
373 final boolean isActive = row == activeRow;
374 final boolean shouldBeVisible = shouldBeVisibleH(row);
375 Util.setVisOrGone(row.view, shouldBeVisible);
376 if (row.view.isShown()) {
377 updateVolumeRowSliderTintH(row, isActive);
378 }
379 }
380 }
381
382 private void trimObsoleteH() {
383 if (D.BUG) Log.d(TAG, "trimObsoleteH");
384 for (int i = mRows.size() - 1; i >= 0; i--) {
385 final VolumeRow row = mRows.get(i);
386 if (row.ss == null || !row.ss.dynamic) continue;
387 if (!mDynamic.get(row.stream)) {
388 mRows.remove(i);
389 mDialogRowsView.removeView(row.view);
390 }
391 }
392 }
393
394 protected void onStateChangedH(State state) {
395 mState = state;
396 mDynamic.clear();
397 // add any new dynamic rows
398 for (int i = 0; i < state.states.size(); i++) {
399 final int stream = state.states.keyAt(i);
400 final StreamState ss = state.states.valueAt(i);
401 if (!ss.dynamic) continue;
402 mDynamic.put(stream, true);
403 if (findRow(stream) == null) {
404 addRow(stream, R.drawable.ic_volume_remote, R.drawable.ic_volume_remote_mute, true,
405 false, true);
406 }
407 }
408
409 if (mActiveStream != state.activeStream) {
410 mPrevActiveStream = mActiveStream;
411 mActiveStream = state.activeStream;
412 updateRowsH(getActiveRow());
413 rescheduleTimeoutH();
414 }
415 for (VolumeRow row : mRows) {
416 updateVolumeRowH(row);
417 }
418
419 }
420
421 private void updateVolumeRowH(VolumeRow row) {
422 if (D.BUG) Log.d(TAG, "updateVolumeRowH s=" + row.stream);
423 if (mState == null) return;
424 final StreamState ss = mState.states.get(row.stream);
425 if (ss == null) return;
426 row.ss = ss;
427 if (ss.level > 0) {
428 row.lastAudibleLevel = ss.level;
429 }
430 if (ss.level == row.requestedLevel) {
431 row.requestedLevel = -1;
432 }
433 final boolean isRingStream = row.stream == AudioManager.STREAM_RING;
434 final boolean isSystemStream = row.stream == AudioManager.STREAM_SYSTEM;
435 final boolean isAlarmStream = row.stream == AudioManager.STREAM_ALARM;
436 final boolean isMusicStream = row.stream == AudioManager.STREAM_MUSIC;
437 final boolean isRingVibrate = isRingStream
438 && mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE;
439 final boolean isRingSilent = isRingStream
440 && mState.ringerModeInternal == AudioManager.RINGER_MODE_SILENT;
441 final boolean isZenPriorityOnly = mState.zenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
442 final boolean isZenAlarms = mState.zenMode == Global.ZEN_MODE_ALARMS;
443 final boolean isZenNone = mState.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
444 final boolean zenMuted = isZenAlarms ? (isRingStream || isSystemStream)
445 : isZenNone ? (isRingStream || isSystemStream || isAlarmStream || isMusicStream)
446 : isZenPriorityOnly ? ((isAlarmStream && mState.disallowAlarms) ||
447 (isMusicStream && mState.disallowMedia) ||
448 (isRingStream && mState.disallowRinger) ||
449 (isSystemStream && mState.disallowSystem))
450 : false;
451
452 // update slider max
453 final int max = ss.levelMax * 100;
454 if (max != row.slider.getMax()) {
455 row.slider.setMax(max);
456 }
457
458 // update icon
459 final boolean iconEnabled = (mAutomute || ss.muteSupported) && !zenMuted;
460 row.icon.setEnabled(iconEnabled);
461 row.icon.setAlpha(iconEnabled ? 1 : 0.5f);
462 final int iconRes =
463 isRingVibrate ? R.drawable.ic_volume_ringer_vibrate
464 : isRingSilent || zenMuted ? row.iconMuteRes
465 : ss.routedToBluetooth ?
466 (ss.muted ? R.drawable.ic_volume_media_bt_mute
467 : R.drawable.ic_volume_media_bt)
468 : mAutomute && ss.level == 0 ? row.iconMuteRes
469 : (ss.muted ? row.iconMuteRes : row.iconRes);
470 row.icon.setImageResource(iconRes);
471 row.iconState =
472 iconRes == R.drawable.ic_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE
473 : (iconRes == R.drawable.ic_volume_media_bt_mute || iconRes == row.iconMuteRes)
474 ? Events.ICON_STATE_MUTE
475 : (iconRes == R.drawable.ic_volume_media_bt || iconRes == row.iconRes)
476 ? Events.ICON_STATE_UNMUTE
477 : Events.ICON_STATE_UNKNOWN;
478 if (iconEnabled) {
479 if (isRingStream) {
480 if (isRingVibrate) {
481 row.icon.setContentDescription(mContext.getString(
482 R.string.volume_stream_content_description_unmute,
483 getStreamLabelH(ss)));
484 } else {
485 if (mController.hasVibrator()) {
486 row.icon.setContentDescription(mContext.getString(
487 R.string.volume_stream_content_description_vibrate,
488 getStreamLabelH(ss)));
489 } else {
490 row.icon.setContentDescription(mContext.getString(
491 R.string.volume_stream_content_description_mute,
492 getStreamLabelH(ss)));
493 }
494 }
495 } else {
496 if (ss.muted || mAutomute && ss.level == 0) {
497 row.icon.setContentDescription(mContext.getString(
498 R.string.volume_stream_content_description_unmute,
499 getStreamLabelH(ss)));
500 } else {
501 row.icon.setContentDescription(mContext.getString(
502 R.string.volume_stream_content_description_mute,
503 getStreamLabelH(ss)));
504 }
505 }
506 } else {
507 row.icon.setContentDescription(getStreamLabelH(ss));
508 }
509
510 // ensure tracking is disabled if zenMuted
511 if (zenMuted) {
512 row.tracking = false;
513 }
514
515 // update slider
516 final boolean enableSlider = !zenMuted;
517 final int vlevel = row.ss.muted && (!isRingStream && !zenMuted) ? 0
518 : row.ss.level;
519 updateVolumeRowSliderH(row, enableSlider, vlevel);
520 }
521
522 private String getStreamLabelH(StreamState ss) {
523 if (ss.remoteLabel != null) {
524 return ss.remoteLabel;
525 }
526 try {
527 return mContext.getResources().getString(ss.name);
528 } catch (Resources.NotFoundException e) {
529 Slog.e(TAG, "Can't find translation for stream " + ss);
530 return "";
531 }
532 }
533
534 private void updateVolumeRowSliderTintH(VolumeRow row, boolean isActive) {
535 if (isActive) {
536 row.slider.requestFocus();
537 }
538 final ColorStateList tint = isActive && row.slider.isEnabled() ? mActiveSliderTint
539 : mInactiveSliderTint;
540 if (tint == row.cachedSliderTint) return;
541 row.cachedSliderTint = tint;
542 row.slider.setProgressTintList(tint);
543 row.slider.setThumbTintList(tint);
544 }
545
546 private void updateVolumeRowSliderH(VolumeRow row, boolean enable, int vlevel) {
547 row.slider.setEnabled(enable);
548 updateVolumeRowSliderTintH(row, row.stream == mActiveStream);
549 if (row.tracking) {
550 return; // don't update if user is sliding
551 }
552 final int progress = row.slider.getProgress();
553 final int level = getImpliedLevel(row.slider, progress);
554 final boolean rowVisible = row.view.getVisibility() == View.VISIBLE;
555 final boolean inGracePeriod = (SystemClock.uptimeMillis() - row.userAttempt)
556 < USER_ATTEMPT_GRACE_PERIOD;
557 mHandler.removeMessages(H.RECHECK, row);
558 if (mShowing && rowVisible && inGracePeriod) {
559 if (D.BUG) Log.d(TAG, "inGracePeriod");
560 mHandler.sendMessageAtTime(mHandler.obtainMessage(H.RECHECK, row),
561 row.userAttempt + USER_ATTEMPT_GRACE_PERIOD);
562 return; // don't update if visible and in grace period
563 }
564 if (vlevel == level) {
565 if (mShowing && rowVisible) {
566 return; // don't clamp if visible
567 }
568 }
569 final int newProgress = vlevel * 100;
570 if (progress != newProgress) {
571 if (mShowing && rowVisible) {
572 // animate!
573 if (row.anim != null && row.anim.isRunning()
574 && row.animTargetProgress == newProgress) {
575 return; // already animating to the target progress
576 }
577 // start/update animation
578 if (row.anim == null) {
579 row.anim = ObjectAnimator.ofInt(row.slider, "progress", progress, newProgress);
580 row.anim.setInterpolator(new DecelerateInterpolator());
581 } else {
582 row.anim.cancel();
583 row.anim.setIntValues(progress, newProgress);
584 }
585 row.animTargetProgress = newProgress;
586 row.anim.setDuration(UPDATE_ANIMATION_DURATION);
587 row.anim.start();
588 } else {
589 // update slider directly to clamped value
590 if (row.anim != null) {
591 row.anim.cancel();
592 }
593 row.slider.setProgress(newProgress, true);
594 }
595 }
596 }
597
598 private void recheckH(VolumeRow row) {
599 if (row == null) {
600 if (D.BUG) Log.d(TAG, "recheckH ALL");
601 trimObsoleteH();
602 for (VolumeRow r : mRows) {
603 updateVolumeRowH(r);
604 }
605 } else {
606 if (D.BUG) Log.d(TAG, "recheckH " + row.stream);
607 updateVolumeRowH(row);
608 }
609 }
610
611 private void setStreamImportantH(int stream, boolean important) {
612 for (VolumeRow row : mRows) {
613 if (row.stream == stream) {
614 row.important = important;
615 return;
616 }
617 }
618 }
619
620 private void showSafetyWarningH(int flags) {
621 if ((flags & (AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_SHOW_UI_WARNINGS)) != 0
622 || mShowing) {
623 synchronized (mSafetyWarningLock) {
624 if (mSafetyWarning != null) {
625 return;
626 }
627 mSafetyWarning = new SafetyWarningDialog(mContext, mController.getAudioManager()) {
628 @Override
629 protected void cleanUp() {
630 synchronized (mSafetyWarningLock) {
631 mSafetyWarning = null;
632 }
633 recheckH(null);
634 }
635 };
636 mSafetyWarning.show();
637 }
638 recheckH(null);
639 }
640 rescheduleTimeoutH();
641 }
642
643 private final VolumeDialogController.Callbacks mControllerCallbackH
644 = new VolumeDialogController.Callbacks() {
645 @Override
646 public void onShowRequested(int reason) {
647 showH(reason);
648 }
649
650 @Override
651 public void onDismissRequested(int reason) {
652 dismissH(reason);
653 }
654
655 @Override
656 public void onScreenOff() {
657 dismissH(Events.DISMISS_REASON_SCREEN_OFF);
658 }
659
660 @Override
661 public void onStateChanged(State state) {
662 onStateChangedH(state);
663 }
664
665 @Override
666 public void onLayoutDirectionChanged(int layoutDirection) {
667 mDialogView.setLayoutDirection(layoutDirection);
668 }
669
670 @Override
671 public void onConfigurationChanged() {
672 mDialog.dismiss();
673 initDialog();
674 mConfigurableTexts.update();
675 }
676
677 @Override
678 public void onShowVibrateHint() {
679 if (mSilentMode) {
680 mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false);
681 }
682 }
683
684 @Override
685 public void onShowSilentHint() {
686 if (mSilentMode) {
687 mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
688 }
689 }
690
691 @Override
692 public void onShowSafetyWarning(int flags) {
693 showSafetyWarningH(flags);
694 }
695
696 @Override
697 public void onAccessibilityModeChanged(Boolean showA11yStream) {
698 }
699 };
700
701 private final class H extends Handler {
702 private static final int SHOW = 1;
703 private static final int DISMISS = 2;
704 private static final int RECHECK = 3;
705 private static final int RECHECK_ALL = 4;
706 private static final int SET_STREAM_IMPORTANT = 5;
707 private static final int RESCHEDULE_TIMEOUT = 6;
708 private static final int STATE_CHANGED = 7;
709
710 public H() {
711 super(Looper.getMainLooper());
712 }
713
714 @Override
715 public void handleMessage(Message msg) {
716 switch (msg.what) {
717 case SHOW: showH(msg.arg1); break;
718 case DISMISS: dismissH(msg.arg1); break;
719 case RECHECK: recheckH((VolumeRow) msg.obj); break;
720 case RECHECK_ALL: recheckH(null); break;
721 case SET_STREAM_IMPORTANT: setStreamImportantH(msg.arg1, msg.arg2 != 0); break;
722 case RESCHEDULE_TIMEOUT: rescheduleTimeoutH(); break;
723 case STATE_CHANGED: onStateChangedH(mState); break;
724 }
725 }
726 }
727
728 private final class CustomDialog extends Dialog implements DialogInterface {
729 public CustomDialog(Context context) {
730 super(context, com.android.systemui.R.style.qs_theme);
731 }
732
733 @Override
734 public boolean dispatchTouchEvent(MotionEvent ev) {
735 rescheduleTimeoutH();
736 return super.dispatchTouchEvent(ev);
737 }
738
739 @Override
740 protected void onStart() {
741 super.setCanceledOnTouchOutside(true);
742 super.onStart();
743 }
744
745 @Override
746 protected void onStop() {
747 super.onStop();
748 mHandler.sendEmptyMessage(H.RECHECK_ALL);
749 }
750
751 @Override
752 public boolean onTouchEvent(MotionEvent event) {
753 if (isShowing()) {
754 if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
755 dismissH(Events.DISMISS_REASON_TOUCH_OUTSIDE);
756 return true;
757 }
758 }
759 return false;
760 }
761 }
762
763 private final class VolumeSeekBarChangeListener implements OnSeekBarChangeListener {
764 private final VolumeRow mRow;
765
766 private VolumeSeekBarChangeListener(VolumeRow row) {
767 mRow = row;
768 }
769
770 @Override
771 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
772 if (mRow.ss == null) return;
773 if (D.BUG) Log.d(TAG, AudioSystem.streamToString(mRow.stream)
774 + " onProgressChanged " + progress + " fromUser=" + fromUser);
775 if (!fromUser) return;
776 if (mRow.ss.levelMin > 0) {
777 final int minProgress = mRow.ss.levelMin * 100;
778 if (progress < minProgress) {
779 seekBar.setProgress(minProgress);
780 progress = minProgress;
781 }
782 }
783 final int userLevel = getImpliedLevel(seekBar, progress);
784 if (mRow.ss.level != userLevel || mRow.ss.muted && userLevel > 0) {
785 mRow.userAttempt = SystemClock.uptimeMillis();
786 if (mRow.requestedLevel != userLevel) {
787 mController.setStreamVolume(mRow.stream, userLevel);
788 mRow.requestedLevel = userLevel;
789 Events.writeEvent(mContext, Events.EVENT_TOUCH_LEVEL_CHANGED, mRow.stream,
790 userLevel);
791 }
792 }
793 }
794
795 @Override
796 public void onStartTrackingTouch(SeekBar seekBar) {
797 if (D.BUG) Log.d(TAG, "onStartTrackingTouch"+ " " + mRow.stream);
798 mController.setActiveStream(mRow.stream);
799 mRow.tracking = true;
800 }
801
802 @Override
803 public void onStopTrackingTouch(SeekBar seekBar) {
804 if (D.BUG) Log.d(TAG, "onStopTrackingTouch"+ " " + mRow.stream);
805 mRow.tracking = false;
806 mRow.userAttempt = SystemClock.uptimeMillis();
807 final int userLevel = getImpliedLevel(seekBar, seekBar.getProgress());
808 Events.writeEvent(mContext, Events.EVENT_TOUCH_LEVEL_DONE, mRow.stream, userLevel);
809 if (mRow.ss.level != userLevel) {
810 mHandler.sendMessageDelayed(mHandler.obtainMessage(H.RECHECK, mRow),
811 USER_ATTEMPT_GRACE_PERIOD);
812 }
813 }
814 }
815
816 private static class VolumeRow {
817 private View view;
818 private ImageButton icon;
819 private SeekBar slider;
820 private int stream;
821 private StreamState ss;
822 private long userAttempt; // last user-driven slider change
823 private boolean tracking; // tracking slider touch
824 private int requestedLevel = -1; // pending user-requested level via progress changed
825 private int iconRes;
826 private int iconMuteRes;
827 private boolean important;
828 private boolean defaultStream;
829 private ColorStateList cachedSliderTint;
830 private int iconState; // from Events
831 private ObjectAnimator anim; // slider progress animation for non-touch-related updates
832 private int animTargetProgress;
833 private int lastAudibleLevel = 1;
834 }
835}