Theme fixit for Car Sys UI

Notifications to use recyclerview in CarSysUI
SysUI UserSwitcher changes for PLV -> RecyclerView
Car SysUI Volume PLV -> RecyclerView

Bug: 128545260
Test: manual
Change-Id: Ic618b85d6836cfc9e4bb9b40c9ba3c0e0a96af76
diff --git a/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java b/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java
index 4a2d2fb..48cb55b 100644
--- a/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java
+++ b/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java
@@ -16,11 +16,11 @@
 
 package com.android.systemui.notifications;
 
-import android.app.ActivityManager;
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.SuppressLint;
+import android.app.ActivityManager;
 import android.car.Car;
 import android.car.CarNotConnectedException;
 import android.car.drivingstate.CarUxRestrictionsManager;
@@ -113,7 +113,7 @@
                         ServiceManager.getService(Context.STATUS_BAR_SERVICE)),
                 launchResult -> {
                     if (launchResult == ActivityManager.START_TASK_TO_FRONT
-                            || launchResult == ActivityManager.START_SUCCESS){
+                            || launchResult == ActivityManager.START_SUCCESS) {
                         closeCarNotifications(DEFAULT_FLING_VELOCITY);
                     }
                 });
@@ -179,8 +179,7 @@
                     }
                 });
 
-        RecyclerView notificationList = mCarNotificationWindow
-                .findViewById(com.android.car.notification.R.id.recycler_view);
+        RecyclerView notificationList = mCarNotificationWindow.findViewById(R.id.notifications);
         // register a scroll listener so we can figure out if we are at the bottom of the
         // list of notifications
         notificationList.addOnScrollListener(new RecyclerView.OnScrollListener() {
@@ -202,7 +201,7 @@
         // There's a view installed at a higher z-order such that we can intercept the ACTION_DOWN
         // to set the initial click state.
         mCarNotificationWindow.findViewById(R.id.glass_pane).setOnTouchListener((v, event) -> {
-            if (event.getActionMasked() == MotionEvent.ACTION_UP ) {
+            if (event.getActionMasked() == MotionEvent.ACTION_UP) {
                 mNotificationListAtBottomAtTimeOfTouch = false;
             }
             if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
@@ -259,7 +258,7 @@
         public boolean onTouch(View v, MotionEvent event) {
             // reset mNotificationListAtBottomAtTimeOfTouch here since the "glass pane" will not
             // get the up event
-            if (event.getActionMasked() == MotionEvent.ACTION_UP ) {
+            if (event.getActionMasked() == MotionEvent.ACTION_UP) {
                 mNotificationListAtBottomAtTimeOfTouch = false;
             }
             boolean wasScrolledUp = mScrollUpDetector.onTouchEvent(event);
@@ -351,7 +350,7 @@
         public boolean onFling(MotionEvent event1, MotionEvent event2,
                 float velocityX, float velocityY) {
             if (Math.abs(event1.getX() - event2.getX()) > SWIPE_MAX_OFF_PATH
-                    || Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY){
+                    || Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) {
                 // swipe was not vertical or was not fast enough
                 return false;
             }
@@ -435,8 +434,7 @@
         mNotificationViewController.disable();
         mIsShowing = false;
         mIsTracking = false;
-        RecyclerView notificationListView = mCarNotificationWindow.findViewById(
-                com.android.car.notification.R.id.recycler_view);
+        RecyclerView notificationListView = mCarNotificationWindow.findViewById(R.id.notifications);
         notificationListView.scrollToPosition(0);
     }
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/qs/car/CarQSFragment.java b/packages/CarSystemUI/src/com/android/systemui/qs/car/CarQSFragment.java
index 41c37d3..769fc52 100644
--- a/packages/CarSystemUI/src/com/android/systemui/qs/car/CarQSFragment.java
+++ b/packages/CarSystemUI/src/com/android/systemui/qs/car/CarQSFragment.java
@@ -79,7 +79,7 @@
         mUserGridView = mUserSwitcherContainer.findViewById(R.id.user_grid);
         GridLayoutManager layoutManager = new GridLayoutManager(context,
                 context.getResources().getInteger(R.integer.user_fullscreen_switcher_num_col));
-        mUserGridView.getRecyclerView().setLayoutManager(layoutManager);
+        mUserGridView.setLayoutManager(layoutManager);
         mUserGridView.buildAdapter();
 
         mUserSwitchCallback = new UserSwitchCallback();
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
index 23fe594..f896cf1 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
@@ -45,7 +45,7 @@
         mUserGridView = container.findViewById(R.id.user_grid);
         GridLayoutManager layoutManager = new GridLayoutManager(context,
                 context.getResources().getInteger(R.integer.user_fullscreen_switcher_num_col));
-        mUserGridView.getRecyclerView().setLayoutManager(layoutManager);
+        mUserGridView.setLayoutManager(layoutManager);
         mUserGridView.buildAdapter();
         mUserGridView.setUserSelectionListener(this::onUserSelected);
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
index fb2b57b..5760378 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
@@ -28,6 +28,7 @@
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.graphics.Rect;
 import android.os.AsyncTask;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
@@ -36,9 +37,9 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
-import androidx.car.widget.PagedListView;
 import androidx.core.graphics.drawable.RoundedBitmapDrawable;
 import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
+import androidx.recyclerview.widget.GridLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.internal.util.UserIcons;
@@ -52,7 +53,7 @@
  * Displays a GridLayout with icons for the users in the system to allow switching between users.
  * One of the uses of this is for the lock screen in auto.
  */
-public class UserGridRecyclerView extends PagedListView implements
+public class UserGridRecyclerView extends RecyclerView implements
         CarUserManagerHelper.OnUsersUpdateListener {
     private UserSelectionListener mUserSelectionListener;
     private UserAdapter mAdapter;
@@ -63,6 +64,9 @@
         super(context, attrs);
         mContext = context;
         mCarUserManagerHelper = new CarUserManagerHelper(mContext);
+
+        addItemDecoration(new ItemSpacingDecoration(context.getResources().getDimensionPixelSize(
+                R.dimen.car_user_switcher_vertical_spacing_between_users)));
     }
 
     /**
@@ -391,4 +395,31 @@
 
         void onUserSelected(UserRecord record);
     }
+
+    /**
+     * A {@link RecyclerView.ItemDecoration} that will add spacing between each item in the
+     * RecyclerView that it is added to.
+     */
+    private static class ItemSpacingDecoration extends RecyclerView.ItemDecoration {
+        private int mItemSpacing;
+
+        private ItemSpacingDecoration(int itemSpacing) {
+            mItemSpacing = itemSpacing;
+        }
+
+        @Override
+        public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+                RecyclerView.State state) {
+            super.getItemOffsets(outRect, view, parent, state);
+            int position = parent.getChildAdapterPosition(view);
+
+            // Skip offset for last item except for GridLayoutManager.
+            if (position == state.getItemCount() - 1
+                    && !(parent.getLayoutManager() instanceof GridLayoutManager)) {
+                return;
+            }
+
+            outRect.bottom = mItemSpacing;
+        }
+    }
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
index 10a0ae5..512210b 100644
--- a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
+++ b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
@@ -32,9 +32,7 @@
 import android.content.ServiceConnection;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
-import android.graphics.Color;
 import android.graphics.PixelFormat;
-import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
 import android.os.Debug;
@@ -46,7 +44,6 @@
 import android.util.Log;
 import android.util.SparseArray;
 import android.util.Xml;
-import android.view.ContextThemeWrapper;
 import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.View;
@@ -56,12 +53,8 @@
 import android.widget.SeekBar;
 import android.widget.SeekBar.OnSeekBarChangeListener;
 
-import androidx.car.widget.ListItem;
-import androidx.car.widget.ListItemAdapter;
-import androidx.car.widget.ListItemAdapter.BackgroundStyle;
-import androidx.car.widget.ListItemProvider.ListProvider;
-import androidx.car.widget.PagedListView;
-import androidx.car.widget.SeekbarListItem;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.systemui.R;
 import com.android.systemui.plugins.VolumeDialog;
@@ -96,13 +89,13 @@
     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<>();
+    // Volume items in the RecyclerView.
+    private final List<CarVolumeItem> mCarVolumeLineItems = new ArrayList<>();
     private final KeyguardManager mKeyguard;
     private Window mWindow;
     private CustomDialog mDialog;
-    private PagedListView mListView;
-    private ListItemAdapter mPagedListAdapter;
+    private RecyclerView mListView;
+    private CarVolumeItemAdapter mVolumeItemsAdapter;
     private Car mCar;
     private CarAudioManager mCarAudioManager;
     private final CarAudioManager.CarVolumeCallback mVolumeChangeCallback =
@@ -126,7 +119,7 @@
                     // callback. Updating the seekbar at the same time could block the continuous
                     // seeking.
                     if (value != volumeItem.progress) {
-                        volumeItem.listItem.setProgress(value);
+                        volumeItem.carVolumeItem.setProgress(value);
                         volumeItem.progress = value;
                     }
                     if ((flags & AudioManager.FLAG_SHOW_UI) != 0) {
@@ -160,13 +153,13 @@
                     // The first one is the default item.
                     if (groupId == 0) {
                         mDefaultVolumeItem = volumeItem;
-                        setupDefaultListItem();
+                        setupDefaultCarVolumeItem();
                     }
                 }
 
                 // If list is already initiated, update its content.
-                if (mPagedListAdapter != null) {
-                    mPagedListAdapter.notifyDataSetChanged();
+                if (mVolumeItemsAdapter != null) {
+                    mVolumeItemsAdapter.notifyDataSetChanged();
                 }
                 mCarAudioManager.registerCarVolumeCallback(mVolumeChangeCallback);
             } catch (CarNotConnectedException e) {
@@ -184,15 +177,15 @@
         }
     };
 
-    private void setupDefaultListItem() {
+    private void setupDefaultCarVolumeItem() {
         mDefaultVolumeItem.defaultItem = true;
-        addSeekbarListItem(mDefaultVolumeItem, /* volumeGroupId = */0,
+        addCarVolumeListItem(mDefaultVolumeItem, /* volumeGroupId = */0,
                 R.drawable.car_ic_keyboard_arrow_down, new ExpandIconListener()
         );
     }
 
     public CarVolumeDialogImpl(Context context) {
-        mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
+        mContext = context;
         mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
         mCar = Car.createCar(mContext, mServiceConnection);
     }
@@ -238,7 +231,7 @@
 
     private void initDialog() {
         loadAudioUsageItems();
-        mVolumeLineItems.clear();
+        mCarVolumeLineItems.clear();
         mDialog = new CustomDialog(mContext);
 
         mHovering = false;
@@ -246,7 +239,6 @@
         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
@@ -263,10 +255,11 @@
         lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
         lp.windowAnimations = -1;
         mWindow.setAttributes(lp);
-        mWindow.setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+
+        mDialog.setContentView(R.layout.car_volume_dialog);
+        mWindow.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
 
         mDialog.setCanceledOnTouchOutside(true);
-        mDialog.setContentView(R.layout.car_volume_dialog);
         mDialog.setOnShowListener(dialog -> {
             mListView.setTranslationY(-mListView.getHeight());
             mListView.setAlpha(0);
@@ -277,7 +270,7 @@
                     .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator())
                     .start();
         });
-        mListView = (PagedListView) mWindow.findViewById(R.id.volume_list);
+        mListView = mWindow.findViewById(R.id.volume_list);
         mListView.setOnHoverListener((v, event) -> {
             int action = event.getActionMasked();
             mHovering = (action == MotionEvent.ACTION_HOVER_ENTER)
@@ -286,10 +279,9 @@
             return true;
         });
 
-        mPagedListAdapter = new ListItemAdapter(mContext, new ListProvider(mVolumeLineItems),
-                BackgroundStyle.PANEL);
-        mListView.setAdapter(mPagedListAdapter);
-        mListView.setMaxPages(PagedListView.UNLIMITED_PAGES);
+        mVolumeItemsAdapter = new CarVolumeItemAdapter(mContext, mCarVolumeLineItems);
+        mListView.setAdapter(mVolumeItemsAdapter);
+        mListView.setLayoutManager(new LinearLayoutManager(mContext));
     }
 
 
@@ -302,13 +294,13 @@
         mHandler.removeMessages(H.DISMISS);
         rescheduleTimeoutH();
         // Refresh the data set before showing.
-        mPagedListAdapter.notifyDataSetChanged();
+        mVolumeItemsAdapter.notifyDataSetChanged();
         if (mShowing) {
             return;
         }
         mShowing = true;
-        if (mVolumeLineItems.isEmpty()) {
-            setupDefaultListItem();
+        if (mCarVolumeLineItems.isEmpty()) {
+            setupDefaultCarVolumeItem();
         }
         mDialog.show();
         Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
@@ -421,40 +413,41 @@
         return result;
     }
 
-    private SeekbarListItem addSeekbarListItem(VolumeItem volumeItem,
-            int volumeGroupId,
+    private CarVolumeItem addCarVolumeListItem(VolumeItem volumeItem, int volumeGroupId,
             int supplementalIconId,
             @Nullable View.OnClickListener supplementalIconOnClickListener) {
-        SeekbarListItem listItem = new SeekbarListItem(mContext);
-        listItem.setMax(getMaxSeekbarValue(mCarAudioManager, volumeGroupId));
+        CarVolumeItem carVolumeItem = new CarVolumeItem();
+        carVolumeItem.setMax(getMaxSeekbarValue(mCarAudioManager, volumeGroupId));
         int color = mContext.getResources().getColor(R.color.car_volume_dialog_tint);
         int progress = getSeekbarValue(mCarAudioManager, volumeGroupId);
-        listItem.setProgress(progress);
-        listItem.setOnSeekBarChangeListener(new CarVolumeDialogImpl
-                .VolumeSeekBarChangeListener(volumeGroupId, mCarAudioManager));
+        carVolumeItem.setProgress(progress);
+        carVolumeItem.setOnSeekBarChangeListener(
+                new CarVolumeDialogImpl.VolumeSeekBarChangeListener(volumeGroupId,
+                        mCarAudioManager));
         Drawable primaryIcon = mContext.getResources().getDrawable(volumeItem.icon);
         primaryIcon.mutate().setTint(color);
-        listItem.setPrimaryActionIcon(primaryIcon);
+        carVolumeItem.setPrimaryIcon(primaryIcon);
         if (supplementalIconId != 0) {
             Drawable supplementalIcon = mContext.getResources().getDrawable(supplementalIconId);
             supplementalIcon.mutate().setTint(color);
-            listItem.setSupplementalIcon(supplementalIcon, true);
-            listItem.setSupplementalIconListener(supplementalIconOnClickListener);
+            carVolumeItem.setSupplementalIcon(supplementalIcon,
+                    /* showSupplementalIconDivider= */ true);
+            carVolumeItem.setSupplementalIconListener(supplementalIconOnClickListener);
         } else {
-            listItem.setSupplementalEmptyIcon(true);
-            listItem.setSupplementalIconListener(null);
+            carVolumeItem.setSupplementalIcon(/* drawable= */ null,
+                    /* showSupplementalIconDivider= */ false);
         }
 
-        mVolumeLineItems.add(listItem);
-        volumeItem.listItem = listItem;
+        mCarVolumeLineItems.add(carVolumeItem);
+        volumeItem.carVolumeItem = carVolumeItem;
         volumeItem.progress = progress;
-        return listItem;
+        return carVolumeItem;
     }
 
-    private VolumeItem findVolumeItem(SeekbarListItem targetItem) {
+    private VolumeItem findVolumeItem(CarVolumeItem targetItem) {
         for (int i = 0; i < mVolumeItems.size(); ++i) {
             VolumeItem volumeItem = mVolumeItems.valueAt(i);
-            if (volumeItem.listItem == targetItem) {
+            if (volumeItem.carVolumeItem == targetItem) {
                 return volumeItem;
             }
         }
@@ -463,7 +456,7 @@
 
     private void cleanupAudioManager() {
         mCarAudioManager.unregisterCarVolumeCallback(mVolumeChangeCallback);
-        mVolumeLineItems.clear();
+        mCarVolumeLineItems.clear();
         mCarAudioManager = null;
     }
 
@@ -474,8 +467,9 @@
 
         private int rank;
         private boolean defaultItem = false;
-        private @DrawableRes int icon;
-        private SeekbarListItem listItem;
+        @DrawableRes
+        private int icon;
+        private CarVolumeItem carVolumeItem;
         private int progress;
     }
 
@@ -554,9 +548,9 @@
                 // Adding the items which are not coming from the default item.
                 VolumeItem volumeItem = mAvailableVolumeItems.get(groupId);
                 if (volumeItem.defaultItem) {
-                    updateDefaultVolumeItem(volumeItem.listItem);
+                    updateDefaultVolumeItem(volumeItem.carVolumeItem);
                 } else {
-                    addSeekbarListItem(volumeItem, groupId, 0, null);
+                    addCarVolumeListItem(volumeItem, groupId, 0, null);
                 }
             }
             inAnimator = AnimatorInflater.loadAnimator(
@@ -564,14 +558,14 @@
 
         } else {
             // Only keeping the default stream if it is not expended.
-            Iterator itr = mVolumeLineItems.iterator();
+            Iterator itr = mCarVolumeLineItems.iterator();
             while (itr.hasNext()) {
-                SeekbarListItem seekbarListItem = (SeekbarListItem) itr.next();
-                VolumeItem volumeItem = findVolumeItem(seekbarListItem);
+                CarVolumeItem carVolumeItem = (CarVolumeItem) itr.next();
+                VolumeItem volumeItem = findVolumeItem(carVolumeItem);
                 if (!volumeItem.defaultItem) {
                     itr.remove();
                 } else {
-                    updateDefaultVolumeItem(seekbarListItem);
+                    updateDefaultVolumeItem(carVolumeItem);
                 }
             }
             inAnimator = AnimatorInflater.loadAnimator(
@@ -590,22 +584,22 @@
         }
         animators.setTarget(mExpandIcon);
         animators.start();
-        mPagedListAdapter.notifyDataSetChanged();
+        mVolumeItemsAdapter.notifyDataSetChanged();
     }
 
-    private void updateDefaultVolumeItem(SeekbarListItem seekbarListItem){
-        VolumeItem volumeItem = findVolumeItem(seekbarListItem);
+    private void updateDefaultVolumeItem(CarVolumeItem carVolumeItem) {
+        VolumeItem volumeItem = findVolumeItem(carVolumeItem);
 
         // When volume dialog is expanded or collapsed the default list item is never
         // reset. Whereas all other list items are removed when the dialog is collapsed and then
-        // added when the dialog is expanded using {@link CarVolumeDialogImpl#addSeekbarListItem}.
+        // added when the dialog is expanded using {@link CarVolumeDialogImpl#addCarVolumeListItem}.
         // This sets the progressbar and the tint color of icons for all items other than default
         // if they were changed. For default list item it should be done manually here.
         int color = mContext.getResources().getColor(R.color.car_volume_dialog_tint);
         Drawable primaryIcon = mContext.getResources().getDrawable(volumeItem.icon);
         primaryIcon.mutate().setTint(color);
-        volumeItem.listItem.setPrimaryActionIcon(primaryIcon);
-        volumeItem.listItem.setProgress(volumeItem.progress);
+        volumeItem.carVolumeItem.setPrimaryIcon(primaryIcon);
+        volumeItem.carVolumeItem.setProgress(volumeItem.progress);
     }
 
     private final class VolumeSeekBarChangeListener implements OnSeekBarChangeListener {
@@ -646,4 +640,4 @@
         public void onStopTrackingTouch(SeekBar seekBar) {
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeItem.java b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeItem.java
new file mode 100644
index 0000000..9613de1
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeItem.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+package com.android.systemui.volume;
+
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.SeekBar;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.systemui.R;
+
+/** Holds all related data to represent a volume group. */
+public class CarVolumeItem {
+
+    private boolean mIsDirty;
+
+    private Drawable mPrimaryIcon;
+    private Drawable mSupplementalIcon;
+    private View.OnClickListener mSupplementalIconOnClickListener;
+    private boolean mShowSupplementalIconDivider;
+
+    private int mMax;
+    private int mProgress;
+    private SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener;
+
+    public CarVolumeItem() {
+        mIsDirty = true;
+    }
+
+    /**
+     * Called when {@link CarVolumeItem} is bound to its ViewHolder.
+     */
+    void bind(CarVolumeItemViewHolder viewHolder) {
+        if (mIsDirty) {
+            viewHolder.bind(/* carVolumeItem= */ this);
+            mIsDirty = false;
+        }
+    }
+
+    /** Sets progress of seekbar. */
+    public void setProgress(int progress) {
+        mProgress = progress;
+        mIsDirty = true;
+    }
+
+    /** Sets max value of seekbar. */
+    public void setMax(int max) {
+        mMax = max;
+        mIsDirty = true;
+    }
+
+    /** Sets {@link SeekBar.OnSeekBarChangeListener}. */
+    public void setOnSeekBarChangeListener(SeekBar.OnSeekBarChangeListener listener) {
+        mOnSeekBarChangeListener = listener;
+        mIsDirty = true;
+    }
+
+    /** Sets the primary icon. */
+    public void setPrimaryIcon(Drawable drawable) {
+        mPrimaryIcon = drawable;
+        mIsDirty = true;
+    }
+
+    /** Sets the supplemental icon and the visibility of the supplemental icon divider. */
+    public void setSupplementalIcon(Drawable drawable, boolean showSupplementalIconDivider) {
+        mSupplementalIcon = drawable;
+        mShowSupplementalIconDivider = showSupplementalIconDivider;
+        mIsDirty = true;
+    }
+
+    /** Sets {@code OnClickListener} for the supplemental icon. */
+    public void setSupplementalIconListener(View.OnClickListener listener) {
+        mSupplementalIconOnClickListener = listener;
+        mIsDirty = true;
+    }
+
+    /** Defines the view holder which shows the information held by {@link CarVolumeItem}. */
+    public static class CarVolumeItemViewHolder extends RecyclerView.ViewHolder {
+
+        private SeekBar mSeekBar;
+        private ImageView mPrimaryIcon;
+        private View mSupplementalIconDivider;
+        private ImageView mSupplementalIcon;
+
+        public CarVolumeItemViewHolder(@NonNull View itemView) {
+            super(itemView);
+
+            mSeekBar = itemView.findViewById(R.id.seek_bar);
+            mPrimaryIcon = itemView.findViewById(R.id.primary_icon);
+            mSupplementalIcon = itemView.findViewById(R.id.supplemental_icon);
+            mSupplementalIconDivider = itemView.findViewById(R.id.supplemental_icon_divider);
+        }
+
+        /**
+         * Binds {@link CarVolumeItem} to the {@link CarVolumeItemViewHolder}.
+         */
+        void bind(CarVolumeItem carVolumeItem) {
+            // Progress bar
+            mSeekBar.setMax(carVolumeItem.mMax);
+            mSeekBar.setProgress(carVolumeItem.mProgress);
+            mSeekBar.setOnSeekBarChangeListener(carVolumeItem.mOnSeekBarChangeListener);
+
+            // Primary icon
+            mPrimaryIcon.setVisibility(View.VISIBLE);
+            mPrimaryIcon.setImageDrawable(carVolumeItem.mPrimaryIcon);
+
+            // Supplemental icon
+            mSupplementalIcon.setVisibility(View.VISIBLE);
+            mSupplementalIconDivider.setVisibility(
+                    carVolumeItem.mShowSupplementalIconDivider ? View.VISIBLE : View.INVISIBLE);
+            mSupplementalIcon.setImageDrawable(carVolumeItem.mSupplementalIcon);
+            mSupplementalIcon.setOnClickListener(
+                    carVolumeItem.mSupplementalIconOnClickListener);
+            mSupplementalIcon.setClickable(
+                    carVolumeItem.mSupplementalIconOnClickListener != null);
+        }
+    }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeItemAdapter.java b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeItemAdapter.java
new file mode 100644
index 0000000..5c1f817
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeItemAdapter.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+package com.android.systemui.volume;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.systemui.R;
+
+import java.util.List;
+
+/** The {@link RecyclerView.Adapter} to show the volume items in the sysUI volume dialog. */
+public class CarVolumeItemAdapter extends
+        RecyclerView.Adapter<CarVolumeItem.CarVolumeItemViewHolder> {
+
+    private final Context mContext;
+    private final List<CarVolumeItem> mItems;
+
+    public CarVolumeItemAdapter(Context context, List<CarVolumeItem> items) {
+        mContext = context;
+        mItems = items;
+    }
+
+    @Override
+    public CarVolumeItem.CarVolumeItemViewHolder onCreateViewHolder(ViewGroup parent,
+            int viewType) {
+        LayoutInflater inflater = LayoutInflater.from(mContext);
+        View view = inflater.inflate(R.layout.car_volume_item, parent, false);
+        return new CarVolumeItem.CarVolumeItemViewHolder(view);
+    }
+
+    @Override
+    public void onBindViewHolder(CarVolumeItem.CarVolumeItemViewHolder holder, int position) {
+        mItems.get(position).bind(holder);
+    }
+
+    @Override
+    public int getItemCount() {
+        return mItems.size();
+    }
+}