blob: d0a63f058291f3875c92cd7ac8d792c9475ac023 [file] [log] [blame]
Brad Stenning8d1a51c2018-11-20 17:34:16 -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.Animator;
20import android.animation.AnimatorInflater;
21import android.animation.AnimatorSet;
22import android.annotation.DrawableRes;
23import android.annotation.Nullable;
24import android.app.Dialog;
25import android.app.KeyguardManager;
26import android.car.Car;
27import android.car.CarNotConnectedException;
28import android.car.media.CarAudioManager;
Brad Stenning8d1a51c2018-11-20 17:34:16 -080029import android.content.ComponentName;
30import android.content.Context;
31import android.content.DialogInterface;
32import android.content.ServiceConnection;
33import android.content.res.TypedArray;
34import android.content.res.XmlResourceParser;
Heemin Seogea8b7fe2019-04-09 08:54:25 -070035import android.graphics.Color;
Brad Stenning8d1a51c2018-11-20 17:34:16 -080036import android.graphics.PixelFormat;
Heemin Seogea8b7fe2019-04-09 08:54:25 -070037import android.graphics.drawable.ColorDrawable;
Brad Stenning8d1a51c2018-11-20 17:34:16 -080038import android.graphics.drawable.Drawable;
39import android.media.AudioManager;
40import android.os.Debug;
41import android.os.Handler;
42import android.os.IBinder;
43import android.os.Looper;
44import android.os.Message;
45import android.util.AttributeSet;
46import android.util.Log;
47import android.util.SparseArray;
48import android.util.Xml;
Brad Stenning8d1a51c2018-11-20 17:34:16 -080049import android.view.Gravity;
50import android.view.MotionEvent;
51import android.view.View;
52import android.view.ViewGroup;
53import android.view.Window;
54import android.view.WindowManager;
55import android.widget.SeekBar;
56import android.widget.SeekBar.OnSeekBarChangeListener;
57
Heemin Seog0d5e0182019-03-13 13:49:24 -070058import androidx.recyclerview.widget.LinearLayoutManager;
59import androidx.recyclerview.widget.RecyclerView;
Brad Stenning8d1a51c2018-11-20 17:34:16 -080060
61import com.android.systemui.R;
62import com.android.systemui.plugins.VolumeDialog;
63
64import org.xmlpull.v1.XmlPullParserException;
65
66import java.io.IOException;
67import java.util.ArrayList;
68import java.util.Iterator;
69import java.util.List;
70
71/**
72 * Car version of the volume dialog.
73 *
74 * Methods ending in "H" must be called on the (ui) handler.
75 */
76public class CarVolumeDialogImpl implements VolumeDialog {
77
78 private static final String TAG = Util.logTag(CarVolumeDialogImpl.class);
79
80 private static final String XML_TAG_VOLUME_ITEMS = "carVolumeItems";
81 private static final String XML_TAG_VOLUME_ITEM = "item";
82 private static final int HOVERING_TIMEOUT = 16000;
83 private static final int NORMAL_TIMEOUT = 3000;
84 private static final int LISTVIEW_ANIMATION_DURATION_IN_MILLIS = 250;
85 private static final int DISMISS_DELAY_IN_MILLIS = 50;
86 private static final int ARROW_FADE_IN_START_DELAY_IN_MILLIS = 100;
87
88 private final Context mContext;
89 private final H mHandler = new H();
90 // All the volume items.
91 private final SparseArray<VolumeItem> mVolumeItems = new SparseArray<>();
92 // Available volume items in car audio manager.
93 private final List<VolumeItem> mAvailableVolumeItems = new ArrayList<>();
Heemin Seog0d5e0182019-03-13 13:49:24 -070094 // Volume items in the RecyclerView.
95 private final List<CarVolumeItem> mCarVolumeLineItems = new ArrayList<>();
Brad Stenning8d1a51c2018-11-20 17:34:16 -080096 private final KeyguardManager mKeyguard;
97 private Window mWindow;
98 private CustomDialog mDialog;
Heemin Seog0d5e0182019-03-13 13:49:24 -070099 private RecyclerView mListView;
100 private CarVolumeItemAdapter mVolumeItemsAdapter;
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800101 private Car mCar;
102 private CarAudioManager mCarAudioManager;
Hongwei Wangefc90db2018-12-07 11:30:08 -0800103 private final CarAudioManager.CarVolumeCallback mVolumeChangeCallback =
104 new CarAudioManager.CarVolumeCallback() {
Priaynk Singhe87b1372018-12-05 10:35:47 -0800105 @Override
106 public void onGroupVolumeChanged(int zoneId, int groupId, int flags) {
107 // TODO: Include zoneId into consideration.
108 // For instance
109 // - single display + single-zone, ignore zoneId
110 // - multi-display + single-zone, zoneId is fixed, may show volume bar on all
111 // displays
112 // - single-display + multi-zone, may show volume bar on primary display only
113 // - multi-display + multi-zone, may show volume bar on display specified by
114 // zoneId
115 VolumeItem volumeItem = mAvailableVolumeItems.get(groupId);
116 int value = getSeekbarValue(mCarAudioManager, groupId);
Priyank Singhf8a87fe2019-03-29 15:00:16 -0700117 // find if the group id for which the volume changed is currently being
118 // displayed.
119 boolean isShowing = mCarVolumeLineItems.stream().anyMatch(
120 item -> item.getGroupId() == groupId);
Priaynk Singhe87b1372018-12-05 10:35:47 -0800121 // Do not update the progress if it is the same as before. When car audio
122 // manager sets
123 // its group volume caused by the seekbar progress changed, it also triggers
124 // this
125 // callback. Updating the seekbar at the same time could block the continuous
126 // seeking.
Priyank Singhf8a87fe2019-03-29 15:00:16 -0700127 if (value != volumeItem.progress && isShowing) {
Heemin Seog0d5e0182019-03-13 13:49:24 -0700128 volumeItem.carVolumeItem.setProgress(value);
Priaynk Singhe87b1372018-12-05 10:35:47 -0800129 volumeItem.progress = value;
130 }
131 if ((flags & AudioManager.FLAG_SHOW_UI) != 0) {
Priyank Singhf8a87fe2019-03-29 15:00:16 -0700132 mCurrentlyDisplayingGroupId = groupId;
Priaynk Singhe87b1372018-12-05 10:35:47 -0800133 mHandler.obtainMessage(H.SHOW,
134 Events.SHOW_REASON_VOLUME_CHANGED).sendToTarget();
135 }
136 }
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800137
Priaynk Singhe87b1372018-12-05 10:35:47 -0800138 @Override
139 public void onMasterMuteChanged(int zoneId, int flags) {
140 // ignored
141 }
142 };
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800143 private boolean mHovering;
Priyank Singhf8a87fe2019-03-29 15:00:16 -0700144 private int mCurrentlyDisplayingGroupId;
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800145 private boolean mShowing;
146 private boolean mExpanded;
Priaynk Singhe87b1372018-12-05 10:35:47 -0800147 private View mExpandIcon;
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800148 private final ServiceConnection mServiceConnection = new ServiceConnection() {
149 @Override
150 public void onServiceConnected(ComponentName name, IBinder service) {
151 try {
152 mExpanded = false;
153 mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE);
154 int volumeGroupCount = mCarAudioManager.getVolumeGroupCount();
155 // Populates volume slider items from volume groups to UI.
156 for (int groupId = 0; groupId < volumeGroupCount; groupId++) {
157 VolumeItem volumeItem = getVolumeItemForUsages(
158 mCarAudioManager.getUsagesForVolumeGroupId(groupId));
159 mAvailableVolumeItems.add(volumeItem);
160 // The first one is the default item.
161 if (groupId == 0) {
Priyank Singhf8a87fe2019-03-29 15:00:16 -0700162 setuptListItem(0);
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800163 }
164 }
165
166 // If list is already initiated, update its content.
Heemin Seog0d5e0182019-03-13 13:49:24 -0700167 if (mVolumeItemsAdapter != null) {
168 mVolumeItemsAdapter.notifyDataSetChanged();
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800169 }
Hongwei Wangefc90db2018-12-07 11:30:08 -0800170 mCarAudioManager.registerCarVolumeCallback(mVolumeChangeCallback);
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800171 } catch (CarNotConnectedException e) {
172 Log.e(TAG, "Car is not connected!", e);
173 }
174 }
175
176 /**
177 * This does not get called when service is properly disconnected.
178 * So we need to also handle cleanups in destroy().
179 */
180 @Override
181 public void onServiceDisconnected(ComponentName name) {
182 cleanupAudioManager();
183 }
184 };
185
Priyank Singhf8a87fe2019-03-29 15:00:16 -0700186 private void setuptListItem(int groupId) {
187 mCarVolumeLineItems.clear();
188 VolumeItem volumeItem = mAvailableVolumeItems.get(groupId);
189 volumeItem.defaultItem = true;
190 addCarVolumeListItem(volumeItem, /* volumeGroupId = */ groupId,
Priaynk Singhe87b1372018-12-05 10:35:47 -0800191 R.drawable.car_ic_keyboard_arrow_down, new ExpandIconListener()
192 );
193 }
194
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800195 public CarVolumeDialogImpl(Context context) {
Heemin Seog0d5e0182019-03-13 13:49:24 -0700196 mContext = context;
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800197 mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
198 mCar = Car.createCar(mContext, mServiceConnection);
199 }
200
201 private static int getSeekbarValue(CarAudioManager carAudioManager, int volumeGroupId) {
202 try {
203 return carAudioManager.getGroupVolume(volumeGroupId);
204 } catch (CarNotConnectedException e) {
205 Log.e(TAG, "Car is not connected!", e);
206 }
207 return 0;
208 }
209
210 private static int getMaxSeekbarValue(CarAudioManager carAudioManager, int volumeGroupId) {
211 try {
212 return carAudioManager.getGroupMaxVolume(volumeGroupId);
213 } catch (CarNotConnectedException e) {
214 Log.e(TAG, "Car is not connected!", e);
215 }
216 return 0;
217 }
218
219 /**
220 * Build the volume window and connect to the CarService which registers with car audio
221 * manager.
222 */
223 @Override
224 public void init(int windowType, Callback callback) {
225 initDialog();
226
227 mCar.connect();
228 }
229
230 @Override
231 public void destroy() {
232 mHandler.removeCallbacksAndMessages(null);
233
234 cleanupAudioManager();
235 // unregisterVolumeCallback is not being called when disconnect car, so we manually cleanup
236 // audio manager beforehand.
237 mCar.disconnect();
238 }
239
240 private void initDialog() {
241 loadAudioUsageItems();
Heemin Seog0d5e0182019-03-13 13:49:24 -0700242 mCarVolumeLineItems.clear();
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800243 mDialog = new CustomDialog(mContext);
244
245 mHovering = false;
246 mShowing = false;
247 mExpanded = false;
248 mWindow = mDialog.getWindow();
249 mWindow.requestFeature(Window.FEATURE_NO_TITLE);
Heemin Seogea8b7fe2019-04-09 08:54:25 -0700250 mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800251 mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND
252 | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
253 mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
254 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
255 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
256 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
257 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
258 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
259 mWindow.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
260 mWindow.setWindowAnimations(com.android.internal.R.style.Animation_Toast);
261 final WindowManager.LayoutParams lp = mWindow.getAttributes();
262 lp.format = PixelFormat.TRANSLUCENT;
263 lp.setTitle(VolumeDialogImpl.class.getSimpleName());
264 lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
265 lp.windowAnimations = -1;
266 mWindow.setAttributes(lp);
Heemin Seog0d5e0182019-03-13 13:49:24 -0700267
268 mDialog.setContentView(R.layout.car_volume_dialog);
269 mWindow.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800270
271 mDialog.setCanceledOnTouchOutside(true);
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800272 mDialog.setOnShowListener(dialog -> {
273 mListView.setTranslationY(-mListView.getHeight());
274 mListView.setAlpha(0);
275 mListView.animate()
276 .alpha(1)
277 .translationY(0)
278 .setDuration(LISTVIEW_ANIMATION_DURATION_IN_MILLIS)
279 .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator())
280 .start();
281 });
Heemin Seog0d5e0182019-03-13 13:49:24 -0700282 mListView = mWindow.findViewById(R.id.volume_list);
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800283 mListView.setOnHoverListener((v, event) -> {
284 int action = event.getActionMasked();
285 mHovering = (action == MotionEvent.ACTION_HOVER_ENTER)
286 || (action == MotionEvent.ACTION_HOVER_MOVE);
287 rescheduleTimeoutH();
288 return true;
289 });
290
Heemin Seog0d5e0182019-03-13 13:49:24 -0700291 mVolumeItemsAdapter = new CarVolumeItemAdapter(mContext, mCarVolumeLineItems);
292 mListView.setAdapter(mVolumeItemsAdapter);
293 mListView.setLayoutManager(new LinearLayoutManager(mContext));
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800294 }
295
296
297 private void showH(int reason) {
298 if (D.BUG) {
299 Log.d(TAG, "showH r=" + Events.DISMISS_REASONS[reason]);
300 }
301
302 mHandler.removeMessages(H.SHOW);
303 mHandler.removeMessages(H.DISMISS);
304 rescheduleTimeoutH();
305 // Refresh the data set before showing.
Heemin Seog0d5e0182019-03-13 13:49:24 -0700306 mVolumeItemsAdapter.notifyDataSetChanged();
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800307 if (mShowing) {
308 return;
309 }
310 mShowing = true;
Priyank Singhf8a87fe2019-03-29 15:00:16 -0700311 setuptListItem(mCurrentlyDisplayingGroupId);
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800312 mDialog.show();
313 Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
314 }
315
316 private void rescheduleTimeoutH() {
317 mHandler.removeMessages(H.DISMISS);
318 final int timeout = computeTimeoutH();
319 mHandler.sendMessageDelayed(mHandler
320 .obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT), timeout);
321
322 if (D.BUG) {
323 Log.d(TAG, "rescheduleTimeout " + timeout + " " + Debug.getCaller());
324 }
325 }
326
327 private int computeTimeoutH() {
328 return mHovering ? HOVERING_TIMEOUT : NORMAL_TIMEOUT;
329 }
330
331 private void dismissH(int reason) {
332 if (D.BUG) {
333 Log.d(TAG, "dismissH r=" + Events.DISMISS_REASONS[reason]);
334 }
335
336 mHandler.removeMessages(H.DISMISS);
337 mHandler.removeMessages(H.SHOW);
338 if (!mShowing) {
339 return;
340 }
341
342 mListView.animate().cancel();
343
344 mListView.setTranslationY(0);
345 mListView.setAlpha(1);
346 mListView.animate()
347 .alpha(0)
348 .translationY(-mListView.getHeight())
349 .setDuration(LISTVIEW_ANIMATION_DURATION_IN_MILLIS)
350 .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
351 .withEndAction(() -> mHandler.postDelayed(() -> {
352 if (D.BUG) {
353 Log.d(TAG, "mDialog.dismiss()");
354 }
355 mDialog.dismiss();
356 mShowing = false;
Priaynk Singhe87b1372018-12-05 10:35:47 -0800357 mShowing = false;
358 // if mExpandIcon is null that means user never clicked on the expanded arrow
359 // which implies that the dialog is still not expanded. In that case we do
360 // not want to reset the state
361 if (mExpandIcon != null && mExpanded) {
362 toggleDialogExpansion(/* isClicked = */ false);
363 }
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800364 }, DISMISS_DELAY_IN_MILLIS))
365 .start();
366
367 Events.writeEvent(mContext, Events.EVENT_DISMISS_DIALOG, reason);
368 }
369
370 private void loadAudioUsageItems() {
371 try (XmlResourceParser parser = mContext.getResources().getXml(R.xml.car_volume_items)) {
372 AttributeSet attrs = Xml.asAttributeSet(parser);
373 int type;
374 // Traverse to the first start tag
375 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
376 && type != XmlResourceParser.START_TAG) {
377 // Do Nothing (moving parser to start element)
378 }
379
380 if (!XML_TAG_VOLUME_ITEMS.equals(parser.getName())) {
381 throw new RuntimeException("Meta-data does not start with carVolumeItems tag");
382 }
383 int outerDepth = parser.getDepth();
384 int rank = 0;
385 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
386 && (type != XmlResourceParser.END_TAG || parser.getDepth() > outerDepth)) {
387 if (type == XmlResourceParser.END_TAG) {
388 continue;
389 }
390 if (XML_TAG_VOLUME_ITEM.equals(parser.getName())) {
391 TypedArray item = mContext.getResources().obtainAttributes(
392 attrs, R.styleable.carVolumeItems_item);
393 int usage = item.getInt(R.styleable.carVolumeItems_item_usage, -1);
394 if (usage >= 0) {
395 VolumeItem volumeItem = new VolumeItem();
396 volumeItem.rank = rank;
397 volumeItem.icon = item.getResourceId(R.styleable.carVolumeItems_item_icon,
398 0);
399 mVolumeItems.put(usage, volumeItem);
400 rank++;
401 }
402 item.recycle();
403 }
404 }
405 } catch (XmlPullParserException | IOException e) {
406 Log.e(TAG, "Error parsing volume groups configuration", e);
407 }
408 }
409
410 private VolumeItem getVolumeItemForUsages(int[] usages) {
411 int rank = Integer.MAX_VALUE;
412 VolumeItem result = null;
413 for (int usage : usages) {
414 VolumeItem volumeItem = mVolumeItems.get(usage);
415 if (volumeItem.rank < rank) {
416 rank = volumeItem.rank;
417 result = volumeItem;
418 }
419 }
420 return result;
421 }
422
Heemin Seog0d5e0182019-03-13 13:49:24 -0700423 private CarVolumeItem addCarVolumeListItem(VolumeItem volumeItem, int volumeGroupId,
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800424 int supplementalIconId,
425 @Nullable View.OnClickListener supplementalIconOnClickListener) {
Heemin Seog0d5e0182019-03-13 13:49:24 -0700426 CarVolumeItem carVolumeItem = new CarVolumeItem();
427 carVolumeItem.setMax(getMaxSeekbarValue(mCarAudioManager, volumeGroupId));
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800428 int color = mContext.getResources().getColor(R.color.car_volume_dialog_tint);
429 int progress = getSeekbarValue(mCarAudioManager, volumeGroupId);
Heemin Seog0d5e0182019-03-13 13:49:24 -0700430 carVolumeItem.setProgress(progress);
431 carVolumeItem.setOnSeekBarChangeListener(
432 new CarVolumeDialogImpl.VolumeSeekBarChangeListener(volumeGroupId,
433 mCarAudioManager));
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800434 Drawable primaryIcon = mContext.getResources().getDrawable(volumeItem.icon);
435 primaryIcon.mutate().setTint(color);
Heemin Seog0d5e0182019-03-13 13:49:24 -0700436 carVolumeItem.setPrimaryIcon(primaryIcon);
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800437 if (supplementalIconId != 0) {
438 Drawable supplementalIcon = mContext.getResources().getDrawable(supplementalIconId);
439 supplementalIcon.mutate().setTint(color);
Heemin Seog0d5e0182019-03-13 13:49:24 -0700440 carVolumeItem.setSupplementalIcon(supplementalIcon,
441 /* showSupplementalIconDivider= */ true);
442 carVolumeItem.setSupplementalIconListener(supplementalIconOnClickListener);
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800443 } else {
Heemin Seog0d5e0182019-03-13 13:49:24 -0700444 carVolumeItem.setSupplementalIcon(/* drawable= */ null,
445 /* showSupplementalIconDivider= */ false);
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800446 }
Priyank Singhf8a87fe2019-03-29 15:00:16 -0700447 carVolumeItem.setGroupId(volumeGroupId);
Heemin Seog0d5e0182019-03-13 13:49:24 -0700448 mCarVolumeLineItems.add(carVolumeItem);
449 volumeItem.carVolumeItem = carVolumeItem;
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800450 volumeItem.progress = progress;
Heemin Seog0d5e0182019-03-13 13:49:24 -0700451 return carVolumeItem;
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800452 }
453
Heemin Seog0d5e0182019-03-13 13:49:24 -0700454 private VolumeItem findVolumeItem(CarVolumeItem targetItem) {
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800455 for (int i = 0; i < mVolumeItems.size(); ++i) {
456 VolumeItem volumeItem = mVolumeItems.valueAt(i);
Heemin Seog0d5e0182019-03-13 13:49:24 -0700457 if (volumeItem.carVolumeItem == targetItem) {
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800458 return volumeItem;
459 }
460 }
461 return null;
462 }
463
464 private void cleanupAudioManager() {
Hongwei Wangefc90db2018-12-07 11:30:08 -0800465 mCarAudioManager.unregisterCarVolumeCallback(mVolumeChangeCallback);
Heemin Seog0d5e0182019-03-13 13:49:24 -0700466 mCarVolumeLineItems.clear();
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800467 mCarAudioManager = null;
468 }
469
470 /**
471 * Wrapper class which contains information of each volume group.
472 */
473 private static class VolumeItem {
474
475 private int rank;
476 private boolean defaultItem = false;
Heemin Seog0d5e0182019-03-13 13:49:24 -0700477 @DrawableRes
478 private int icon;
479 private CarVolumeItem carVolumeItem;
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800480 private int progress;
481 }
482
483 private final class H extends Handler {
484
485 private static final int SHOW = 1;
486 private static final int DISMISS = 2;
487
488 private H() {
489 super(Looper.getMainLooper());
490 }
491
492 @Override
493 public void handleMessage(Message msg) {
494 switch (msg.what) {
495 case SHOW:
496 showH(msg.arg1);
497 break;
498 case DISMISS:
499 dismissH(msg.arg1);
500 break;
501 default:
502 }
503 }
504 }
505
506 private final class CustomDialog extends Dialog implements DialogInterface {
507
508 private CustomDialog(Context context) {
509 super(context, com.android.systemui.R.style.qs_theme);
510 }
511
512 @Override
513 public boolean dispatchTouchEvent(MotionEvent ev) {
514 rescheduleTimeoutH();
515 return super.dispatchTouchEvent(ev);
516 }
517
518 @Override
519 protected void onStart() {
520 super.setCanceledOnTouchOutside(true);
521 super.onStart();
522 }
523
524 @Override
525 protected void onStop() {
526 super.onStop();
527 }
528
529 @Override
530 public boolean onTouchEvent(MotionEvent event) {
531 if (isShowing()) {
532 if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
533 mHandler.obtainMessage(
534 H.DISMISS, Events.DISMISS_REASON_TOUCH_OUTSIDE).sendToTarget();
535 return true;
536 }
537 }
538 return false;
539 }
540 }
541
542 private final class ExpandIconListener implements View.OnClickListener {
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800543 @Override
544 public void onClick(final View v) {
Priaynk Singhe87b1372018-12-05 10:35:47 -0800545 mExpandIcon = v;
546 toggleDialogExpansion(true);
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800547 }
548 }
549
Priaynk Singhe87b1372018-12-05 10:35:47 -0800550 private void toggleDialogExpansion(boolean isClicked) {
551 mExpanded = !mExpanded;
552 Animator inAnimator;
553 if (mExpanded) {
554 for (int groupId = 0; groupId < mAvailableVolumeItems.size(); ++groupId) {
Priyank Singhf8a87fe2019-03-29 15:00:16 -0700555 if (groupId != mCurrentlyDisplayingGroupId) {
556 VolumeItem volumeItem = mAvailableVolumeItems.get(groupId);
Heemin Seog0d5e0182019-03-13 13:49:24 -0700557 addCarVolumeListItem(volumeItem, groupId, 0, null);
Priaynk Singhe87b1372018-12-05 10:35:47 -0800558 }
559 }
560 inAnimator = AnimatorInflater.loadAnimator(
561 mContext, R.anim.car_arrow_fade_in_rotate_up);
562
563 } else {
564 // Only keeping the default stream if it is not expended.
Heemin Seog0d5e0182019-03-13 13:49:24 -0700565 Iterator itr = mCarVolumeLineItems.iterator();
Priaynk Singhe87b1372018-12-05 10:35:47 -0800566 while (itr.hasNext()) {
Heemin Seog0d5e0182019-03-13 13:49:24 -0700567 CarVolumeItem carVolumeItem = (CarVolumeItem) itr.next();
Priyank Singhf8a87fe2019-03-29 15:00:16 -0700568 if (carVolumeItem.getGroupId() != mCurrentlyDisplayingGroupId) {
Priaynk Singhe87b1372018-12-05 10:35:47 -0800569 itr.remove();
Priaynk Singhe87b1372018-12-05 10:35:47 -0800570 }
571 }
572 inAnimator = AnimatorInflater.loadAnimator(
573 mContext, R.anim.car_arrow_fade_in_rotate_down);
574 }
575
576 Animator outAnimator = AnimatorInflater.loadAnimator(
577 mContext, R.anim.car_arrow_fade_out);
578 inAnimator.setStartDelay(ARROW_FADE_IN_START_DELAY_IN_MILLIS);
579 AnimatorSet animators = new AnimatorSet();
580 animators.playTogether(outAnimator, inAnimator);
581 if (!isClicked) {
582 // Do not animate when the state is called to reset the dialogs view and not clicked
583 // by user.
584 animators.setDuration(0);
585 }
586 animators.setTarget(mExpandIcon);
587 animators.start();
Heemin Seog0d5e0182019-03-13 13:49:24 -0700588 mVolumeItemsAdapter.notifyDataSetChanged();
Priaynk Singhe87b1372018-12-05 10:35:47 -0800589 }
590
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800591 private final class VolumeSeekBarChangeListener implements OnSeekBarChangeListener {
592
593 private final int mVolumeGroupId;
594 private final CarAudioManager mCarAudioManager;
595
596 private VolumeSeekBarChangeListener(int volumeGroupId, CarAudioManager carAudioManager) {
597 mVolumeGroupId = volumeGroupId;
598 mCarAudioManager = carAudioManager;
599 }
600
601 @Override
602 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
603 if (!fromUser) {
604 // For instance, if this event is originated from AudioService,
605 // we can ignore it as it has already been handled and doesn't need to be
606 // sent back down again.
607 return;
608 }
609 try {
610 if (mCarAudioManager == null) {
611 Log.w(TAG, "Ignoring volume change event because the car isn't connected");
612 return;
613 }
614 mAvailableVolumeItems.get(mVolumeGroupId).progress = progress;
Priyank Singh5ca4f422019-04-26 16:03:35 -0700615 mAvailableVolumeItems.get(
616 mVolumeGroupId).carVolumeItem.setProgress(progress);
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800617 mCarAudioManager.setGroupVolume(mVolumeGroupId, progress, 0);
618 } catch (CarNotConnectedException e) {
619 Log.e(TAG, "Car is not connected!", e);
620 }
621 }
622
623 @Override
624 public void onStartTrackingTouch(SeekBar seekBar) {
625 }
626
627 @Override
628 public void onStopTrackingTouch(SeekBar seekBar) {
629 }
630 }
Heemin Seog0d5e0182019-03-13 13:49:24 -0700631}