1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.car.volume; 18 19 import static android.car.media.CarAudioManager.AUDIO_FEATURE_VOLUME_GROUP_MUTING; 20 import static android.car.media.CarAudioManager.PRIMARY_AUDIO_ZONE; 21 22 import android.animation.Animator; 23 import android.animation.AnimatorInflater; 24 import android.animation.AnimatorSet; 25 import android.annotation.DrawableRes; 26 import android.annotation.Nullable; 27 import android.app.Dialog; 28 import android.app.KeyguardManager; 29 import android.car.Car; 30 import android.car.media.CarAudioManager; 31 import android.content.BroadcastReceiver; 32 import android.content.Context; 33 import android.content.DialogInterface; 34 import android.content.Intent; 35 import android.content.IntentFilter; 36 import android.content.res.TypedArray; 37 import android.content.res.XmlResourceParser; 38 import android.graphics.Color; 39 import android.graphics.PixelFormat; 40 import android.graphics.drawable.ColorDrawable; 41 import android.graphics.drawable.Drawable; 42 import android.media.AudioManager; 43 import android.os.Build; 44 import android.os.Debug; 45 import android.os.Handler; 46 import android.os.Looper; 47 import android.os.Message; 48 import android.os.UserHandle; 49 import android.util.AttributeSet; 50 import android.util.Log; 51 import android.util.SparseArray; 52 import android.util.Xml; 53 import android.view.Gravity; 54 import android.view.MotionEvent; 55 import android.view.View; 56 import android.view.ViewGroup; 57 import android.view.Window; 58 import android.view.WindowManager; 59 import android.widget.SeekBar; 60 import android.widget.SeekBar.OnSeekBarChangeListener; 61 62 import androidx.recyclerview.widget.LinearLayoutManager; 63 import androidx.recyclerview.widget.RecyclerView; 64 65 import com.android.systemui.R; 66 import com.android.systemui.car.CarServiceProvider; 67 import com.android.systemui.plugins.VolumeDialog; 68 import com.android.systemui.volume.Events; 69 import com.android.systemui.volume.SystemUIInterpolators; 70 import com.android.systemui.volume.VolumeDialogImpl; 71 72 import org.xmlpull.v1.XmlPullParserException; 73 74 import java.io.IOException; 75 import java.util.ArrayList; 76 import java.util.List; 77 78 /** 79 * Car version of the volume dialog. 80 * 81 * Methods ending in "H" must be called on the (ui) handler. 82 */ 83 public class CarVolumeDialogImpl implements VolumeDialog { 84 85 private static final String TAG = "CarVolumeDialog"; 86 private static final boolean DEBUG = Build.IS_USERDEBUG || Build.IS_ENG; 87 88 private static final String XML_TAG_VOLUME_ITEMS = "carVolumeItems"; 89 private static final String XML_TAG_VOLUME_ITEM = "item"; 90 private static final int LISTVIEW_ANIMATION_DURATION_IN_MILLIS = 250; 91 private static final int DISMISS_DELAY_IN_MILLIS = 50; 92 private static final int ARROW_FADE_IN_START_DELAY_IN_MILLIS = 100; 93 94 private final Context mContext; 95 private final H mHandler = new H(); 96 // All the volume items. 97 private final SparseArray<VolumeItem> mVolumeItems = new SparseArray<>(); 98 // Available volume items in car audio manager. 99 private final List<VolumeItem> mAvailableVolumeItems = new ArrayList<>(); 100 // Volume items in the RecyclerView. 101 private final List<CarVolumeItem> mCarVolumeLineItems = new ArrayList<>(); 102 private final KeyguardManager mKeyguard; 103 private final int mNormalTimeout; 104 private final int mHoveringTimeout; 105 private final int mExpNormalTimeout; 106 private final int mExpHoveringTimeout; 107 private final CarServiceProvider mCarServiceProvider; 108 109 private Window mWindow; 110 private CustomDialog mDialog; 111 private RecyclerView mListView; 112 private CarVolumeItemAdapter mVolumeItemsAdapter; 113 private CarAudioManager mCarAudioManager; 114 private boolean mHovering; 115 private int mCurrentlyDisplayingGroupId; 116 private int mPreviouslyDisplayingGroupId; 117 private boolean mShowing; 118 private boolean mDismissing; 119 private boolean mExpanded; 120 private View mExpandIcon; 121 122 private final CarAudioManager.CarVolumeCallback mVolumeChangeCallback = 123 new CarAudioManager.CarVolumeCallback() { 124 @Override 125 public void onGroupVolumeChanged(int zoneId, int groupId, int flags) { 126 updateVolumeAndMute(zoneId, groupId, flags); 127 } 128 129 @Override 130 public void onMasterMuteChanged(int zoneId, int flags) { 131 // ignored 132 } 133 134 @Override 135 public void onGroupMuteChanged(int zoneId, int groupId, int flags) { 136 updateVolumeAndMute(zoneId, groupId, flags); 137 } 138 139 private void updateVolumeAndMute(int zoneId, int groupId, int flags) { 140 // TODO: Include zoneId into consideration. 141 VolumeItem volumeItem = mAvailableVolumeItems.get(groupId); 142 boolean muted = isGroupMuted(mCarAudioManager, groupId); 143 int value = getSeekbarValue(mCarAudioManager, groupId); 144 145 boolean isShowing = mCarVolumeLineItems.stream().anyMatch( 146 item -> item.getGroupId() == groupId); 147 148 if ((value != volumeItem.mProgress || muted != volumeItem.mIsMuted) 149 && isShowing) { 150 volumeItem.mCarVolumeItem.setProgress(value); 151 volumeItem.mProgress = value; 152 volumeItem.mCarVolumeItem.setIsMuted(muted); 153 volumeItem.mIsMuted = muted; 154 } 155 if ((flags & AudioManager.FLAG_SHOW_UI) != 0) { 156 mPreviouslyDisplayingGroupId = mCurrentlyDisplayingGroupId; 157 mCurrentlyDisplayingGroupId = groupId; 158 mHandler.obtainMessage(H.SHOW, 159 Events.SHOW_REASON_VOLUME_CHANGED).sendToTarget(); 160 } 161 } 162 }; 163 164 private final CarServiceProvider.CarServiceOnConnectedListener mCarServiceOnConnectedListener = 165 car -> { 166 mExpanded = false; 167 mCarAudioManager = (CarAudioManager) car.getCarManager(Car.AUDIO_SERVICE); 168 int volumeGroupCount = mCarAudioManager.getVolumeGroupCount(); 169 // Populates volume slider items from volume groups to UI. 170 for (int groupId = 0; groupId < volumeGroupCount; groupId++) { 171 VolumeItem volumeItem = getVolumeItemForUsages( 172 mCarAudioManager.getUsagesForVolumeGroupId(groupId)); 173 mAvailableVolumeItems.add(volumeItem); 174 // The first one is the default item. 175 if (groupId == 0) { 176 clearAllAndSetupDefaultCarVolumeLineItem(0); 177 } 178 } 179 180 // If list is already initiated, update its content. 181 if (mVolumeItemsAdapter != null) { 182 mVolumeItemsAdapter.notifyDataSetChanged(); 183 } 184 mCarAudioManager.registerCarVolumeCallback(mVolumeChangeCallback); 185 }; 186 187 private final BroadcastReceiver mHomeButtonPressedBroadcastReceiver = new BroadcastReceiver() { 188 @Override 189 public void onReceive(Context context, Intent intent) { 190 if (!intent.getAction().equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) { 191 return; 192 } 193 194 dismissH(Events.DISMISS_REASON_VOLUME_CONTROLLER); 195 } 196 }; 197 CarVolumeDialogImpl(Context context, CarServiceProvider carServiceProvider)198 public CarVolumeDialogImpl(Context context, CarServiceProvider carServiceProvider) { 199 mContext = context; 200 mCarServiceProvider = carServiceProvider; 201 mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); 202 mNormalTimeout = mContext.getResources().getInteger( 203 R.integer.car_volume_dialog_display_normal_timeout); 204 mHoveringTimeout = mContext.getResources().getInteger( 205 R.integer.car_volume_dialog_display_hovering_timeout); 206 mExpNormalTimeout = mContext.getResources().getInteger( 207 R.integer.car_volume_dialog_display_expanded_normal_timeout); 208 mExpHoveringTimeout = mContext.getResources().getInteger( 209 R.integer.car_volume_dialog_display_expanded_hovering_timeout); 210 } 211 getSeekbarValue(CarAudioManager carAudioManager, int volumeGroupId)212 private static int getSeekbarValue(CarAudioManager carAudioManager, int volumeGroupId) { 213 return carAudioManager.getGroupVolume(volumeGroupId); 214 } 215 isGroupMuted(CarAudioManager carAudioManager, int volumeGroupId)216 private static boolean isGroupMuted(CarAudioManager carAudioManager, int volumeGroupId) { 217 if (!carAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_VOLUME_GROUP_MUTING)) { 218 return false; 219 } 220 return carAudioManager.isVolumeGroupMuted(PRIMARY_AUDIO_ZONE, volumeGroupId); 221 } 222 getMaxSeekbarValue(CarAudioManager carAudioManager, int volumeGroupId)223 private static int getMaxSeekbarValue(CarAudioManager carAudioManager, int volumeGroupId) { 224 return carAudioManager.getGroupMaxVolume(volumeGroupId); 225 } 226 227 /** 228 * Build the volume window and connect to the CarService which registers with car audio 229 * manager. 230 */ 231 @Override init(int windowType, Callback callback)232 public void init(int windowType, Callback callback) { 233 initDialog(); 234 mCarServiceProvider.addListener(mCarServiceOnConnectedListener); 235 236 mContext.registerReceiverAsUser(mHomeButtonPressedBroadcastReceiver, UserHandle.CURRENT, 237 new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), /* broadcastPermission= */ 238 null, /* scheduler= */ null); 239 } 240 241 @Override destroy()242 public void destroy() { 243 mHandler.removeCallbacksAndMessages(/* token= */ null); 244 245 mContext.unregisterReceiver(mHomeButtonPressedBroadcastReceiver); 246 247 cleanupAudioManager(); 248 } 249 250 /** 251 * Reveals volume dialog. 252 */ show(int reason)253 public void show(int reason) { 254 mHandler.obtainMessage(H.SHOW, reason).sendToTarget(); 255 } 256 257 /** 258 * Hides volume dialog. 259 */ dismiss(int reason)260 public void dismiss(int reason) { 261 mHandler.obtainMessage(H.DISMISS, reason).sendToTarget(); 262 } 263 initDialog()264 private void initDialog() { 265 loadAudioUsageItems(); 266 mCarVolumeLineItems.clear(); 267 mDialog = new CustomDialog(mContext); 268 269 mHovering = false; 270 mShowing = false; 271 mDismissing = false; 272 mExpanded = false; 273 mWindow = mDialog.getWindow(); 274 mWindow.requestFeature(Window.FEATURE_NO_TITLE); 275 mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); 276 mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND 277 | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); 278 mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 279 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 280 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 281 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED 282 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH 283 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); 284 mWindow.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY); 285 mWindow.setWindowAnimations(com.android.internal.R.style.Animation_Toast); 286 final WindowManager.LayoutParams lp = mWindow.getAttributes(); 287 lp.format = PixelFormat.TRANSLUCENT; 288 lp.setTitle(VolumeDialogImpl.class.getSimpleName()); 289 lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL; 290 lp.windowAnimations = -1; 291 mWindow.setAttributes(lp); 292 293 mDialog.setContentView(R.layout.car_volume_dialog); 294 mWindow.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 295 296 mDialog.setCanceledOnTouchOutside(true); 297 mDialog.setOnShowListener(dialog -> { 298 mListView.setTranslationY(-mListView.getHeight()); 299 mListView.setAlpha(0); 300 mListView.animate() 301 .alpha(1) 302 .translationY(0) 303 .setDuration(LISTVIEW_ANIMATION_DURATION_IN_MILLIS) 304 .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator()) 305 .start(); 306 }); 307 mListView = mWindow.findViewById(R.id.volume_list); 308 mListView.setOnHoverListener((v, event) -> { 309 int action = event.getActionMasked(); 310 mHovering = (action == MotionEvent.ACTION_HOVER_ENTER) 311 || (action == MotionEvent.ACTION_HOVER_MOVE); 312 rescheduleTimeoutH(); 313 return true; 314 }); 315 316 mVolumeItemsAdapter = new CarVolumeItemAdapter(mContext, mCarVolumeLineItems); 317 mListView.setAdapter(mVolumeItemsAdapter); 318 mListView.setLayoutManager(new LinearLayoutManager(mContext)); 319 } 320 321 showH(int reason)322 private void showH(int reason) { 323 if (DEBUG) { 324 Log.d(TAG, "showH r=" + Events.DISMISS_REASONS[reason]); 325 } 326 327 mHandler.removeMessages(H.SHOW); 328 mHandler.removeMessages(H.DISMISS); 329 330 rescheduleTimeoutH(); 331 332 // Refresh the data set before showing. 333 mVolumeItemsAdapter.notifyDataSetChanged(); 334 335 if (mShowing) { 336 if (mPreviouslyDisplayingGroupId == mCurrentlyDisplayingGroupId || mExpanded) { 337 return; 338 } 339 340 clearAllAndSetupDefaultCarVolumeLineItem(mCurrentlyDisplayingGroupId); 341 return; 342 } 343 344 mShowing = true; 345 clearAllAndSetupDefaultCarVolumeLineItem(mCurrentlyDisplayingGroupId); 346 mDialog.show(); 347 Events.writeEvent(Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked()); 348 } 349 clearAllAndSetupDefaultCarVolumeLineItem(int groupId)350 private void clearAllAndSetupDefaultCarVolumeLineItem(int groupId) { 351 mCarVolumeLineItems.clear(); 352 VolumeItem volumeItem = mAvailableVolumeItems.get(groupId); 353 volumeItem.mDefaultItem = true; 354 addCarVolumeListItem(volumeItem, /* volumeGroupId = */ groupId, 355 R.drawable.car_ic_keyboard_arrow_down, new ExpandIconListener()); 356 } 357 rescheduleTimeoutH()358 protected void rescheduleTimeoutH() { 359 mHandler.removeMessages(H.DISMISS); 360 final int timeout = computeTimeoutH(); 361 mHandler.sendMessageDelayed(mHandler 362 .obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT), timeout); 363 364 if (DEBUG) { 365 Log.d(TAG, "rescheduleTimeout " + timeout + " " + Debug.getCaller()); 366 } 367 } 368 computeTimeoutH()369 private int computeTimeoutH() { 370 if (mExpanded) { 371 return mHovering ? mExpHoveringTimeout : mExpNormalTimeout; 372 } else { 373 return mHovering ? mHoveringTimeout : mNormalTimeout; 374 } 375 } 376 dismissH(int reason)377 private void dismissH(int reason) { 378 if (DEBUG) { 379 Log.d(TAG, "dismissH r=" + Events.DISMISS_REASONS[reason]); 380 } 381 382 mHandler.removeMessages(H.DISMISS); 383 mHandler.removeMessages(H.SHOW); 384 if (!mShowing || mDismissing) { 385 return; 386 } 387 388 mDismissing = true; 389 mListView.animate() 390 .alpha(0) 391 .translationY(-mListView.getHeight()) 392 .setDuration(LISTVIEW_ANIMATION_DURATION_IN_MILLIS) 393 .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator()) 394 .withEndAction(() -> mHandler.postDelayed(() -> { 395 if (DEBUG) { 396 Log.d(TAG, "mDialog.dismiss()"); 397 } 398 mDialog.dismiss(); 399 mShowing = false; 400 mDismissing = false; 401 // if mExpandIcon is null that means user never clicked on the expanded arrow 402 // which implies that the dialog is still not expanded. In that case we do 403 // not want to reset the state 404 if (mExpandIcon != null && mExpanded) { 405 toggleDialogExpansion(/* isClicked = */ false); 406 } 407 }, DISMISS_DELAY_IN_MILLIS)) 408 .start(); 409 410 Events.writeEvent(Events.EVENT_DISMISS_DIALOG, reason); 411 } 412 loadAudioUsageItems()413 private void loadAudioUsageItems() { 414 if (DEBUG) { 415 Log.i(TAG, "loadAudioUsageItems start"); 416 } 417 418 try (XmlResourceParser parser = mContext.getResources().getXml(R.xml.car_volume_items)) { 419 AttributeSet attrs = Xml.asAttributeSet(parser); 420 int type; 421 // Traverse to the first start tag 422 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 423 && type != XmlResourceParser.START_TAG) { 424 // Do Nothing (moving parser to start element) 425 } 426 427 if (!XML_TAG_VOLUME_ITEMS.equals(parser.getName())) { 428 throw new RuntimeException("Meta-data does not start with carVolumeItems tag"); 429 } 430 int outerDepth = parser.getDepth(); 431 int rank = 0; 432 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 433 && (type != XmlResourceParser.END_TAG || parser.getDepth() > outerDepth)) { 434 if (type == XmlResourceParser.END_TAG) { 435 continue; 436 } 437 if (XML_TAG_VOLUME_ITEM.equals(parser.getName())) { 438 TypedArray item = mContext.getResources().obtainAttributes( 439 attrs, R.styleable.carVolumeItems_item); 440 int usage = item.getInt(R.styleable.carVolumeItems_item_usage, 441 /* defValue= */ -1); 442 if (usage >= 0) { 443 VolumeItem volumeItem = new VolumeItem(); 444 volumeItem.mRank = rank; 445 volumeItem.mIcon = item.getResourceId( 446 R.styleable.carVolumeItems_item_icon, /* defValue= */ 0); 447 volumeItem.mMuteIcon = item.getResourceId( 448 R.styleable.carVolumeItems_item_mute_icon, /* defValue= */ 0); 449 mVolumeItems.put(usage, volumeItem); 450 rank++; 451 } 452 item.recycle(); 453 } 454 } 455 } catch (XmlPullParserException | IOException e) { 456 Log.e(TAG, "Error parsing volume groups configuration", e); 457 } 458 459 if (DEBUG) { 460 Log.i(TAG, 461 "loadAudioUsageItems finished. Number of volume items: " + mVolumeItems.size()); 462 } 463 } 464 getVolumeItemForUsages(int[] usages)465 private VolumeItem getVolumeItemForUsages(int[] usages) { 466 int rank = Integer.MAX_VALUE; 467 VolumeItem result = null; 468 for (int usage : usages) { 469 VolumeItem volumeItem = mVolumeItems.get(usage); 470 if (DEBUG) { 471 Log.i(TAG, "getVolumeItemForUsage: " + usage + ": " + volumeItem); 472 } 473 if (volumeItem.mRank < rank) { 474 rank = volumeItem.mRank; 475 result = volumeItem; 476 } 477 } 478 return result; 479 } 480 createCarVolumeListItem(VolumeItem volumeItem, int volumeGroupId, Drawable supplementalIcon, int seekbarProgressValue, boolean isMuted, @Nullable View.OnClickListener supplementalIconOnClickListener)481 private CarVolumeItem createCarVolumeListItem(VolumeItem volumeItem, int volumeGroupId, 482 Drawable supplementalIcon, int seekbarProgressValue, 483 boolean isMuted, @Nullable View.OnClickListener supplementalIconOnClickListener) { 484 CarVolumeItem carVolumeItem = new CarVolumeItem(); 485 carVolumeItem.setMax(getMaxSeekbarValue(mCarAudioManager, volumeGroupId)); 486 carVolumeItem.setProgress(seekbarProgressValue); 487 carVolumeItem.setIsMuted(isMuted); 488 carVolumeItem.setOnSeekBarChangeListener( 489 new CarVolumeDialogImpl.VolumeSeekBarChangeListener(volumeGroupId, 490 mCarAudioManager)); 491 carVolumeItem.setGroupId(volumeGroupId); 492 493 int color = mContext.getColor(R.color.car_volume_dialog_tint); 494 Drawable primaryIcon = mContext.getDrawable(volumeItem.mIcon); 495 primaryIcon.mutate().setTint(color); 496 carVolumeItem.setPrimaryIcon(primaryIcon); 497 498 Drawable primaryMuteIcon = mContext.getDrawable(volumeItem.mMuteIcon); 499 primaryMuteIcon.mutate().setTint(color); 500 carVolumeItem.setPrimaryMuteIcon(primaryMuteIcon); 501 502 if (supplementalIcon != null) { 503 supplementalIcon.mutate().setTint(color); 504 carVolumeItem.setSupplementalIcon(supplementalIcon, 505 /* showSupplementalIconDivider= */ true); 506 carVolumeItem.setSupplementalIconListener(supplementalIconOnClickListener); 507 } else { 508 carVolumeItem.setSupplementalIcon(/* drawable= */ null, 509 /* showSupplementalIconDivider= */ false); 510 } 511 512 volumeItem.mCarVolumeItem = carVolumeItem; 513 volumeItem.mProgress = seekbarProgressValue; 514 515 return carVolumeItem; 516 } 517 addCarVolumeListItem(VolumeItem volumeItem, int volumeGroupId, int supplementalIconId, @Nullable View.OnClickListener supplementalIconOnClickListener)518 private CarVolumeItem addCarVolumeListItem(VolumeItem volumeItem, int volumeGroupId, 519 int supplementalIconId, 520 @Nullable View.OnClickListener supplementalIconOnClickListener) { 521 int seekbarProgressValue = getSeekbarValue(mCarAudioManager, volumeGroupId); 522 boolean isMuted = isGroupMuted(mCarAudioManager, volumeGroupId); 523 Drawable supplementalIcon = supplementalIconId == 0 ? null : mContext.getDrawable( 524 supplementalIconId); 525 CarVolumeItem carVolumeItem = createCarVolumeListItem(volumeItem, volumeGroupId, 526 supplementalIcon, seekbarProgressValue, isMuted, supplementalIconOnClickListener); 527 mCarVolumeLineItems.add(carVolumeItem); 528 return carVolumeItem; 529 } 530 cleanupAudioManager()531 private void cleanupAudioManager() { 532 mCarAudioManager.unregisterCarVolumeCallback(mVolumeChangeCallback); 533 mCarVolumeLineItems.clear(); 534 mCarAudioManager = null; 535 } 536 537 /** 538 * Wrapper class which contains information of each volume group. 539 */ 540 private static class VolumeItem { 541 private int mRank; 542 private boolean mDefaultItem = false; 543 @DrawableRes 544 private int mIcon; 545 @DrawableRes 546 private int mMuteIcon; 547 private CarVolumeItem mCarVolumeItem; 548 private int mProgress; 549 private boolean mIsMuted; 550 } 551 552 private final class H extends Handler { 553 554 private static final int SHOW = 1; 555 private static final int DISMISS = 2; 556 H()557 private H() { 558 super(Looper.getMainLooper()); 559 } 560 561 @Override handleMessage(Message msg)562 public void handleMessage(Message msg) { 563 switch (msg.what) { 564 case SHOW: 565 showH(msg.arg1); 566 break; 567 case DISMISS: 568 dismissH(msg.arg1); 569 break; 570 default: 571 } 572 } 573 } 574 575 private final class CustomDialog extends Dialog implements DialogInterface { 576 CustomDialog(Context context)577 private CustomDialog(Context context) { 578 super(context, com.android.systemui.R.style.Theme_SystemUI); 579 } 580 581 @Override dispatchTouchEvent(MotionEvent ev)582 public boolean dispatchTouchEvent(MotionEvent ev) { 583 rescheduleTimeoutH(); 584 return super.dispatchTouchEvent(ev); 585 } 586 587 @Override onStart()588 protected void onStart() { 589 super.setCanceledOnTouchOutside(true); 590 super.onStart(); 591 } 592 593 @Override onStop()594 protected void onStop() { 595 super.onStop(); 596 } 597 598 @Override onTouchEvent(MotionEvent event)599 public boolean onTouchEvent(MotionEvent event) { 600 if (isShowing()) { 601 if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { 602 mHandler.obtainMessage( 603 H.DISMISS, Events.DISMISS_REASON_TOUCH_OUTSIDE).sendToTarget(); 604 return true; 605 } 606 } 607 return false; 608 } 609 } 610 611 private final class ExpandIconListener implements View.OnClickListener { 612 @Override onClick(final View v)613 public void onClick(final View v) { 614 mExpandIcon = v; 615 toggleDialogExpansion(true); 616 rescheduleTimeoutH(); 617 } 618 } 619 toggleDialogExpansion(boolean isClicked)620 private void toggleDialogExpansion(boolean isClicked) { 621 mExpanded = !mExpanded; 622 Animator inAnimator; 623 if (mExpanded) { 624 for (int groupId = 0; groupId < mAvailableVolumeItems.size(); ++groupId) { 625 if (groupId != mCurrentlyDisplayingGroupId) { 626 VolumeItem volumeItem = mAvailableVolumeItems.get(groupId); 627 addCarVolumeListItem(volumeItem, groupId, /* supplementalIconId= */ 0, 628 /* supplementalIconOnClickListener= */ null); 629 } 630 } 631 inAnimator = AnimatorInflater.loadAnimator( 632 mContext, R.anim.car_arrow_fade_in_rotate_up); 633 634 } else { 635 clearAllAndSetupDefaultCarVolumeLineItem(mCurrentlyDisplayingGroupId); 636 inAnimator = AnimatorInflater.loadAnimator( 637 mContext, R.anim.car_arrow_fade_in_rotate_down); 638 } 639 640 Animator outAnimator = AnimatorInflater.loadAnimator( 641 mContext, R.anim.car_arrow_fade_out); 642 inAnimator.setStartDelay(ARROW_FADE_IN_START_DELAY_IN_MILLIS); 643 AnimatorSet animators = new AnimatorSet(); 644 animators.playTogether(outAnimator, inAnimator); 645 if (!isClicked) { 646 // Do not animate when the state is called to reset the dialogs view and not clicked 647 // by user. 648 animators.setDuration(0); 649 } 650 animators.setTarget(mExpandIcon); 651 animators.start(); 652 mVolumeItemsAdapter.notifyDataSetChanged(); 653 } 654 655 private final class VolumeSeekBarChangeListener implements OnSeekBarChangeListener { 656 657 private final int mVolumeGroupId; 658 private final CarAudioManager mCarAudioManager; 659 VolumeSeekBarChangeListener(int volumeGroupId, CarAudioManager carAudioManager)660 private VolumeSeekBarChangeListener(int volumeGroupId, CarAudioManager carAudioManager) { 661 mVolumeGroupId = volumeGroupId; 662 mCarAudioManager = carAudioManager; 663 } 664 665 @Override onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)666 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 667 if (!fromUser) { 668 // For instance, if this event is originated from AudioService, 669 // we can ignore it as it has already been handled and doesn't need to be 670 // sent back down again. 671 return; 672 } 673 if (mCarAudioManager == null) { 674 Log.w(TAG, "Ignoring volume change event because the car isn't connected"); 675 return; 676 } 677 mAvailableVolumeItems.get(mVolumeGroupId).mProgress = progress; 678 mAvailableVolumeItems.get( 679 mVolumeGroupId).mCarVolumeItem.setProgress(progress); 680 mCarAudioManager.setGroupVolume(mVolumeGroupId, progress, 0); 681 } 682 683 @Override onStartTrackingTouch(SeekBar seekBar)684 public void onStartTrackingTouch(SeekBar seekBar) { 685 } 686 687 @Override onStopTrackingTouch(SeekBar seekBar)688 public void onStopTrackingTouch(SeekBar seekBar) { 689 } 690 } 691 } 692