blob: f1a7183e1602e5c0b0302550427b2faba7878317 [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
Lin Guoa65cf9482018-04-23 15:19:35 -070019import android.animation.Animator;
20import android.animation.AnimatorInflater;
21import android.animation.AnimatorSet;
Lin Guoaea00232018-04-06 19:18:33 -070022import android.annotation.Nullable;
Lujiang Xuee53f7492018-03-01 09:53:28 -080023import android.app.Dialog;
24import android.app.KeyguardManager;
25import android.content.Context;
26import android.content.DialogInterface;
Lujiang Xuee53f7492018-03-01 09:53:28 -080027import android.graphics.Color;
Lujiang Xuee53f7492018-03-01 09:53:28 -080028import android.graphics.drawable.ColorDrawable;
Lin Guoaea00232018-04-06 19:18:33 -070029import android.graphics.PixelFormat;
Lin Guoa65cf9482018-04-23 15:19:35 -070030import android.graphics.drawable.Drawable;
Lujiang Xuee53f7492018-03-01 09:53:28 -080031import android.media.AudioManager;
32import android.media.AudioSystem;
33import android.os.Debug;
34import android.os.Handler;
35import android.os.Looper;
36import android.os.Message;
Lujiang Xuee53f7492018-03-01 09:53:28 -080037import android.util.Log;
Lujiang Xuee53f7492018-03-01 09:53:28 -080038import android.util.SparseBooleanArray;
39import android.view.ContextThemeWrapper;
40import android.view.Gravity;
41import android.view.MotionEvent;
42import android.view.View;
43import android.view.ViewGroup;
44import android.view.Window;
45import android.view.WindowManager;
Lujiang Xuee53f7492018-03-01 09:53:28 -080046import android.widget.SeekBar;
47import android.widget.SeekBar.OnSeekBarChangeListener;
48
Lin Guoaea00232018-04-06 19:18:33 -070049import androidx.car.widget.ListItem;
50import androidx.car.widget.ListItemAdapter;
51import androidx.car.widget.ListItemAdapter.BackgroundStyle;
52import androidx.car.widget.ListItemProvider.ListProvider;
53import androidx.car.widget.PagedListView;
54import androidx.car.widget.SeekbarListItem;
55
Lujiang Xuee53f7492018-03-01 09:53:28 -080056import java.io.PrintWriter;
57import java.util.ArrayList;
Lin Guoaea00232018-04-06 19:18:33 -070058import java.util.Iterator;
Lujiang Xuee53f7492018-03-01 09:53:28 -080059import java.util.List;
60
Lujiang Xuee53f7492018-03-01 09:53:28 -080061import com.android.systemui.Dependency;
62import com.android.systemui.R;
63import com.android.systemui.plugins.VolumeDialog;
64import com.android.systemui.plugins.VolumeDialogController;
65import com.android.systemui.plugins.VolumeDialogController.State;
66import com.android.systemui.plugins.VolumeDialogController.StreamState;
67
68/**
69 * Car version of the volume dialog.
70 *
71 * A client of VolumeDialogControllerImpl and its state model.
72 *
73 * Methods ending in "H" must be called on the (ui) handler.
74 */
75public class CarVolumeDialogImpl implements VolumeDialog {
76 private static final String TAG = Util.logTag(CarVolumeDialogImpl.class);
77
78 private static final long USER_ATTEMPT_GRACE_PERIOD = 1000;
Lujiang Xuee53f7492018-03-01 09:53:28 -080079
80 private final Context mContext;
81 private final H mHandler = new H();
82 private final VolumeDialogController mController;
Lin Guoaea00232018-04-06 19:18:33 -070083 private final AudioManager mAudioManager;
Lujiang Xuee53f7492018-03-01 09:53:28 -080084
85 private Window mWindow;
86 private CustomDialog mDialog;
Lin Guoaea00232018-04-06 19:18:33 -070087 private PagedListView mListView;
88 private ListItemAdapter mPagedListAdapter;
89 private final List<ListItem> mVolumeLineItems = new ArrayList<>();
Lujiang Xuee53f7492018-03-01 09:53:28 -080090 private final List<VolumeRow> mRows = new ArrayList<>();
91 private ConfigurableTexts mConfigurableTexts;
92 private final SparseBooleanArray mDynamic = new SparseBooleanArray();
93 private final KeyguardManager mKeyguard;
94 private final Object mSafetyWarningLock = new Object();
Lujiang Xuee53f7492018-03-01 09:53:28 -080095
96 private boolean mShowing;
97
Lujiang Xuee53f7492018-03-01 09:53:28 -080098 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;
Lin Guoaea00232018-04-06 19:18:33 -0700103 private boolean mExpanded;
104
Lujiang Xuee53f7492018-03-01 09:53:28 -0800105 public CarVolumeDialogImpl(Context context) {
106 mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
107 mController = Dependency.get(VolumeDialogController.class);
108 mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
Lin Guoaea00232018-04-06 19:18:33 -0700109 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
Lujiang Xuee53f7492018-03-01 09:53:28 -0800110 }
111
112 public void init(int windowType, Callback callback) {
113 initDialog();
114
115 mController.addCallback(mControllerCallbackH, mHandler);
116 mController.getState();
117 }
118
119 @Override
120 public void destroy() {
121 mController.removeCallback(mControllerCallbackH);
122 mHandler.removeCallbacksAndMessages(null);
123 }
124
125 private void initDialog() {
Lin Guoaea00232018-04-06 19:18:33 -0700126 mRows.clear();
127 mVolumeLineItems.clear();
Lujiang Xuee53f7492018-03-01 09:53:28 -0800128 mDialog = new CustomDialog(mContext);
129
130 mConfigurableTexts = new ConfigurableTexts(mContext);
131 mHovering = false;
132 mShowing = false;
Lin Guoaea00232018-04-06 19:18:33 -0700133 mExpanded = false;
Lujiang Xuee53f7492018-03-01 09:53:28 -0800134 mWindow = mDialog.getWindow();
135 mWindow.requestFeature(Window.FEATURE_NO_TITLE);
136 mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
137 mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND
138 | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
139 mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
140 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
141 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
142 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
143 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
144 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
145 mWindow.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
146 mWindow.setWindowAnimations(com.android.internal.R.style.Animation_Toast);
147 final WindowManager.LayoutParams lp = mWindow.getAttributes();
148 lp.format = PixelFormat.TRANSLUCENT;
149 lp.setTitle(VolumeDialogImpl.class.getSimpleName());
150 lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
151 lp.windowAnimations = -1;
152 mWindow.setAttributes(lp);
153 mWindow.setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
154
155 mDialog.setCanceledOnTouchOutside(true);
156 mDialog.setContentView(R.layout.car_volume_dialog);
157 mDialog.setOnShowListener(dialog -> {
Lin Guoa65cf9482018-04-23 15:19:35 -0700158 mListView.setTranslationY(-mListView.getHeight());
159 mListView.setAlpha(0);
160 mListView.animate()
Lujiang Xuee53f7492018-03-01 09:53:28 -0800161 .alpha(1)
162 .translationY(0)
163 .setDuration(300)
164 .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator())
165 .start();
166 });
Lin Guoa65cf9482018-04-23 15:19:35 -0700167 mListView = (PagedListView) mWindow.findViewById(R.id.volume_list);
168 mListView.setOnHoverListener((v, event) -> {
Lujiang Xuee53f7492018-03-01 09:53:28 -0800169 int action = event.getActionMasked();
170 mHovering = (action == MotionEvent.ACTION_HOVER_ENTER)
171 || (action == MotionEvent.ACTION_HOVER_MOVE);
172 rescheduleTimeoutH();
173 return true;
174 });
175
Lin Guoa65cf9482018-04-23 15:19:35 -0700176 addSeekbarListItem(addVolumeRow(AudioManager.STREAM_MUSIC, R.drawable.car_ic_music,
177 R.drawable.car_ic_keyboard_arrow_down, true, true),
178 new ExpandIconListener());
179 // We map AudioManager.STREAM_RING to a navigation icon for demo.
180 addVolumeRow(AudioManager.STREAM_RING, R.drawable.car_ic_navigation, 0,
Lin Guoaea00232018-04-06 19:18:33 -0700181 true, false);
Lin Guoa65cf9482018-04-23 15:19:35 -0700182 addVolumeRow(AudioManager.STREAM_NOTIFICATION, R.drawable.car_ic_notification_2, 0,
Lin Guoaea00232018-04-06 19:18:33 -0700183 true, false);
Lujiang Xuee53f7492018-03-01 09:53:28 -0800184
Lin Guoaea00232018-04-06 19:18:33 -0700185 mPagedListAdapter = new ListItemAdapter(mContext, new ListProvider(mVolumeLineItems),
186 BackgroundStyle.PANEL);
187 mListView.setAdapter(mPagedListAdapter);
188 mListView.setMaxPages(PagedListView.UNLIMITED_PAGES);
Lujiang Xuee53f7492018-03-01 09:53:28 -0800189 }
190
191 public void setStreamImportant(int stream, boolean important) {
192 mHandler.obtainMessage(H.SET_STREAM_IMPORTANT, stream, important ? 1 : 0).sendToTarget();
193 }
194
195 public void setAutomute(boolean automute) {
Lin Guoaea00232018-04-06 19:18:33 -0700196 if (mAutomute == automute) {
197 return;
198 }
Lujiang Xuee53f7492018-03-01 09:53:28 -0800199 mAutomute = automute;
200 mHandler.sendEmptyMessage(H.RECHECK_ALL);
201 }
202
203 public void setSilentMode(boolean silentMode) {
Lin Guoaea00232018-04-06 19:18:33 -0700204 if (mSilentMode == silentMode) {
205 return;
206 }
Lujiang Xuee53f7492018-03-01 09:53:28 -0800207 mSilentMode = silentMode;
208 mHandler.sendEmptyMessage(H.RECHECK_ALL);
209 }
210
Lin Guoaea00232018-04-06 19:18:33 -0700211 private VolumeRow addVolumeRow(int stream, int primaryActionIcon, int supplementalIcon,
212 boolean important, boolean defaultStream) {
213 VolumeRow volumeRow = new VolumeRow();
214 volumeRow.stream = stream;
215 volumeRow.primaryActionIcon = primaryActionIcon;
216 volumeRow.supplementalIcon = supplementalIcon;
217 volumeRow.important = important;
218 volumeRow.defaultStream = defaultStream;
219 volumeRow.listItem = null;
220 mRows.add(volumeRow);
221 return volumeRow;
Lujiang Xuee53f7492018-03-01 09:53:28 -0800222 }
223
Lin Guoaea00232018-04-06 19:18:33 -0700224 private SeekbarListItem addSeekbarListItem(
225 VolumeRow volumeRow, @Nullable View.OnClickListener supplementalIconOnClickListener) {
226 int volumeMax = mAudioManager.getStreamMaxVolume(volumeRow.stream);
227 int currentVolume = mAudioManager.getStreamVolume(volumeRow.stream);
228 SeekbarListItem listItem =
229 new SeekbarListItem(mContext, volumeMax, currentVolume,
230 new VolumeSeekBarChangeListener(volumeRow), null);
Lin Guoa65cf9482018-04-23 15:19:35 -0700231 Drawable primaryIcon = mContext.getResources().getDrawable(volumeRow.primaryActionIcon);
232 listItem.setPrimaryActionIcon(primaryIcon);
Lin Guoaea00232018-04-06 19:18:33 -0700233 if (volumeRow.supplementalIcon != 0) {
Lin Guoa65cf9482018-04-23 15:19:35 -0700234 Drawable supplementalIcon = mContext.getResources()
235 .getDrawable(volumeRow.supplementalIcon);
236 listItem.setSupplementalIcon(supplementalIcon, true,
237 supplementalIconOnClickListener);
Lin Guoaea00232018-04-06 19:18:33 -0700238 } else {
239 listItem.setSupplementalEmptyIcon(true);
Lujiang Xuee53f7492018-03-01 09:53:28 -0800240 }
Lujiang Xuee53f7492018-03-01 09:53:28 -0800241
Lin Guoaea00232018-04-06 19:18:33 -0700242 mVolumeLineItems.add(listItem);
243 volumeRow.listItem = listItem;
Lujiang Xuee53f7492018-03-01 09:53:28 -0800244
Lin Guoaea00232018-04-06 19:18:33 -0700245 return listItem;
Lujiang Xuee53f7492018-03-01 09:53:28 -0800246 }
247
248 private static int getImpliedLevel(SeekBar seekBar, int progress) {
249 final int m = seekBar.getMax();
250 final int n = m / 100 - 1;
251 final int level = progress == 0 ? 0
252 : progress == m ? (m / 100) : (1 + (int)((progress / (float) m) * n));
253 return level;
254 }
255
Lujiang Xuee53f7492018-03-01 09:53:28 -0800256 public void show(int reason) {
257 mHandler.obtainMessage(H.SHOW, reason, 0).sendToTarget();
258 }
259
260 public void dismiss(int reason) {
261 mHandler.obtainMessage(H.DISMISS, reason, 0).sendToTarget();
262 }
263
264 private void showH(int reason) {
265 if (D.BUG) Log.d(TAG, "showH r=" + Events.DISMISS_REASONS[reason]);
266 mHandler.removeMessages(H.SHOW);
267 mHandler.removeMessages(H.DISMISS);
268 rescheduleTimeoutH();
269 if (mShowing) return;
270 mShowing = true;
271
272 mDialog.show();
273 Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
274 mController.notifyVisible(true);
275 }
276
277 protected void rescheduleTimeoutH() {
278 mHandler.removeMessages(H.DISMISS);
279 final int timeout = computeTimeoutH();
280 mHandler.sendMessageDelayed(mHandler
281 .obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT, 0), timeout);
282 if (D.BUG) Log.d(TAG, "rescheduleTimeout " + timeout + " " + Debug.getCaller());
283 mController.userActivity();
284 }
285
286 private int computeTimeoutH() {
287 if (mHovering) return 16000;
288 if (mSafetyWarning != null) return 5000;
289 return 3000;
290 }
291
292 protected void dismissH(int reason) {
293 mHandler.removeMessages(H.DISMISS);
294 mHandler.removeMessages(H.SHOW);
295 if (!mShowing) return;
Lin Guoa65cf9482018-04-23 15:19:35 -0700296 mListView.animate().cancel();
Lujiang Xuee53f7492018-03-01 09:53:28 -0800297 mShowing = false;
298
Lin Guoa65cf9482018-04-23 15:19:35 -0700299 mListView.setTranslationY(0);
300 mListView.setAlpha(1);
301 mListView.animate()
Lujiang Xuee53f7492018-03-01 09:53:28 -0800302 .alpha(0)
Lin Guoa65cf9482018-04-23 15:19:35 -0700303 .translationY(-mListView.getHeight())
Lujiang Xuee53f7492018-03-01 09:53:28 -0800304 .setDuration(250)
305 .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
306 .withEndAction(() -> mHandler.postDelayed(() -> {
307 if (D.BUG) Log.d(TAG, "mDialog.dismiss()");
308 mDialog.dismiss();
309 }, 50))
310 .start();
311
312 Events.writeEvent(mContext, Events.EVENT_DISMISS_DIALOG, reason);
313 mController.notifyVisible(false);
314 synchronized (mSafetyWarningLock) {
315 if (mSafetyWarning != null) {
316 if (D.BUG) Log.d(TAG, "SafetyWarning dismissed");
317 mSafetyWarning.dismiss();
318 }
319 }
320 }
321
Lujiang Xuee53f7492018-03-01 09:53:28 -0800322 private void trimObsoleteH() {
Lin Guoaea00232018-04-06 19:18:33 -0700323 int initialVolumeItemSize = mVolumeLineItems.size();
Lujiang Xuee53f7492018-03-01 09:53:28 -0800324 for (int i = mRows.size() - 1; i >= 0; i--) {
325 final VolumeRow row = mRows.get(i);
326 if (row.ss == null || !row.ss.dynamic) continue;
327 if (!mDynamic.get(row.stream)) {
328 mRows.remove(i);
Lin Guoaea00232018-04-06 19:18:33 -0700329 mVolumeLineItems.remove(row.listItem);
Lujiang Xuee53f7492018-03-01 09:53:28 -0800330 }
331 }
Lin Guoaea00232018-04-06 19:18:33 -0700332
333 if (mVolumeLineItems.size() != initialVolumeItemSize) {
334 mPagedListAdapter.notifyDataSetChanged();
335 }
Lujiang Xuee53f7492018-03-01 09:53:28 -0800336 }
337
Lin Guoaea00232018-04-06 19:18:33 -0700338 private void onStateChangedH(State state) {
Lujiang Xuee53f7492018-03-01 09:53:28 -0800339 mState = state;
340 mDynamic.clear();
341 // add any new dynamic rows
342 for (int i = 0; i < state.states.size(); i++) {
343 final int stream = state.states.keyAt(i);
344 final StreamState ss = state.states.valueAt(i);
Lin Guoaea00232018-04-06 19:18:33 -0700345 if (!ss.dynamic) {
346 continue;
347 }
Lujiang Xuee53f7492018-03-01 09:53:28 -0800348 mDynamic.put(stream, true);
349 if (findRow(stream) == null) {
Lin Guoaea00232018-04-06 19:18:33 -0700350 VolumeRow row = addVolumeRow(stream, R.drawable.ic_volume_remote,
351 0, true,false);
352 if (mExpanded) {
353 addSeekbarListItem(row, null);
354 }
Lujiang Xuee53f7492018-03-01 09:53:28 -0800355 }
356 }
357
Lujiang Xuee53f7492018-03-01 09:53:28 -0800358 for (VolumeRow row : mRows) {
359 updateVolumeRowH(row);
360 }
Lujiang Xuee53f7492018-03-01 09:53:28 -0800361 }
362
363 private void updateVolumeRowH(VolumeRow row) {
364 if (D.BUG) Log.d(TAG, "updateVolumeRowH s=" + row.stream);
Lin Guoaea00232018-04-06 19:18:33 -0700365 if (mState == null) {
366 return;
Lujiang Xuee53f7492018-03-01 09:53:28 -0800367 }
Lin Guoaea00232018-04-06 19:18:33 -0700368 final StreamState ss = mState.states.get(row.stream);
369 if (ss == null) {
370 return;
371 }
372 row.ss = ss;
Lujiang Xuee53f7492018-03-01 09:53:28 -0800373 if (ss.level == row.requestedLevel) {
374 row.requestedLevel = -1;
375 }
Lin Guoaea00232018-04-06 19:18:33 -0700376 // TODO: update Seekbar progress and change the mute icon if necessary.
Lujiang Xuee53f7492018-03-01 09:53:28 -0800377 }
378
Lin Guoaea00232018-04-06 19:18:33 -0700379 private VolumeRow findRow(int stream) {
380 for (VolumeRow row : mRows) {
381 if (row.stream == stream) {
382 return row;
Lujiang Xuee53f7492018-03-01 09:53:28 -0800383 }
384 }
Lin Guoaea00232018-04-06 19:18:33 -0700385 return null;
386 }
387
388 private VolumeRow findRow(SeekbarListItem targetItem) {
389 for (VolumeRow row : mRows) {
390 if (row.listItem == targetItem) {
391 return row;
Lujiang Xuee53f7492018-03-01 09:53:28 -0800392 }
393 }
Lin Guoaea00232018-04-06 19:18:33 -0700394 return null;
395 }
396
397 public void dump(PrintWriter writer) {
398 writer.println(VolumeDialogImpl.class.getSimpleName() + " state:");
399 writer.print(" mShowing: "); writer.println(mShowing);
400 writer.print(" mDynamic: "); writer.println(mDynamic);
401 writer.print(" mAutomute: "); writer.println(mAutomute);
402 writer.print(" mSilentMode: "); writer.println(mSilentMode);
Lujiang Xuee53f7492018-03-01 09:53:28 -0800403 }
404
405 private void recheckH(VolumeRow row) {
406 if (row == null) {
407 if (D.BUG) Log.d(TAG, "recheckH ALL");
408 trimObsoleteH();
409 for (VolumeRow r : mRows) {
410 updateVolumeRowH(r);
411 }
412 } else {
413 if (D.BUG) Log.d(TAG, "recheckH " + row.stream);
414 updateVolumeRowH(row);
415 }
416 }
417
418 private void setStreamImportantH(int stream, boolean important) {
419 for (VolumeRow row : mRows) {
420 if (row.stream == stream) {
421 row.important = important;
422 return;
423 }
424 }
425 }
426
427 private void showSafetyWarningH(int flags) {
428 if ((flags & (AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_SHOW_UI_WARNINGS)) != 0
429 || mShowing) {
430 synchronized (mSafetyWarningLock) {
431 if (mSafetyWarning != null) {
432 return;
433 }
434 mSafetyWarning = new SafetyWarningDialog(mContext, mController.getAudioManager()) {
435 @Override
436 protected void cleanUp() {
437 synchronized (mSafetyWarningLock) {
438 mSafetyWarning = null;
439 }
440 recheckH(null);
441 }
442 };
443 mSafetyWarning.show();
444 }
445 recheckH(null);
446 }
447 rescheduleTimeoutH();
448 }
449
450 private final VolumeDialogController.Callbacks mControllerCallbackH
Lin Guoaea00232018-04-06 19:18:33 -0700451 = new VolumeDialogController.Callbacks() {
Lujiang Xuee53f7492018-03-01 09:53:28 -0800452 @Override
453 public void onShowRequested(int reason) {
454 showH(reason);
455 }
456
457 @Override
458 public void onDismissRequested(int reason) {
459 dismissH(reason);
460 }
461
462 @Override
463 public void onScreenOff() {
464 dismissH(Events.DISMISS_REASON_SCREEN_OFF);
465 }
466
467 @Override
468 public void onStateChanged(State state) {
469 onStateChangedH(state);
470 }
471
472 @Override
473 public void onLayoutDirectionChanged(int layoutDirection) {
Lin Guoa65cf9482018-04-23 15:19:35 -0700474 mListView.setLayoutDirection(layoutDirection);
Lujiang Xuee53f7492018-03-01 09:53:28 -0800475 }
476
477 @Override
478 public void onConfigurationChanged() {
479 mDialog.dismiss();
480 initDialog();
481 mConfigurableTexts.update();
482 }
483
484 @Override
485 public void onShowVibrateHint() {
486 if (mSilentMode) {
487 mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false);
488 }
489 }
490
491 @Override
492 public void onShowSilentHint() {
493 if (mSilentMode) {
494 mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
495 }
496 }
497
498 @Override
499 public void onShowSafetyWarning(int flags) {
500 showSafetyWarningH(flags);
501 }
502
503 @Override
504 public void onAccessibilityModeChanged(Boolean showA11yStream) {
505 }
506 };
507
508 private final class H extends Handler {
509 private static final int SHOW = 1;
510 private static final int DISMISS = 2;
511 private static final int RECHECK = 3;
512 private static final int RECHECK_ALL = 4;
513 private static final int SET_STREAM_IMPORTANT = 5;
514 private static final int RESCHEDULE_TIMEOUT = 6;
515 private static final int STATE_CHANGED = 7;
516
517 public H() {
518 super(Looper.getMainLooper());
519 }
520
521 @Override
522 public void handleMessage(Message msg) {
523 switch (msg.what) {
524 case SHOW: showH(msg.arg1); break;
525 case DISMISS: dismissH(msg.arg1); break;
526 case RECHECK: recheckH((VolumeRow) msg.obj); break;
527 case RECHECK_ALL: recheckH(null); break;
528 case SET_STREAM_IMPORTANT: setStreamImportantH(msg.arg1, msg.arg2 != 0); break;
529 case RESCHEDULE_TIMEOUT: rescheduleTimeoutH(); break;
530 case STATE_CHANGED: onStateChangedH(mState); break;
531 }
532 }
533 }
534
535 private final class CustomDialog extends Dialog implements DialogInterface {
536 public CustomDialog(Context context) {
537 super(context, com.android.systemui.R.style.qs_theme);
538 }
539
540 @Override
541 public boolean dispatchTouchEvent(MotionEvent ev) {
542 rescheduleTimeoutH();
543 return super.dispatchTouchEvent(ev);
544 }
545
546 @Override
547 protected void onStart() {
548 super.setCanceledOnTouchOutside(true);
549 super.onStart();
550 }
551
552 @Override
553 protected void onStop() {
554 super.onStop();
555 mHandler.sendEmptyMessage(H.RECHECK_ALL);
556 }
557
558 @Override
559 public boolean onTouchEvent(MotionEvent event) {
560 if (isShowing()) {
561 if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
562 dismissH(Events.DISMISS_REASON_TOUCH_OUTSIDE);
563 return true;
564 }
565 }
566 return false;
567 }
568 }
569
570 private final class VolumeSeekBarChangeListener implements OnSeekBarChangeListener {
571 private final VolumeRow mRow;
572
Lin Guoaea00232018-04-06 19:18:33 -0700573 private VolumeSeekBarChangeListener(VolumeRow volumeRow) {
574 mRow = volumeRow;
Lujiang Xuee53f7492018-03-01 09:53:28 -0800575 }
576
577 @Override
578 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
Lin Guoaea00232018-04-06 19:18:33 -0700579 if (mRow.ss == null) {
580 return;
581 }
582 if (D.BUG) {
583 Log.d(TAG, AudioSystem.streamToString(mRow.stream)
584 + " onProgressChanged " + progress + " fromUser=" + fromUser);
585 }
586 if (!fromUser) {
587 return;
588 }
Lujiang Xuee53f7492018-03-01 09:53:28 -0800589 if (mRow.ss.levelMin > 0) {
Lin Guoaea00232018-04-06 19:18:33 -0700590 final int minProgress = mRow.ss.levelMin;
Lujiang Xuee53f7492018-03-01 09:53:28 -0800591 if (progress < minProgress) {
592 seekBar.setProgress(minProgress);
593 progress = minProgress;
594 }
595 }
596 final int userLevel = getImpliedLevel(seekBar, progress);
597 if (mRow.ss.level != userLevel || mRow.ss.muted && userLevel > 0) {
Lujiang Xuee53f7492018-03-01 09:53:28 -0800598 if (mRow.requestedLevel != userLevel) {
599 mController.setStreamVolume(mRow.stream, userLevel);
600 mRow.requestedLevel = userLevel;
601 Events.writeEvent(mContext, Events.EVENT_TOUCH_LEVEL_CHANGED, mRow.stream,
602 userLevel);
603 }
604 }
605 }
606
607 @Override
608 public void onStartTrackingTouch(SeekBar seekBar) {
Lin Guoaea00232018-04-06 19:18:33 -0700609 if (D.BUG) {
610 Log.d(TAG, "onStartTrackingTouch"+ " " + mRow.stream);
611 }
Lujiang Xuee53f7492018-03-01 09:53:28 -0800612 mController.setActiveStream(mRow.stream);
Lujiang Xuee53f7492018-03-01 09:53:28 -0800613 }
614
615 @Override
616 public void onStopTrackingTouch(SeekBar seekBar) {
Lin Guoaea00232018-04-06 19:18:33 -0700617 if (D.BUG) {
618 Log.d(TAG, "onStopTrackingTouch"+ " " + mRow.stream);
619 }
Lujiang Xuee53f7492018-03-01 09:53:28 -0800620 final int userLevel = getImpliedLevel(seekBar, seekBar.getProgress());
621 Events.writeEvent(mContext, Events.EVENT_TOUCH_LEVEL_DONE, mRow.stream, userLevel);
622 if (mRow.ss.level != userLevel) {
623 mHandler.sendMessageDelayed(mHandler.obtainMessage(H.RECHECK, mRow),
624 USER_ATTEMPT_GRACE_PERIOD);
625 }
626 }
627 }
628
Lin Guoa65cf9482018-04-23 15:19:35 -0700629 private final class ExpandIconListener implements View.OnClickListener {
630 @Override
631 public void onClick(final View v) {
632 mExpanded = !mExpanded;
633 Animator inAnimator;
634 if (mExpanded) {
635 for (VolumeRow row : mRows) {
636 // Adding the items which are not coming from default stream.
637 if (!row.defaultStream) {
638 addSeekbarListItem(row, null);
639 }
640 }
641 inAnimator = AnimatorInflater.loadAnimator(
642 mContext, R.anim.car_arrow_fade_in_rotate_up);
643 } else {
644 // Only keeping the default stream if it is not expended.
645 Iterator itr = mVolumeLineItems.iterator();
646 while (itr.hasNext()) {
647 SeekbarListItem item = (SeekbarListItem) itr.next();
648 VolumeRow row = findRow(item);
649 if (!row.defaultStream) {
650 itr.remove();
651 }
652 }
653 inAnimator = AnimatorInflater.loadAnimator(
654 mContext, R.anim.car_arrow_fade_in_rotate_down);
655 }
656
657 Animator outAnimator = AnimatorInflater.loadAnimator(
658 mContext, R.anim.car_arrow_fade_out);
659 inAnimator.setStartDelay(100);
660 AnimatorSet animators = new AnimatorSet();
661 animators.playTogether(outAnimator, inAnimator);
662 animators.setTarget(v);
663 animators.start();
664 mPagedListAdapter.notifyDataSetChanged();
665 }
666 }
667
Lujiang Xuee53f7492018-03-01 09:53:28 -0800668 private static class VolumeRow {
Lujiang Xuee53f7492018-03-01 09:53:28 -0800669 private int stream;
670 private StreamState ss;
Lujiang Xuee53f7492018-03-01 09:53:28 -0800671 private boolean important;
672 private boolean defaultStream;
Lin Guoaea00232018-04-06 19:18:33 -0700673 private int primaryActionIcon;
674 private int supplementalIcon;
675 private SeekbarListItem listItem;
676 private int requestedLevel = -1; // pending user-requested level via progress changed
Lujiang Xuee53f7492018-03-01 09:53:28 -0800677 }
678}