Refactor CarVolumeDialogImpl to use CarAudioManager

Using CarAudioManager to get volume change callback and set volume
change.

BUG: 78766946
Test: manual
Change-Id: I0563938ca20ad83b4b0de0b7cfe780bb6a97b0fd
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index d53f1a0..99d6511 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -208,6 +208,9 @@
     <!-- to read and change hvac values in a car -->
     <uses-permission android:name="android.car.permission.CONTROL_CAR_CLIMATE" />
 
+    <!-- Permission necessary to change car audio volume through CarAudioManager -->
+    <uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME" />
+
     <application
         android:name=".SystemUIApplication"
         android:persistent="true"
diff --git a/packages/SystemUI/res/drawable/car_ic_notification_2.xml b/packages/SystemUI/res/drawable/car_ic_notification_2.xml
deleted file mode 100644
index c74ae15..0000000
--- a/packages/SystemUI/res/drawable/car_ic_notification_2.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2018 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="32dp"
-    android:height="38dp"
-    android:viewportWidth="32"
-    android:viewportHeight="38" >
-  <group
-      android:translateX="-6"
-      android:translateY="-3">
-    <path
-        android:pathData="M26.6195649,6.98115478 C31.5083629,8.85235985 34.9817444,13.6069337 34.9817444,19.1767606 L34.9817444,27.9542254 L38,27.9542254 L38,34.2161972 L6,34.2161972 L6,27.9542254 L9.01825558,27.9542254 L9.01825558,19.1767606 C9.01825558,13.6069337 12.4916371,8.85235985 17.3804351,6.98115478 C17.723241,4.726863 19.6609451,3 22,3 C24.3390549,3 26.276759,4.726863 26.6195649,6.98115478 Z M17.326572,36.3035211 L26.673428,36.3035211 C26.673428,38.8973148 24.581063,41 22,41 C19.418937,41 17.326572,38.8973148 17.326572,36.3035211 Z"
-        android:strokeColor="#00000000"
-        android:fillType="evenOdd"
-        android:fillColor="@color/car_grey_50" />
-  </group>
-</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/attrs_car.xml b/packages/SystemUI/res/values/attrs_car.xml
index 99d2425..41e0786 100644
--- a/packages/SystemUI/res/values/attrs_car.xml
+++ b/packages/SystemUI/res/values/attrs_car.xml
@@ -63,4 +63,32 @@
         <attr name="hvacPropertyId" format="integer"/>
         <attr name="hvacTempFormat" format="string"/>
     </declare-styleable>
+
+    <declare-styleable name="carVolumeItems"/>
+    <declare-styleable name="carVolumeItems_item">
+        <!-- Align with AudioAttributes.USAGE_* -->
+        <attr name="usage">
+            <enum name="unknown" value="0"/>
+            <enum name="media" value="1"/>
+            <enum name="voice_communication" value="2"/>
+            <enum name="voice_communication_signalling" value="3"/>
+            <enum name="alarm" value="4"/>
+            <enum name="notification" value="5"/>
+            <enum name="notification_ringtone" value="6"/>
+            <enum name="notification_communication_request" value="7"/>
+            <enum name="notification_communication_instant" value="8"/>
+            <enum name="notification_communication_delayed" value="9"/>
+            <enum name="notification_event" value="10"/>
+            <enum name="assistance_accessibility" value="11"/>
+            <enum name="assistance_navigation_guidance" value="12"/>
+            <enum name="assistance_sonification" value="13"/>
+            <enum name="game" value="14"/>
+            <!-- hidden, do not use -->
+            <!-- enum name="virtual_source" value="15"/ -->
+            <enum name="assistant" value="16"/>
+        </attr>
+
+        <!-- Icon resource ids to render on UI -->
+        <attr name="icon" format="reference"/>
+    </declare-styleable>
 </resources>
diff --git a/packages/SystemUI/res/xml/car_volume_items.xml b/packages/SystemUI/res/xml/car_volume_items.xml
new file mode 100644
index 0000000..742dfdd
--- /dev/null
+++ b/packages/SystemUI/res/xml/car_volume_items.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ *
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+-->
+
+<!--
+  Defines all possible items on car volume settings UI, keyed by usage.
+
+  This enables the CarSettings UI to associate VolumeGroups surfaced by
+  CarAudioManager.getVolumeGroupCount with renderable assets (ie: title, icon)
+  for presentation.
+
+  Order matters in this configuration. If one volume group contains multiple
+  audio usages, the first one appears in this file would be picked to be
+  presented on UI.
+
+  When overriding this configuration, please consult also the
+  car_volume_groups.xml, which is read by car audio service.
+-->
+<carVolumeItems xmlns:car="http://schemas.android.com/apk/res-auto">
+  <item car:usage="unknown"
+        car:icon="@drawable/car_ic_music"/>
+  <item car:usage="media"
+        car:icon="@drawable/car_ic_music"/>
+  <item car:usage="voice_communication"
+        car:icon="@*android:drawable/ic_audio_ring_notif"/>
+  <item car:usage="voice_communication_signalling"
+        car:icon="@*android:drawable/ic_audio_ring_notif"/>
+  <item car:usage="alarm"
+        car:icon="@*android:drawable/ic_audio_alarm"/>
+  <item car:usage="notification"
+        car:icon="@drawable/car_ic_notification"/>
+  <item car:usage="notification_ringtone"
+        car:icon="@*android:drawable/ic_audio_ring_notif"/>
+  <item car:usage="notification_communication_request"
+        car:icon="@drawable/car_ic_notification"/>
+  <item car:usage="notification_communication_instant"
+        car:icon="@drawable/car_ic_notification"/>
+  <item car:usage="notification_communication_delayed"
+        car:icon="@drawable/car_ic_notification"/>
+  <item car:usage="notification_event"
+        car:icon="@drawable/car_ic_notification"/>
+  <item car:usage="assistance_accessibility"
+        car:icon="@drawable/car_ic_notification"/>
+  <item car:usage="assistance_navigation_guidance"
+        car:icon="@drawable/car_ic_navigation"/>
+  <item car:usage="assistance_sonification"
+        car:icon="@drawable/car_ic_notification"/>
+  <item car:usage="game"
+        car:icon="@drawable/car_ic_music"/>
+  <item car:usage="assistant"
+        car:icon="@drawable/car_ic_music"/>
+</carVolumeItems>
+
diff --git a/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
index f1a7183..927b0e7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
@@ -22,20 +22,30 @@
 import android.annotation.Nullable;
 import android.app.Dialog;
 import android.app.KeyguardManager;
+import android.car.Car;
+import android.car.CarNotConnectedException;
+import android.car.media.CarAudioManager;
+import android.car.media.ICarVolumeCallback;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.DialogInterface;
+import android.content.ServiceConnection;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
 import android.graphics.Color;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.PixelFormat;
 import android.graphics.drawable.Drawable;
-import android.media.AudioManager;
-import android.media.AudioSystem;
+import android.media.AudioAttributes;
 import android.os.Debug;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.util.AttributeSet;
 import android.util.Log;
-import android.util.SparseBooleanArray;
+import android.util.SparseArray;
+import android.util.Xml;
 import android.view.ContextThemeWrapper;
 import android.view.Gravity;
 import android.view.MotionEvent;
@@ -46,6 +56,7 @@
 import android.widget.SeekBar;
 import android.widget.SeekBar.OnSeekBarChangeListener;
 
+import androidx.annotation.DrawableRes;
 import androidx.car.widget.ListItem;
 import androidx.car.widget.ListItemAdapter;
 import androidx.car.widget.ListItemAdapter.BackgroundStyle;
@@ -53,626 +64,533 @@
 import androidx.car.widget.PagedListView;
 import androidx.car.widget.SeekbarListItem;
 
+import java.util.Iterator;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Iterator;
 import java.util.List;
 
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.plugins.VolumeDialog;
-import com.android.systemui.plugins.VolumeDialogController;
-import com.android.systemui.plugins.VolumeDialogController.State;
-import com.android.systemui.plugins.VolumeDialogController.StreamState;
 
 /**
  * Car version of the volume dialog.
  *
- * A client of VolumeDialogControllerImpl and its state model.
- *
  * Methods ending in "H" must be called on the (ui) handler.
  */
 public class CarVolumeDialogImpl implements VolumeDialog {
-    private static final String TAG = Util.logTag(CarVolumeDialogImpl.class);
+  private static final String TAG = Util.logTag(CarVolumeDialogImpl.class);
 
-    private static final long USER_ATTEMPT_GRACE_PERIOD = 1000;
+  private static final String XML_TAG_VOLUME_ITEMS = "carVolumeItems";
+  private static final String XML_TAG_VOLUME_ITEM = "item";
+  private static final int HOVERING_TIMEOUT = 16000;
+  private static final int NORMAL_TIMEOUT = 3000;
+  private static final int LISTVIEW_ANIMATION_DURATION_IN_MILLIS = 250;
+  private static final int DISMISS_DELAY_IN_MILLIS = 50;
+  private static final int ARROW_FADE_IN_START_DELAY_IN_MILLIS = 100;
 
-    private final Context mContext;
-    private final H mHandler = new H();
-    private final VolumeDialogController mController;
-    private final AudioManager mAudioManager;
+  private final Context mContext;
+  private final H mHandler = new H();
 
-    private Window mWindow;
-    private CustomDialog mDialog;
-    private PagedListView mListView;
-    private ListItemAdapter mPagedListAdapter;
-    private final List<ListItem> mVolumeLineItems = new ArrayList<>();
-    private final List<VolumeRow> mRows = new ArrayList<>();
-    private ConfigurableTexts mConfigurableTexts;
-    private final SparseBooleanArray mDynamic = new SparseBooleanArray();
-    private final KeyguardManager mKeyguard;
-    private final Object mSafetyWarningLock = new Object();
+  private Window mWindow;
+  private CustomDialog mDialog;
+  private PagedListView mListView;
+  private ListItemAdapter mPagedListAdapter;
+  // All the volume items.
+  private final SparseArray<VolumeItem> mVolumeItems = new SparseArray<>();
+  // Available volume items in car audio manager.
+  private final List<VolumeItem> mAvailableVolumeItems = new ArrayList<>();
+  // Volume items in the PagedListView.
+  private final List<ListItem> mVolumeLineItems = new ArrayList<>();
+  private final KeyguardManager mKeyguard;
 
-    private boolean mShowing;
+  private Car mCar;
+  private CarAudioManager mCarAudioManager;
 
-    private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE;
-    private boolean mSilentMode = VolumePrefs.DEFAULT_ENABLE_SILENT_MODE;
-    private State mState;
-    private SafetyWarningDialog mSafetyWarning;
-    private boolean mHovering = false;
-    private boolean mExpanded;
+  private boolean mHovering;
+  private boolean mShowing;
+  private boolean mExpanded;
 
-    public CarVolumeDialogImpl(Context context) {
-        mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
-        mController = Dependency.get(VolumeDialogController.class);
-        mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+  public CarVolumeDialogImpl(Context context) {
+    mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
+    mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+    mCar = Car.createCar(mContext, mServiceConnection);
+  }
+
+  public void init(int windowType, Callback callback) {
+    initDialog();
+
+    mCar.connect();
+  }
+
+  @Override
+  public void destroy() {
+    mHandler.removeCallbacksAndMessages(null);
+
+    cleanupAudioManager();
+    // unregisterVolumeCallback is not being called when disconnect car, so we manually cleanup
+    // audio manager beforehand.
+    mCar.disconnect();
+  }
+
+  private void initDialog() {
+    loadAudioUsageItems();
+    mVolumeLineItems.clear();
+    mDialog = new CustomDialog(mContext);
+
+    mHovering = false;
+    mShowing = false;
+    mExpanded = false;
+    mWindow = mDialog.getWindow();
+    mWindow.requestFeature(Window.FEATURE_NO_TITLE);
+    mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+    mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND
+        | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
+    mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+        | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+        | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+        | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+    mWindow.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
+    mWindow.setWindowAnimations(com.android.internal.R.style.Animation_Toast);
+    final WindowManager.LayoutParams lp = mWindow.getAttributes();
+    lp.format = PixelFormat.TRANSLUCENT;
+    lp.setTitle(VolumeDialogImpl.class.getSimpleName());
+    lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
+    lp.windowAnimations = -1;
+    mWindow.setAttributes(lp);
+    mWindow.setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+
+    mDialog.setCanceledOnTouchOutside(true);
+    mDialog.setContentView(R.layout.car_volume_dialog);
+    mDialog.setOnShowListener(dialog -> {
+      mListView.setTranslationY(-mListView.getHeight());
+      mListView.setAlpha(0);
+      mListView.animate()
+          .alpha(1)
+          .translationY(0)
+          .setDuration(LISTVIEW_ANIMATION_DURATION_IN_MILLIS)
+          .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator())
+          .start();
+    });
+    mListView = (PagedListView) mWindow.findViewById(R.id.volume_list);
+    mListView.setOnHoverListener((v, event) -> {
+      int action = event.getActionMasked();
+      mHovering = (action == MotionEvent.ACTION_HOVER_ENTER)
+          || (action == MotionEvent.ACTION_HOVER_MOVE);
+      rescheduleTimeoutH();
+      return true;
+    });
+
+    mPagedListAdapter = new ListItemAdapter(mContext, new ListProvider(mVolumeLineItems),
+        BackgroundStyle.PANEL);
+    mListView.setAdapter(mPagedListAdapter);
+    mListView.setMaxPages(PagedListView.UNLIMITED_PAGES);
+  }
+
+  public void show(int reason) {
+    mHandler.obtainMessage(H.SHOW, reason, 0).sendToTarget();
+  }
+
+  public void dismiss(int reason) {
+    mHandler.obtainMessage(H.DISMISS, reason, 0).sendToTarget();
+  }
+
+  private void showH(int reason) {
+    if (D.BUG) {
+      Log.d(TAG, "showH r=" + Events.DISMISS_REASONS[reason]);
     }
 
-    public void init(int windowType, Callback callback) {
-        initDialog();
+    mHandler.removeMessages(H.SHOW);
+    mHandler.removeMessages(H.DISMISS);
+    rescheduleTimeoutH();
+    // Refresh the data set before showing.
+    mPagedListAdapter.notifyDataSetChanged();
+    if (mShowing) {
+      return;
+    }
+    mShowing = true;
 
-        mController.addCallback(mControllerCallbackH, mHandler);
-        mController.getState();
+    mDialog.show();
+    Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
+  }
+
+  protected void rescheduleTimeoutH() {
+    mHandler.removeMessages(H.DISMISS);
+    final int timeout = computeTimeoutH();
+    mHandler.sendMessageDelayed(mHandler
+        .obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT, 0), timeout);
+
+    if (D.BUG) {
+      Log.d(TAG, "rescheduleTimeout " + timeout + " " + Debug.getCaller());
+    }
+  }
+
+  private int computeTimeoutH() {
+    return mHovering ? HOVERING_TIMEOUT : NORMAL_TIMEOUT;
+  }
+
+  protected void dismissH(int reason) {
+    if (D.BUG) {
+      Log.d(TAG, "dismissH r=" + Events.DISMISS_REASONS[reason]);
+    }
+
+    mHandler.removeMessages(H.DISMISS);
+    mHandler.removeMessages(H.SHOW);
+    if (!mShowing) {
+      return;
+    }
+
+    mListView.animate().cancel();
+    mShowing = false;
+
+    mListView.setTranslationY(0);
+    mListView.setAlpha(1);
+    mListView.animate()
+        .alpha(0)
+        .translationY(-mListView.getHeight())
+        .setDuration(LISTVIEW_ANIMATION_DURATION_IN_MILLIS)
+        .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
+        .withEndAction(() -> mHandler.postDelayed(() -> {
+          if (D.BUG) {
+            Log.d(TAG, "mDialog.dismiss()");
+          }
+          mDialog.dismiss();
+        }, DISMISS_DELAY_IN_MILLIS))
+        .start();
+
+    Events.writeEvent(mContext, Events.EVENT_DISMISS_DIALOG, reason);
+  }
+
+  public void dump(PrintWriter writer) {
+    writer.println(VolumeDialogImpl.class.getSimpleName() + " state:");
+    writer.print("  mShowing: "); writer.println(mShowing);
+  }
+
+  private void loadAudioUsageItems() {
+    try (XmlResourceParser parser = mContext.getResources().getXml(R.xml.car_volume_items)) {
+      AttributeSet attrs = Xml.asAttributeSet(parser);
+      int type;
+      // Traverse to the first start tag
+      while ((type=parser.next()) != XmlResourceParser.END_DOCUMENT
+          && type != XmlResourceParser.START_TAG) {
+      }
+
+      if (!XML_TAG_VOLUME_ITEMS.equals(parser.getName())) {
+        throw new RuntimeException("Meta-data does not start with carVolumeItems tag");
+      }
+      int outerDepth = parser.getDepth();
+      int rank = 0;
+      while ((type=parser.next()) != XmlResourceParser.END_DOCUMENT
+          && (type != XmlResourceParser.END_TAG || parser.getDepth() > outerDepth)) {
+        if (type == XmlResourceParser.END_TAG) {
+          continue;
+        }
+        if (XML_TAG_VOLUME_ITEM.equals(parser.getName())) {
+          TypedArray item = mContext.getResources().obtainAttributes(
+              attrs, R.styleable.carVolumeItems_item);
+          int usage = item.getInt(R.styleable.carVolumeItems_item_usage, -1);
+          if (usage >= 0) {
+            VolumeItem volumeItem = new VolumeItem();
+            volumeItem.usage = usage;
+            volumeItem.rank = rank;
+            volumeItem.icon = item.getResourceId(R.styleable.carVolumeItems_item_icon, 0);
+            mVolumeItems.put(usage, volumeItem);
+            rank++;
+          }
+          item.recycle();
+        }
+      }
+    } catch (XmlPullParserException | IOException e) {
+      Log.e(TAG, "Error parsing volume groups configuration", e);
+    }
+  }
+
+  private VolumeItem getVolumeItemForUsages(int[] usages) {
+    int rank = Integer.MAX_VALUE;
+    VolumeItem result = null;
+    for (int usage : usages) {
+      VolumeItem volumeItem = mVolumeItems.get(usage);
+      if (volumeItem.rank < rank) {
+        rank = volumeItem.rank;
+        result = volumeItem;
+      }
+    }
+    return result;
+  }
+
+  private static int getSeekbarValue(CarAudioManager carAudioManager, int volumeGroupId) {
+    try {
+      return carAudioManager.getGroupVolume(volumeGroupId);
+    } catch (CarNotConnectedException e) {
+      Log.e(TAG, "Car is not connected!", e);
+    }
+    return 0;
+  }
+
+  private static int getMaxSeekbarValue(CarAudioManager carAudioManager, int volumeGroupId) {
+    try {
+      return carAudioManager.getGroupMaxVolume(volumeGroupId);
+    } catch (CarNotConnectedException e) {
+      Log.e(TAG, "Car is not connected!", e);
+    }
+    return 0;
+  }
+
+  private SeekbarListItem addSeekbarListItem(VolumeItem volumeItem, int volumeGroupId,
+      int supplementalIconId, @Nullable View.OnClickListener supplementalIconOnClickListener) {
+    SeekbarListItem listItem = new SeekbarListItem(mContext);
+    listItem.setMax(getMaxSeekbarValue(mCarAudioManager, volumeGroupId));
+    int progress = getSeekbarValue(mCarAudioManager, volumeGroupId);
+    listItem.setProgress(progress);
+    listItem.setOnSeekBarChangeListener(
+        new CarVolumeDialogImpl.VolumeSeekBarChangeListener(volumeGroupId, mCarAudioManager));
+    listItem.setPrimaryActionIcon(mContext.getResources().getDrawable(volumeItem.icon));
+    if (supplementalIconId != 0) {
+      Drawable supplementalIcon = mContext.getResources().getDrawable(supplementalIconId);
+      listItem.setSupplementalIcon(supplementalIcon, true,
+          supplementalIconOnClickListener);
+    } else {
+      listItem.setSupplementalEmptyIcon(true);
+    }
+
+    mVolumeLineItems.add(listItem);
+    volumeItem.listItem = listItem;
+    volumeItem.progress = progress;
+    return listItem;
+  }
+
+  private VolumeItem findVolumeItem(SeekbarListItem targetItem) {
+    for (int i = 0; i < mVolumeItems.size(); ++i) {
+      VolumeItem volumeItem = mVolumeItems.valueAt(i);
+      if (volumeItem.listItem == targetItem) {
+        return volumeItem;
+      }
+    }
+    return null;
+  }
+
+  private void cleanupAudioManager() {
+    try {
+      mCarAudioManager.unregisterVolumeCallback(mVolumeChangeCallback.asBinder());
+    } catch (CarNotConnectedException e) {
+      Log.e(TAG, "Car is not connected!", e);
+    }
+    mVolumeLineItems.clear();
+    mCarAudioManager = null;
+  }
+
+  private final class H extends Handler {
+    private static final int SHOW = 1;
+    private static final int DISMISS = 2;
+
+    public H() {
+      super(Looper.getMainLooper());
     }
 
     @Override
-    public void destroy() {
-        mController.removeCallback(mControllerCallbackH);
-        mHandler.removeCallbacksAndMessages(null);
+    public void handleMessage(Message msg) {
+      switch (msg.what) {
+        case SHOW:
+          showH(msg.arg1);
+          break;
+        case DISMISS:
+          dismissH(msg.arg1);
+          break;
+        default:
+      }
+    }
+  }
+
+  private final class CustomDialog extends Dialog implements DialogInterface {
+    public CustomDialog(Context context) {
+      super(context, com.android.systemui.R.style.qs_theme);
     }
 
-    private void initDialog() {
-        mRows.clear();
-        mVolumeLineItems.clear();
-        mDialog = new CustomDialog(mContext);
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+      rescheduleTimeoutH();
+      return super.dispatchTouchEvent(ev);
+    }
 
-        mConfigurableTexts = new ConfigurableTexts(mContext);
-        mHovering = false;
-        mShowing = false;
+    @Override
+    protected void onStart() {
+      super.setCanceledOnTouchOutside(true);
+      super.onStart();
+    }
+
+    @Override
+    protected void onStop() {
+      super.onStop();
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+      if (isShowing()) {
+        if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
+          dismissH(Events.DISMISS_REASON_TOUCH_OUTSIDE);
+          return true;
+        }
+      }
+      return false;
+    }
+  }
+
+  private final class ExpandIconListener implements View.OnClickListener {
+    @Override
+    public void onClick(final View v) {
+      mExpanded = !mExpanded;
+      Animator inAnimator;
+      if (mExpanded) {
+        for (int groupId = 0; groupId < mAvailableVolumeItems.size(); ++groupId) {
+          // Adding the items which are not coming from the default item.
+          VolumeItem volumeItem = mAvailableVolumeItems.get(groupId);
+          if (volumeItem.defaultItem) {
+            // Set progress here due to the progress of seekbar may not be updated.
+            volumeItem.listItem.setProgress(volumeItem.progress);
+          } else {
+            addSeekbarListItem(volumeItem, groupId, 0, null);
+          }
+        }
+        inAnimator = AnimatorInflater.loadAnimator(
+            mContext, R.anim.car_arrow_fade_in_rotate_up);
+      } else {
+        // Only keeping the default stream if it is not expended.
+        Iterator itr = mVolumeLineItems.iterator();
+        while (itr.hasNext()) {
+          SeekbarListItem seekbarListItem = (SeekbarListItem) itr.next();
+          VolumeItem volumeItem = findVolumeItem(seekbarListItem);
+          if (!volumeItem.defaultItem) {
+            itr.remove();
+          } else {
+            // Set progress here due to the progress of seekbar may not be updated.
+            seekbarListItem.setProgress(volumeItem.progress);
+          }
+        }
+        inAnimator = AnimatorInflater.loadAnimator(
+            mContext, R.anim.car_arrow_fade_in_rotate_down);
+      }
+
+      Animator outAnimator = AnimatorInflater.loadAnimator(
+          mContext, R.anim.car_arrow_fade_out);
+      inAnimator.setStartDelay(ARROW_FADE_IN_START_DELAY_IN_MILLIS);
+      AnimatorSet animators = new AnimatorSet();
+      animators.playTogether(outAnimator, inAnimator);
+      animators.setTarget(v);
+      animators.start();
+      mPagedListAdapter.notifyDataSetChanged();
+    }
+  }
+
+  private final class VolumeSeekBarChangeListener implements OnSeekBarChangeListener {
+    private final int mVolumeGroupId;
+    private final CarAudioManager mCarAudioManager;
+
+    private VolumeSeekBarChangeListener(int volumeGroupId, CarAudioManager carAudioManager) {
+      mVolumeGroupId = volumeGroupId;
+      mCarAudioManager = carAudioManager;
+    }
+
+    @Override
+    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+      if (!fromUser) {
+        // For instance, if this event is originated from AudioService,
+        // we can ignore it as it has already been handled and doesn't need to be
+        // sent back down again.
+        return;
+      }
+      try {
+        if (mCarAudioManager == null) {
+          Log.w(TAG, "Ignoring volume change event because the car isn't connected");
+          return;
+        }
+        mAvailableVolumeItems.get(mVolumeGroupId).progress = progress;
+        mCarAudioManager.setGroupVolume(mVolumeGroupId, progress, 0);
+      } catch (CarNotConnectedException e) {
+        Log.e(TAG, "Car is not connected!", e);
+      }
+    }
+
+    @Override
+    public void onStartTrackingTouch(SeekBar seekBar) {}
+
+    @Override
+    public void onStopTrackingTouch(SeekBar seekBar) {}
+  }
+
+  private final ICarVolumeCallback mVolumeChangeCallback = new ICarVolumeCallback.Stub() {
+    @Override
+    public void onGroupVolumeChanged(int groupId) {
+      VolumeItem volumeItem = mAvailableVolumeItems.get(groupId);
+      int value = getSeekbarValue(mCarAudioManager, groupId);
+      // Do not update the progress if it is the same as before. When car audio manager sets its
+      // group volume caused by the seekbar progress changed, it also triggers this callback.
+      // Updating the seekbar at the same time could block the continuous seeking.
+      if (value != volumeItem.progress) {
+        volumeItem.listItem.setProgress(value);
+        volumeItem.progress = value;
+        show(Events.SHOW_REASON_VOLUME_CHANGED);
+      }
+    }
+
+    @Override
+    public void onMasterMuteChanged() {
+      // ignored
+    }
+  };
+
+  private final ServiceConnection mServiceConnection = new ServiceConnection() {
+    @Override
+    public void onServiceConnected(ComponentName name, IBinder service) {
+      try {
         mExpanded = false;
-        mWindow = mDialog.getWindow();
-        mWindow.requestFeature(Window.FEATURE_NO_TITLE);
-        mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
-        mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND
-            | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
-        mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-            | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-            | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-            | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
-            | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
-            | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
-        mWindow.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
-        mWindow.setWindowAnimations(com.android.internal.R.style.Animation_Toast);
-        final WindowManager.LayoutParams lp = mWindow.getAttributes();
-        lp.format = PixelFormat.TRANSLUCENT;
-        lp.setTitle(VolumeDialogImpl.class.getSimpleName());
-        lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
-        lp.windowAnimations = -1;
-        mWindow.setAttributes(lp);
-        mWindow.setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+        mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE);
+        int volumeGroupCount = mCarAudioManager.getVolumeGroupCount();
+        // Populates volume slider items from volume groups to UI.
+        for (int groupId = 0; groupId < volumeGroupCount; groupId++) {
+          VolumeItem volumeItem = getVolumeItemForUsages(
+              mCarAudioManager.getUsagesForVolumeGroupId(groupId));
+          mAvailableVolumeItems.add(volumeItem);
+          // The first one is the default item.
+          if (groupId == 0) {
+            volumeItem.defaultItem = true;
+            addSeekbarListItem(volumeItem, groupId, R.drawable.car_ic_keyboard_arrow_down,
+                new ExpandIconListener());
+          }
+        }
 
-        mDialog.setCanceledOnTouchOutside(true);
-        mDialog.setContentView(R.layout.car_volume_dialog);
-        mDialog.setOnShowListener(dialog -> {
-            mListView.setTranslationY(-mListView.getHeight());
-            mListView.setAlpha(0);
-            mListView.animate()
-                .alpha(1)
-                .translationY(0)
-                .setDuration(300)
-                .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator())
-                .start();
-        });
-        mListView = (PagedListView) mWindow.findViewById(R.id.volume_list);
-        mListView.setOnHoverListener((v, event) -> {
-            int action = event.getActionMasked();
-            mHovering = (action == MotionEvent.ACTION_HOVER_ENTER)
-                || (action == MotionEvent.ACTION_HOVER_MOVE);
-            rescheduleTimeoutH();
-            return true;
-        });
-
-        addSeekbarListItem(addVolumeRow(AudioManager.STREAM_MUSIC, R.drawable.car_ic_music,
-            R.drawable.car_ic_keyboard_arrow_down, true, true),
-            new ExpandIconListener());
-        // We map AudioManager.STREAM_RING to a navigation icon for demo.
-        addVolumeRow(AudioManager.STREAM_RING, R.drawable.car_ic_navigation, 0,
-            true, false);
-        addVolumeRow(AudioManager.STREAM_NOTIFICATION, R.drawable.car_ic_notification_2, 0,
-            true, false);
-
-        mPagedListAdapter = new ListItemAdapter(mContext, new ListProvider(mVolumeLineItems),
-            BackgroundStyle.PANEL);
-        mListView.setAdapter(mPagedListAdapter);
-        mListView.setMaxPages(PagedListView.UNLIMITED_PAGES);
+        // If list is already initiated, update its content.
+        if (mPagedListAdapter != null) {
+          mPagedListAdapter.notifyDataSetChanged();
+        }
+        mCarAudioManager.registerVolumeCallback(mVolumeChangeCallback.asBinder());
+      } catch (CarNotConnectedException e) {
+        Log.e(TAG, "Car is not connected!", e);
+      }
     }
 
-    public void setStreamImportant(int stream, boolean important) {
-        mHandler.obtainMessage(H.SET_STREAM_IMPORTANT, stream, important ? 1 : 0).sendToTarget();
+    /**
+     * This does not get called when service is properly disconnected.
+     * So we need to also handle cleanups in destroy().
+     */
+    @Override
+    public void onServiceDisconnected(ComponentName name) {
+      cleanupAudioManager();
     }
+  };
 
-    public void setAutomute(boolean automute) {
-        if (mAutomute == automute) {
-            return;
-        }
-        mAutomute = automute;
-        mHandler.sendEmptyMessage(H.RECHECK_ALL);
-    }
-
-    public void setSilentMode(boolean silentMode) {
-        if (mSilentMode == silentMode) {
-            return;
-        }
-        mSilentMode = silentMode;
-        mHandler.sendEmptyMessage(H.RECHECK_ALL);
-    }
-
-    private VolumeRow addVolumeRow(int stream, int primaryActionIcon, int supplementalIcon,
-        boolean important, boolean defaultStream) {
-        VolumeRow volumeRow = new VolumeRow();
-        volumeRow.stream = stream;
-        volumeRow.primaryActionIcon = primaryActionIcon;
-        volumeRow.supplementalIcon = supplementalIcon;
-        volumeRow.important = important;
-        volumeRow.defaultStream = defaultStream;
-        volumeRow.listItem = null;
-        mRows.add(volumeRow);
-        return volumeRow;
-    }
-
-    private SeekbarListItem addSeekbarListItem(
-        VolumeRow volumeRow, @Nullable View.OnClickListener supplementalIconOnClickListener) {
-        int volumeMax = mAudioManager.getStreamMaxVolume(volumeRow.stream);
-        int currentVolume = mAudioManager.getStreamVolume(volumeRow.stream);
-        SeekbarListItem listItem =
-            new SeekbarListItem(mContext, volumeMax, currentVolume,
-                new VolumeSeekBarChangeListener(volumeRow), null);
-        Drawable primaryIcon = mContext.getResources().getDrawable(volumeRow.primaryActionIcon);
-        listItem.setPrimaryActionIcon(primaryIcon);
-        if (volumeRow.supplementalIcon != 0) {
-            Drawable supplementalIcon = mContext.getResources()
-                .getDrawable(volumeRow.supplementalIcon);
-            listItem.setSupplementalIcon(supplementalIcon, true,
-                supplementalIconOnClickListener);
-        } else {
-            listItem.setSupplementalEmptyIcon(true);
-        }
-
-        mVolumeLineItems.add(listItem);
-        volumeRow.listItem = listItem;
-
-        return listItem;
-    }
-
-    private static int getImpliedLevel(SeekBar seekBar, int progress) {
-        final int m = seekBar.getMax();
-        final int n = m / 100 - 1;
-        final int level = progress == 0 ? 0
-            : progress == m ? (m / 100) : (1 + (int)((progress / (float) m) * n));
-        return level;
-    }
-
-    public void show(int reason) {
-        mHandler.obtainMessage(H.SHOW, reason, 0).sendToTarget();
-    }
-
-    public void dismiss(int reason) {
-        mHandler.obtainMessage(H.DISMISS, reason, 0).sendToTarget();
-    }
-
-    private void showH(int reason) {
-        if (D.BUG) Log.d(TAG, "showH r=" + Events.DISMISS_REASONS[reason]);
-        mHandler.removeMessages(H.SHOW);
-        mHandler.removeMessages(H.DISMISS);
-        rescheduleTimeoutH();
-        if (mShowing) return;
-        mShowing = true;
-
-        mDialog.show();
-        Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
-        mController.notifyVisible(true);
-    }
-
-    protected void rescheduleTimeoutH() {
-        mHandler.removeMessages(H.DISMISS);
-        final int timeout = computeTimeoutH();
-        mHandler.sendMessageDelayed(mHandler
-            .obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT, 0), timeout);
-        if (D.BUG) Log.d(TAG, "rescheduleTimeout " + timeout + " " + Debug.getCaller());
-        mController.userActivity();
-    }
-
-    private int computeTimeoutH() {
-        if (mHovering) return 16000;
-        if (mSafetyWarning != null) return 5000;
-        return 3000;
-    }
-
-    protected void dismissH(int reason) {
-        mHandler.removeMessages(H.DISMISS);
-        mHandler.removeMessages(H.SHOW);
-        if (!mShowing) return;
-        mListView.animate().cancel();
-        mShowing = false;
-
-        mListView.setTranslationY(0);
-        mListView.setAlpha(1);
-        mListView.animate()
-            .alpha(0)
-            .translationY(-mListView.getHeight())
-            .setDuration(250)
-            .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
-            .withEndAction(() -> mHandler.postDelayed(() -> {
-                if (D.BUG) Log.d(TAG, "mDialog.dismiss()");
-                mDialog.dismiss();
-            }, 50))
-            .start();
-
-        Events.writeEvent(mContext, Events.EVENT_DISMISS_DIALOG, reason);
-        mController.notifyVisible(false);
-        synchronized (mSafetyWarningLock) {
-            if (mSafetyWarning != null) {
-                if (D.BUG) Log.d(TAG, "SafetyWarning dismissed");
-                mSafetyWarning.dismiss();
-            }
-        }
-    }
-
-    private void trimObsoleteH() {
-        int initialVolumeItemSize = mVolumeLineItems.size();
-        for (int i = mRows.size() - 1; i >= 0; i--) {
-            final VolumeRow row = mRows.get(i);
-            if (row.ss == null || !row.ss.dynamic) continue;
-            if (!mDynamic.get(row.stream)) {
-                mRows.remove(i);
-                mVolumeLineItems.remove(row.listItem);
-            }
-        }
-
-        if (mVolumeLineItems.size() != initialVolumeItemSize) {
-            mPagedListAdapter.notifyDataSetChanged();
-        }
-    }
-
-    private void onStateChangedH(State state) {
-        mState = state;
-        mDynamic.clear();
-        // add any new dynamic rows
-        for (int i = 0; i < state.states.size(); i++) {
-            final int stream = state.states.keyAt(i);
-            final StreamState ss = state.states.valueAt(i);
-            if (!ss.dynamic) {
-                continue;
-            }
-            mDynamic.put(stream, true);
-            if (findRow(stream) == null) {
-                VolumeRow row = addVolumeRow(stream, R.drawable.ic_volume_remote,
-                    0, true,false);
-                if (mExpanded) {
-                    addSeekbarListItem(row, null);
-                }
-            }
-        }
-
-        for (VolumeRow row : mRows) {
-            updateVolumeRowH(row);
-        }
-    }
-
-    private void updateVolumeRowH(VolumeRow row) {
-        if (D.BUG) Log.d(TAG, "updateVolumeRowH s=" + row.stream);
-        if (mState == null) {
-            return;
-        }
-        final StreamState ss = mState.states.get(row.stream);
-        if (ss == null) {
-            return;
-        }
-        row.ss = ss;
-        if (ss.level == row.requestedLevel) {
-            row.requestedLevel = -1;
-        }
-        // TODO: update Seekbar progress and change the mute icon if necessary.
-    }
-
-    private VolumeRow findRow(int stream) {
-        for (VolumeRow row : mRows) {
-            if (row.stream == stream) {
-                return row;
-            }
-        }
-        return null;
-    }
-
-    private VolumeRow findRow(SeekbarListItem targetItem) {
-        for (VolumeRow row : mRows) {
-            if (row.listItem == targetItem) {
-                return row;
-            }
-        }
-        return null;
-    }
-
-    public void dump(PrintWriter writer) {
-        writer.println(VolumeDialogImpl.class.getSimpleName() + " state:");
-        writer.print("  mShowing: "); writer.println(mShowing);
-        writer.print("  mDynamic: "); writer.println(mDynamic);
-        writer.print("  mAutomute: "); writer.println(mAutomute);
-        writer.print("  mSilentMode: "); writer.println(mSilentMode);
-    }
-
-    private void recheckH(VolumeRow row) {
-        if (row == null) {
-            if (D.BUG) Log.d(TAG, "recheckH ALL");
-            trimObsoleteH();
-            for (VolumeRow r : mRows) {
-                updateVolumeRowH(r);
-            }
-        } else {
-            if (D.BUG) Log.d(TAG, "recheckH " + row.stream);
-            updateVolumeRowH(row);
-        }
-    }
-
-    private void setStreamImportantH(int stream, boolean important) {
-        for (VolumeRow row : mRows) {
-            if (row.stream == stream) {
-                row.important = important;
-                return;
-            }
-        }
-    }
-
-    private void showSafetyWarningH(int flags) {
-        if ((flags & (AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_SHOW_UI_WARNINGS)) != 0
-            || mShowing) {
-            synchronized (mSafetyWarningLock) {
-                if (mSafetyWarning != null) {
-                    return;
-                }
-                mSafetyWarning = new SafetyWarningDialog(mContext, mController.getAudioManager()) {
-                    @Override
-                    protected void cleanUp() {
-                        synchronized (mSafetyWarningLock) {
-                            mSafetyWarning = null;
-                        }
-                        recheckH(null);
-                    }
-                };
-                mSafetyWarning.show();
-            }
-            recheckH(null);
-        }
-        rescheduleTimeoutH();
-    }
-
-    private final VolumeDialogController.Callbacks mControllerCallbackH
-            = new VolumeDialogController.Callbacks() {
-        @Override
-        public void onShowRequested(int reason) {
-            showH(reason);
-        }
-
-        @Override
-        public void onDismissRequested(int reason) {
-            dismissH(reason);
-        }
-
-        @Override
-        public void onScreenOff() {
-            dismissH(Events.DISMISS_REASON_SCREEN_OFF);
-        }
-
-        @Override
-        public void onStateChanged(State state) {
-            onStateChangedH(state);
-        }
-
-        @Override
-        public void onLayoutDirectionChanged(int layoutDirection) {
-            mListView.setLayoutDirection(layoutDirection);
-        }
-
-        @Override
-        public void onConfigurationChanged() {
-            mDialog.dismiss();
-            initDialog();
-            mConfigurableTexts.update();
-        }
-
-        @Override
-        public void onShowVibrateHint() {
-            if (mSilentMode) {
-                mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false);
-            }
-        }
-
-        @Override
-        public void onShowSilentHint() {
-            if (mSilentMode) {
-                mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
-            }
-        }
-
-        @Override
-        public void onShowSafetyWarning(int flags) {
-            showSafetyWarningH(flags);
-        }
-
-        @Override
-        public void onAccessibilityModeChanged(Boolean showA11yStream) {
-        }
-    };
-
-    private final class H extends Handler {
-        private static final int SHOW = 1;
-        private static final int DISMISS = 2;
-        private static final int RECHECK = 3;
-        private static final int RECHECK_ALL = 4;
-        private static final int SET_STREAM_IMPORTANT = 5;
-        private static final int RESCHEDULE_TIMEOUT = 6;
-        private static final int STATE_CHANGED = 7;
-
-        public H() {
-            super(Looper.getMainLooper());
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case SHOW: showH(msg.arg1); break;
-                case DISMISS: dismissH(msg.arg1); break;
-                case RECHECK: recheckH((VolumeRow) msg.obj); break;
-                case RECHECK_ALL: recheckH(null); break;
-                case SET_STREAM_IMPORTANT: setStreamImportantH(msg.arg1, msg.arg2 != 0); break;
-                case RESCHEDULE_TIMEOUT: rescheduleTimeoutH(); break;
-                case STATE_CHANGED: onStateChangedH(mState); break;
-            }
-        }
-    }
-
-    private final class CustomDialog extends Dialog implements DialogInterface {
-        public CustomDialog(Context context) {
-            super(context, com.android.systemui.R.style.qs_theme);
-        }
-
-        @Override
-        public boolean dispatchTouchEvent(MotionEvent ev) {
-            rescheduleTimeoutH();
-            return super.dispatchTouchEvent(ev);
-        }
-
-        @Override
-        protected void onStart() {
-            super.setCanceledOnTouchOutside(true);
-            super.onStart();
-        }
-
-        @Override
-        protected void onStop() {
-            super.onStop();
-            mHandler.sendEmptyMessage(H.RECHECK_ALL);
-        }
-
-        @Override
-        public boolean onTouchEvent(MotionEvent event) {
-            if (isShowing()) {
-                if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
-                    dismissH(Events.DISMISS_REASON_TOUCH_OUTSIDE);
-                    return true;
-                }
-            }
-            return false;
-        }
-    }
-
-    private final class VolumeSeekBarChangeListener implements OnSeekBarChangeListener {
-        private final VolumeRow mRow;
-
-        private VolumeSeekBarChangeListener(VolumeRow volumeRow) {
-            mRow = volumeRow;
-        }
-
-        @Override
-        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-            if (mRow.ss == null) {
-                return;
-            }
-            if (D.BUG) {
-                Log.d(TAG, AudioSystem.streamToString(mRow.stream)
-                    + " onProgressChanged " + progress + " fromUser=" + fromUser);
-            }
-            if (!fromUser) {
-                return;
-            }
-            if (mRow.ss.levelMin > 0) {
-                final int minProgress = mRow.ss.levelMin;
-                if (progress < minProgress) {
-                    seekBar.setProgress(minProgress);
-                    progress = minProgress;
-                }
-            }
-            final int userLevel = getImpliedLevel(seekBar, progress);
-            if (mRow.ss.level != userLevel || mRow.ss.muted && userLevel > 0) {
-                if (mRow.requestedLevel != userLevel) {
-                    mController.setStreamVolume(mRow.stream, userLevel);
-                    mRow.requestedLevel = userLevel;
-                    Events.writeEvent(mContext, Events.EVENT_TOUCH_LEVEL_CHANGED, mRow.stream,
-                        userLevel);
-                }
-            }
-        }
-
-        @Override
-        public void onStartTrackingTouch(SeekBar seekBar) {
-            if (D.BUG) {
-                Log.d(TAG, "onStartTrackingTouch"+ " " + mRow.stream);
-            }
-            mController.setActiveStream(mRow.stream);
-        }
-
-        @Override
-        public void onStopTrackingTouch(SeekBar seekBar) {
-            if (D.BUG) {
-                Log.d(TAG, "onStopTrackingTouch"+ " " + mRow.stream);
-            }
-            final int userLevel = getImpliedLevel(seekBar, seekBar.getProgress());
-            Events.writeEvent(mContext, Events.EVENT_TOUCH_LEVEL_DONE, mRow.stream, userLevel);
-            if (mRow.ss.level != userLevel) {
-                mHandler.sendMessageDelayed(mHandler.obtainMessage(H.RECHECK, mRow),
-                    USER_ATTEMPT_GRACE_PERIOD);
-            }
-        }
-    }
-
-    private final class ExpandIconListener implements View.OnClickListener {
-        @Override
-        public void onClick(final View v) {
-            mExpanded = !mExpanded;
-            Animator inAnimator;
-            if (mExpanded) {
-                for (VolumeRow row : mRows) {
-                    // Adding the items which are not coming from default stream.
-                    if (!row.defaultStream) {
-                        addSeekbarListItem(row, null);
-                    }
-                }
-                inAnimator = AnimatorInflater.loadAnimator(
-                    mContext, R.anim.car_arrow_fade_in_rotate_up);
-            } else {
-                // Only keeping the default stream if it is not expended.
-                Iterator itr = mVolumeLineItems.iterator();
-                while (itr.hasNext()) {
-                    SeekbarListItem item = (SeekbarListItem) itr.next();
-                    VolumeRow row = findRow(item);
-                    if (!row.defaultStream) {
-                        itr.remove();
-                    }
-                }
-                inAnimator = AnimatorInflater.loadAnimator(
-                    mContext, R.anim.car_arrow_fade_in_rotate_down);
-            }
-
-            Animator outAnimator = AnimatorInflater.loadAnimator(
-                mContext, R.anim.car_arrow_fade_out);
-            inAnimator.setStartDelay(100);
-            AnimatorSet animators = new AnimatorSet();
-            animators.playTogether(outAnimator, inAnimator);
-            animators.setTarget(v);
-            animators.start();
-            mPagedListAdapter.notifyDataSetChanged();
-        }
-    }
-
-    private static class VolumeRow {
-        private int stream;
-        private StreamState ss;
-        private boolean important;
-        private boolean defaultStream;
-        private int primaryActionIcon;
-        private int supplementalIcon;
-        private SeekbarListItem listItem;
-        private int requestedLevel = -1;  // pending user-requested level via progress changed
-    }
-}
\ No newline at end of file
+  /**
+   * Wrapper class which contains information of each volume group.
+   */
+  private static class VolumeItem {
+    private @AudioAttributes.AttributeUsage int usage;
+    private int rank;
+    private boolean defaultItem = false;
+    private @DrawableRes int icon;
+    private SeekbarListItem listItem;
+    private int progress;
+  }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
index 6e5b548..dd55264 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -103,11 +103,7 @@
     }
 
     private VolumeDialog createCarDefault() {
-        CarVolumeDialogImpl impl = new CarVolumeDialogImpl(mContext);
-        impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
-        impl.setAutomute(true);
-        impl.setSilentMode(false);
-        return impl;
+        return new CarVolumeDialogImpl(mContext);
     }
 
     @Override