blob: 9b616e00a72fa003d382a970e044034963c84ca6 [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 Guo30ceb782018-05-07 15:01:40 -070022import android.annotation.DrawableRes;
Lin Guoaea00232018-04-06 19:18:33 -070023import android.annotation.Nullable;
Lujiang Xuee53f7492018-03-01 09:53:28 -080024import android.app.Dialog;
25import android.app.KeyguardManager;
Lin Guod7468d42018-05-02 14:25:10 -070026import android.car.Car;
27import android.car.CarNotConnectedException;
28import android.car.media.CarAudioManager;
29import android.car.media.ICarVolumeCallback;
30import android.content.ComponentName;
Lujiang Xuee53f7492018-03-01 09:53:28 -080031import android.content.Context;
32import android.content.DialogInterface;
Lin Guod7468d42018-05-02 14:25:10 -070033import android.content.ServiceConnection;
34import android.content.res.TypedArray;
35import android.content.res.XmlResourceParser;
Lujiang Xuee53f7492018-03-01 09:53:28 -080036import android.graphics.Color;
Lin Guoaea00232018-04-06 19:18:33 -070037import android.graphics.PixelFormat;
Gus Prevasab336792018-11-14 13:52:20 -050038import android.graphics.drawable.ColorDrawable;
Lin Guoa65cf9482018-04-23 15:19:35 -070039import android.graphics.drawable.Drawable;
Lin Guod7468d42018-05-02 14:25:10 -070040import android.media.AudioAttributes;
Lin Guof37d0eb2018-06-07 17:08:30 -070041import android.media.AudioManager;
Lujiang Xuee53f7492018-03-01 09:53:28 -080042import android.os.Debug;
43import android.os.Handler;
Lin Guod7468d42018-05-02 14:25:10 -070044import android.os.IBinder;
Lujiang Xuee53f7492018-03-01 09:53:28 -080045import android.os.Looper;
46import android.os.Message;
Lin Guod7468d42018-05-02 14:25:10 -070047import android.util.AttributeSet;
Lujiang Xuee53f7492018-03-01 09:53:28 -080048import android.util.Log;
Lin Guod7468d42018-05-02 14:25:10 -070049import android.util.SparseArray;
50import android.util.Xml;
Lujiang Xuee53f7492018-03-01 09:53:28 -080051import android.view.ContextThemeWrapper;
52import android.view.Gravity;
53import android.view.MotionEvent;
54import android.view.View;
55import android.view.ViewGroup;
56import android.view.Window;
57import android.view.WindowManager;
Lujiang Xuee53f7492018-03-01 09:53:28 -080058import android.widget.SeekBar;
59import android.widget.SeekBar.OnSeekBarChangeListener;
60
Lin Guoaea00232018-04-06 19:18:33 -070061import androidx.car.widget.ListItem;
62import androidx.car.widget.ListItemAdapter;
63import androidx.car.widget.ListItemAdapter.BackgroundStyle;
64import androidx.car.widget.ListItemProvider.ListProvider;
65import androidx.car.widget.PagedListView;
66import androidx.car.widget.SeekbarListItem;
67
Gus Prevasab336792018-11-14 13:52:20 -050068import com.android.systemui.R;
69import com.android.systemui.plugins.VolumeDialog;
70
Lin Guod7468d42018-05-02 14:25:10 -070071import org.xmlpull.v1.XmlPullParserException;
72
73import java.io.IOException;
Lujiang Xuee53f7492018-03-01 09:53:28 -080074import java.io.PrintWriter;
75import java.util.ArrayList;
Gus Prevasab336792018-11-14 13:52:20 -050076import java.util.Iterator;
Lujiang Xuee53f7492018-03-01 09:53:28 -080077import java.util.List;
78
Lujiang Xuee53f7492018-03-01 09:53:28 -080079/**
80 * Car version of the volume dialog.
81 *
Lujiang Xuee53f7492018-03-01 09:53:28 -080082 * Methods ending in "H" must be called on the (ui) handler.
83 */
84public class CarVolumeDialogImpl implements VolumeDialog {
Lin Guod7468d42018-05-02 14:25:10 -070085 private static final String TAG = Util.logTag(CarVolumeDialogImpl.class);
Lujiang Xuee53f7492018-03-01 09:53:28 -080086
Lin Guod7468d42018-05-02 14:25:10 -070087 private static final String XML_TAG_VOLUME_ITEMS = "carVolumeItems";
88 private static final String XML_TAG_VOLUME_ITEM = "item";
89 private static final int HOVERING_TIMEOUT = 16000;
90 private static final int NORMAL_TIMEOUT = 3000;
91 private static final int LISTVIEW_ANIMATION_DURATION_IN_MILLIS = 250;
92 private static final int DISMISS_DELAY_IN_MILLIS = 50;
93 private static final int ARROW_FADE_IN_START_DELAY_IN_MILLIS = 100;
Lujiang Xuee53f7492018-03-01 09:53:28 -080094
Lin Guod7468d42018-05-02 14:25:10 -070095 private final Context mContext;
96 private final H mHandler = new H();
Lujiang Xuee53f7492018-03-01 09:53:28 -080097
Lin Guod7468d42018-05-02 14:25:10 -070098 private Window mWindow;
99 private CustomDialog mDialog;
100 private PagedListView mListView;
101 private ListItemAdapter mPagedListAdapter;
102 // All the volume items.
103 private final SparseArray<VolumeItem> mVolumeItems = new SparseArray<>();
104 // Available volume items in car audio manager.
105 private final List<VolumeItem> mAvailableVolumeItems = new ArrayList<>();
106 // Volume items in the PagedListView.
107 private final List<ListItem> mVolumeLineItems = new ArrayList<>();
108 private final KeyguardManager mKeyguard;
Lujiang Xuee53f7492018-03-01 09:53:28 -0800109
Lin Guod7468d42018-05-02 14:25:10 -0700110 private Car mCar;
111 private CarAudioManager mCarAudioManager;
Lujiang Xuee53f7492018-03-01 09:53:28 -0800112
Lin Guod7468d42018-05-02 14:25:10 -0700113 private boolean mHovering;
114 private boolean mShowing;
115 private boolean mExpanded;
Lin Guoaea00232018-04-06 19:18:33 -0700116
Lin Guod7468d42018-05-02 14:25:10 -0700117 public CarVolumeDialogImpl(Context context) {
118 mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
119 mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
120 mCar = Car.createCar(mContext, mServiceConnection);
121 }
122
123 public void init(int windowType, Callback callback) {
124 initDialog();
125
126 mCar.connect();
127 }
128
129 @Override
130 public void destroy() {
131 mHandler.removeCallbacksAndMessages(null);
132
133 cleanupAudioManager();
134 // unregisterVolumeCallback is not being called when disconnect car, so we manually cleanup
135 // audio manager beforehand.
136 mCar.disconnect();
137 }
138
139 private void initDialog() {
140 loadAudioUsageItems();
141 mVolumeLineItems.clear();
142 mDialog = new CustomDialog(mContext);
143
144 mHovering = false;
145 mShowing = false;
146 mExpanded = false;
147 mWindow = mDialog.getWindow();
148 mWindow.requestFeature(Window.FEATURE_NO_TITLE);
149 mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
150 mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND
151 | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
152 mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
153 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
154 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
155 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
156 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
157 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
158 mWindow.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
159 mWindow.setWindowAnimations(com.android.internal.R.style.Animation_Toast);
160 final WindowManager.LayoutParams lp = mWindow.getAttributes();
161 lp.format = PixelFormat.TRANSLUCENT;
162 lp.setTitle(VolumeDialogImpl.class.getSimpleName());
163 lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
164 lp.windowAnimations = -1;
165 mWindow.setAttributes(lp);
166 mWindow.setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
167
168 mDialog.setCanceledOnTouchOutside(true);
169 mDialog.setContentView(R.layout.car_volume_dialog);
170 mDialog.setOnShowListener(dialog -> {
171 mListView.setTranslationY(-mListView.getHeight());
172 mListView.setAlpha(0);
173 mListView.animate()
174 .alpha(1)
175 .translationY(0)
176 .setDuration(LISTVIEW_ANIMATION_DURATION_IN_MILLIS)
177 .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator())
178 .start();
179 });
180 mListView = (PagedListView) mWindow.findViewById(R.id.volume_list);
181 mListView.setOnHoverListener((v, event) -> {
182 int action = event.getActionMasked();
183 mHovering = (action == MotionEvent.ACTION_HOVER_ENTER)
184 || (action == MotionEvent.ACTION_HOVER_MOVE);
185 rescheduleTimeoutH();
186 return true;
187 });
188
189 mPagedListAdapter = new ListItemAdapter(mContext, new ListProvider(mVolumeLineItems),
190 BackgroundStyle.PANEL);
191 mListView.setAdapter(mPagedListAdapter);
192 mListView.setMaxPages(PagedListView.UNLIMITED_PAGES);
193 }
194
195 public void show(int reason) {
Lin Guo1e7b32f2018-06-08 15:15:48 -0700196 mHandler.obtainMessage(H.SHOW, reason).sendToTarget();
Lin Guod7468d42018-05-02 14:25:10 -0700197 }
198
199 public void dismiss(int reason) {
Lin Guo1e7b32f2018-06-08 15:15:48 -0700200 mHandler.obtainMessage(H.DISMISS, reason).sendToTarget();
Lin Guod7468d42018-05-02 14:25:10 -0700201 }
202
203 private void showH(int reason) {
204 if (D.BUG) {
205 Log.d(TAG, "showH r=" + Events.DISMISS_REASONS[reason]);
Lujiang Xuee53f7492018-03-01 09:53:28 -0800206 }
207
Lin Guod7468d42018-05-02 14:25:10 -0700208 mHandler.removeMessages(H.SHOW);
209 mHandler.removeMessages(H.DISMISS);
210 rescheduleTimeoutH();
211 // Refresh the data set before showing.
212 mPagedListAdapter.notifyDataSetChanged();
213 if (mShowing) {
214 return;
215 }
216 mShowing = true;
Lujiang Xuee53f7492018-03-01 09:53:28 -0800217
Lin Guod7468d42018-05-02 14:25:10 -0700218 mDialog.show();
219 Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
220 }
221
222 protected void rescheduleTimeoutH() {
223 mHandler.removeMessages(H.DISMISS);
224 final int timeout = computeTimeoutH();
225 mHandler.sendMessageDelayed(mHandler
Lin Guo1e7b32f2018-06-08 15:15:48 -0700226 .obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT), timeout);
Lin Guod7468d42018-05-02 14:25:10 -0700227
228 if (D.BUG) {
229 Log.d(TAG, "rescheduleTimeout " + timeout + " " + Debug.getCaller());
230 }
231 }
232
233 private int computeTimeoutH() {
234 return mHovering ? HOVERING_TIMEOUT : NORMAL_TIMEOUT;
235 }
236
237 protected void dismissH(int reason) {
238 if (D.BUG) {
239 Log.d(TAG, "dismissH r=" + Events.DISMISS_REASONS[reason]);
240 }
241
242 mHandler.removeMessages(H.DISMISS);
243 mHandler.removeMessages(H.SHOW);
244 if (!mShowing) {
245 return;
246 }
247
248 mListView.animate().cancel();
Lin Guod7468d42018-05-02 14:25:10 -0700249
250 mListView.setTranslationY(0);
251 mListView.setAlpha(1);
252 mListView.animate()
253 .alpha(0)
254 .translationY(-mListView.getHeight())
255 .setDuration(LISTVIEW_ANIMATION_DURATION_IN_MILLIS)
256 .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
257 .withEndAction(() -> mHandler.postDelayed(() -> {
258 if (D.BUG) {
259 Log.d(TAG, "mDialog.dismiss()");
260 }
261 mDialog.dismiss();
Lin Guo1e7b32f2018-06-08 15:15:48 -0700262 mShowing = false;
Lin Guod7468d42018-05-02 14:25:10 -0700263 }, DISMISS_DELAY_IN_MILLIS))
264 .start();
265
266 Events.writeEvent(mContext, Events.EVENT_DISMISS_DIALOG, reason);
267 }
268
269 public void dump(PrintWriter writer) {
270 writer.println(VolumeDialogImpl.class.getSimpleName() + " state:");
271 writer.print(" mShowing: "); writer.println(mShowing);
272 }
273
274 private void loadAudioUsageItems() {
275 try (XmlResourceParser parser = mContext.getResources().getXml(R.xml.car_volume_items)) {
276 AttributeSet attrs = Xml.asAttributeSet(parser);
277 int type;
278 // Traverse to the first start tag
279 while ((type=parser.next()) != XmlResourceParser.END_DOCUMENT
280 && type != XmlResourceParser.START_TAG) {
281 }
282
283 if (!XML_TAG_VOLUME_ITEMS.equals(parser.getName())) {
284 throw new RuntimeException("Meta-data does not start with carVolumeItems tag");
285 }
286 int outerDepth = parser.getDepth();
287 int rank = 0;
288 while ((type=parser.next()) != XmlResourceParser.END_DOCUMENT
289 && (type != XmlResourceParser.END_TAG || parser.getDepth() > outerDepth)) {
290 if (type == XmlResourceParser.END_TAG) {
291 continue;
292 }
293 if (XML_TAG_VOLUME_ITEM.equals(parser.getName())) {
294 TypedArray item = mContext.getResources().obtainAttributes(
295 attrs, R.styleable.carVolumeItems_item);
296 int usage = item.getInt(R.styleable.carVolumeItems_item_usage, -1);
297 if (usage >= 0) {
298 VolumeItem volumeItem = new VolumeItem();
299 volumeItem.usage = usage;
300 volumeItem.rank = rank;
301 volumeItem.icon = item.getResourceId(R.styleable.carVolumeItems_item_icon, 0);
302 mVolumeItems.put(usage, volumeItem);
303 rank++;
304 }
305 item.recycle();
306 }
307 }
308 } catch (XmlPullParserException | IOException e) {
309 Log.e(TAG, "Error parsing volume groups configuration", e);
310 }
311 }
312
313 private VolumeItem getVolumeItemForUsages(int[] usages) {
314 int rank = Integer.MAX_VALUE;
315 VolumeItem result = null;
316 for (int usage : usages) {
317 VolumeItem volumeItem = mVolumeItems.get(usage);
318 if (volumeItem.rank < rank) {
319 rank = volumeItem.rank;
320 result = volumeItem;
321 }
322 }
323 return result;
324 }
325
326 private static int getSeekbarValue(CarAudioManager carAudioManager, int volumeGroupId) {
327 try {
328 return carAudioManager.getGroupVolume(volumeGroupId);
329 } catch (CarNotConnectedException e) {
330 Log.e(TAG, "Car is not connected!", e);
331 }
332 return 0;
333 }
334
335 private static int getMaxSeekbarValue(CarAudioManager carAudioManager, int volumeGroupId) {
336 try {
337 return carAudioManager.getGroupMaxVolume(volumeGroupId);
338 } catch (CarNotConnectedException e) {
339 Log.e(TAG, "Car is not connected!", e);
340 }
341 return 0;
342 }
343
344 private SeekbarListItem addSeekbarListItem(VolumeItem volumeItem, int volumeGroupId,
345 int supplementalIconId, @Nullable View.OnClickListener supplementalIconOnClickListener) {
346 SeekbarListItem listItem = new SeekbarListItem(mContext);
347 listItem.setMax(getMaxSeekbarValue(mCarAudioManager, volumeGroupId));
Lin Guoa412b452018-05-24 17:31:50 -0700348 int color = mContext.getResources().getColor(R.color.car_volume_dialog_tint);
Lin Guod7468d42018-05-02 14:25:10 -0700349 int progress = getSeekbarValue(mCarAudioManager, volumeGroupId);
350 listItem.setProgress(progress);
351 listItem.setOnSeekBarChangeListener(
352 new CarVolumeDialogImpl.VolumeSeekBarChangeListener(volumeGroupId, mCarAudioManager));
Lin Guoa412b452018-05-24 17:31:50 -0700353 Drawable primaryIcon = mContext.getResources().getDrawable(volumeItem.icon);
Brad Stenning754f8c92018-06-19 15:03:46 -0700354 primaryIcon.mutate().setTint(color);
Lin Guoa412b452018-05-24 17:31:50 -0700355 listItem.setPrimaryActionIcon(primaryIcon);
Lin Guod7468d42018-05-02 14:25:10 -0700356 if (supplementalIconId != 0) {
357 Drawable supplementalIcon = mContext.getResources().getDrawable(supplementalIconId);
Brad Stenning754f8c92018-06-19 15:03:46 -0700358 supplementalIcon.mutate().setTint(color);
Anthony Chendf941292018-09-11 15:35:50 -0700359 listItem.setSupplementalIcon(supplementalIcon, true);
360 listItem.setSupplementalIconListener(supplementalIconOnClickListener);
Lin Guod7468d42018-05-02 14:25:10 -0700361 } else {
362 listItem.setSupplementalEmptyIcon(true);
Anthony Chendf941292018-09-11 15:35:50 -0700363 listItem.setSupplementalIconListener(null);
Lin Guod7468d42018-05-02 14:25:10 -0700364 }
365
366 mVolumeLineItems.add(listItem);
367 volumeItem.listItem = listItem;
368 volumeItem.progress = progress;
369 return listItem;
370 }
371
372 private VolumeItem findVolumeItem(SeekbarListItem targetItem) {
373 for (int i = 0; i < mVolumeItems.size(); ++i) {
374 VolumeItem volumeItem = mVolumeItems.valueAt(i);
375 if (volumeItem.listItem == targetItem) {
376 return volumeItem;
377 }
378 }
379 return null;
380 }
381
382 private void cleanupAudioManager() {
383 try {
384 mCarAudioManager.unregisterVolumeCallback(mVolumeChangeCallback.asBinder());
385 } catch (CarNotConnectedException e) {
386 Log.e(TAG, "Car is not connected!", e);
387 }
388 mVolumeLineItems.clear();
389 mCarAudioManager = null;
390 }
391
392 private final class H extends Handler {
393 private static final int SHOW = 1;
394 private static final int DISMISS = 2;
395
396 public H() {
397 super(Looper.getMainLooper());
Lujiang Xuee53f7492018-03-01 09:53:28 -0800398 }
399
400 @Override
Lin Guod7468d42018-05-02 14:25:10 -0700401 public void handleMessage(Message msg) {
402 switch (msg.what) {
403 case SHOW:
404 showH(msg.arg1);
405 break;
406 case DISMISS:
407 dismissH(msg.arg1);
408 break;
409 default:
410 }
411 }
412 }
413
414 private final class CustomDialog extends Dialog implements DialogInterface {
415 public CustomDialog(Context context) {
416 super(context, com.android.systemui.R.style.qs_theme);
Lujiang Xuee53f7492018-03-01 09:53:28 -0800417 }
418
Lin Guod7468d42018-05-02 14:25:10 -0700419 @Override
420 public boolean dispatchTouchEvent(MotionEvent ev) {
421 rescheduleTimeoutH();
422 return super.dispatchTouchEvent(ev);
423 }
Lujiang Xuee53f7492018-03-01 09:53:28 -0800424
Lin Guod7468d42018-05-02 14:25:10 -0700425 @Override
426 protected void onStart() {
427 super.setCanceledOnTouchOutside(true);
428 super.onStart();
429 }
430
431 @Override
432 protected void onStop() {
433 super.onStop();
434 }
435
436 @Override
437 public boolean onTouchEvent(MotionEvent event) {
438 if (isShowing()) {
439 if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
Lin Guo1e7b32f2018-06-08 15:15:48 -0700440 mHandler.obtainMessage(
441 H.DISMISS, Events.DISMISS_REASON_TOUCH_OUTSIDE).sendToTarget();
Lin Guod7468d42018-05-02 14:25:10 -0700442 return true;
443 }
444 }
445 return false;
446 }
447 }
448
449 private final class ExpandIconListener implements View.OnClickListener {
450 @Override
451 public void onClick(final View v) {
452 mExpanded = !mExpanded;
453 Animator inAnimator;
454 if (mExpanded) {
455 for (int groupId = 0; groupId < mAvailableVolumeItems.size(); ++groupId) {
456 // Adding the items which are not coming from the default item.
457 VolumeItem volumeItem = mAvailableVolumeItems.get(groupId);
458 if (volumeItem.defaultItem) {
459 // Set progress here due to the progress of seekbar may not be updated.
460 volumeItem.listItem.setProgress(volumeItem.progress);
461 } else {
462 addSeekbarListItem(volumeItem, groupId, 0, null);
463 }
464 }
465 inAnimator = AnimatorInflater.loadAnimator(
466 mContext, R.anim.car_arrow_fade_in_rotate_up);
467 } else {
468 // Only keeping the default stream if it is not expended.
469 Iterator itr = mVolumeLineItems.iterator();
470 while (itr.hasNext()) {
471 SeekbarListItem seekbarListItem = (SeekbarListItem) itr.next();
472 VolumeItem volumeItem = findVolumeItem(seekbarListItem);
473 if (!volumeItem.defaultItem) {
474 itr.remove();
475 } else {
476 // Set progress here due to the progress of seekbar may not be updated.
477 seekbarListItem.setProgress(volumeItem.progress);
478 }
479 }
480 inAnimator = AnimatorInflater.loadAnimator(
481 mContext, R.anim.car_arrow_fade_in_rotate_down);
482 }
483
484 Animator outAnimator = AnimatorInflater.loadAnimator(
485 mContext, R.anim.car_arrow_fade_out);
486 inAnimator.setStartDelay(ARROW_FADE_IN_START_DELAY_IN_MILLIS);
487 AnimatorSet animators = new AnimatorSet();
488 animators.playTogether(outAnimator, inAnimator);
489 animators.setTarget(v);
490 animators.start();
491 mPagedListAdapter.notifyDataSetChanged();
492 }
493 }
494
495 private final class VolumeSeekBarChangeListener implements OnSeekBarChangeListener {
496 private final int mVolumeGroupId;
497 private final CarAudioManager mCarAudioManager;
498
499 private VolumeSeekBarChangeListener(int volumeGroupId, CarAudioManager carAudioManager) {
500 mVolumeGroupId = volumeGroupId;
501 mCarAudioManager = carAudioManager;
502 }
503
504 @Override
505 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
506 if (!fromUser) {
507 // For instance, if this event is originated from AudioService,
508 // we can ignore it as it has already been handled and doesn't need to be
509 // sent back down again.
510 return;
511 }
512 try {
513 if (mCarAudioManager == null) {
514 Log.w(TAG, "Ignoring volume change event because the car isn't connected");
515 return;
516 }
517 mAvailableVolumeItems.get(mVolumeGroupId).progress = progress;
518 mCarAudioManager.setGroupVolume(mVolumeGroupId, progress, 0);
519 } catch (CarNotConnectedException e) {
520 Log.e(TAG, "Car is not connected!", e);
521 }
522 }
523
524 @Override
525 public void onStartTrackingTouch(SeekBar seekBar) {}
526
527 @Override
528 public void onStopTrackingTouch(SeekBar seekBar) {}
529 }
530
531 private final ICarVolumeCallback mVolumeChangeCallback = new ICarVolumeCallback.Stub() {
532 @Override
Hongwei Wangc8cdc592018-06-07 12:35:08 -0700533 public void onGroupVolumeChanged(int groupId, int flags) {
Lin Guod7468d42018-05-02 14:25:10 -0700534 VolumeItem volumeItem = mAvailableVolumeItems.get(groupId);
535 int value = getSeekbarValue(mCarAudioManager, groupId);
536 // Do not update the progress if it is the same as before. When car audio manager sets its
537 // group volume caused by the seekbar progress changed, it also triggers this callback.
538 // Updating the seekbar at the same time could block the continuous seeking.
539 if (value != volumeItem.progress) {
540 volumeItem.listItem.setProgress(value);
541 volumeItem.progress = value;
Priyanke5c1db92018-11-01 17:49:54 -0700542 }
543 if ((flags & AudioManager.FLAG_SHOW_UI) != 0) {
544 show(Events.SHOW_REASON_VOLUME_CHANGED);
Lin Guod7468d42018-05-02 14:25:10 -0700545 }
546 }
547
548 @Override
Hongwei Wangc8cdc592018-06-07 12:35:08 -0700549 public void onMasterMuteChanged(int flags) {
Lin Guod7468d42018-05-02 14:25:10 -0700550 // ignored
551 }
552 };
553
554 private final ServiceConnection mServiceConnection = new ServiceConnection() {
555 @Override
556 public void onServiceConnected(ComponentName name, IBinder service) {
557 try {
Lin Guoaea00232018-04-06 19:18:33 -0700558 mExpanded = false;
Lin Guod7468d42018-05-02 14:25:10 -0700559 mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE);
560 int volumeGroupCount = mCarAudioManager.getVolumeGroupCount();
561 // Populates volume slider items from volume groups to UI.
562 for (int groupId = 0; groupId < volumeGroupCount; groupId++) {
563 VolumeItem volumeItem = getVolumeItemForUsages(
564 mCarAudioManager.getUsagesForVolumeGroupId(groupId));
565 mAvailableVolumeItems.add(volumeItem);
566 // The first one is the default item.
567 if (groupId == 0) {
568 volumeItem.defaultItem = true;
569 addSeekbarListItem(volumeItem, groupId, R.drawable.car_ic_keyboard_arrow_down,
570 new ExpandIconListener());
571 }
572 }
Lujiang Xuee53f7492018-03-01 09:53:28 -0800573
Lin Guod7468d42018-05-02 14:25:10 -0700574 // If list is already initiated, update its content.
575 if (mPagedListAdapter != null) {
576 mPagedListAdapter.notifyDataSetChanged();
577 }
578 mCarAudioManager.registerVolumeCallback(mVolumeChangeCallback.asBinder());
579 } catch (CarNotConnectedException e) {
580 Log.e(TAG, "Car is not connected!", e);
581 }
Lujiang Xuee53f7492018-03-01 09:53:28 -0800582 }
583
Lin Guod7468d42018-05-02 14:25:10 -0700584 /**
585 * This does not get called when service is properly disconnected.
586 * So we need to also handle cleanups in destroy().
587 */
588 @Override
589 public void onServiceDisconnected(ComponentName name) {
590 cleanupAudioManager();
Lujiang Xuee53f7492018-03-01 09:53:28 -0800591 }
Lin Guod7468d42018-05-02 14:25:10 -0700592 };
Lujiang Xuee53f7492018-03-01 09:53:28 -0800593
Lin Guod7468d42018-05-02 14:25:10 -0700594 /**
595 * Wrapper class which contains information of each volume group.
596 */
597 private static class VolumeItem {
598 private @AudioAttributes.AttributeUsage int usage;
599 private int rank;
600 private boolean defaultItem = false;
601 private @DrawableRes int icon;
602 private SeekbarListItem listItem;
603 private int progress;
604 }
605}