Add dynamic item in output switcher if it is available

-Add extra item for dynamic group if getSelectedMediaDevice() > 1
-Move common method to base class

Bug: 171455839
Test: atest MediaOutputAdapterTest MediaOutputControllerTest MediaOutputBaseDialogTest MediaOutputDialogTest MediaOutputGroupAdapterTest MediaOutputGroupDialogTest
Merged-In: I5990df864cb7c5795e2d1a1cd404807d265b4cc3
Change-Id: I5990df864cb7c5795e2d1a1cd404807d265b4cc3
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index d26f7ab..0d5faff 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -22,6 +22,7 @@
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.drawable.Drawable;
 import android.text.SpannableString;
+import android.text.TextUtils;
 import android.text.style.ForegroundColorSpan;
 import android.util.Log;
 import android.view.View;
@@ -45,6 +46,7 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private ViewGroup mConnectedItem;
+    private boolean mInclueDynamicGroup;
 
     public MediaOutputAdapter(MediaOutputController controller) {
         super(controller);
@@ -61,9 +63,21 @@
     @Override
     public void onBindViewHolder(@NonNull MediaDeviceBaseViewHolder viewHolder, int position) {
         final int size = mController.getMediaDevices().size();
-        if (mController.isZeroMode() && position == size) {
+        if (position == size && mController.isZeroMode()) {
             viewHolder.onBind(CUSTOMIZED_ITEM_PAIR_NEW, false /* topMargin */,
                     true /* bottomMargin */);
+        } else if (mInclueDynamicGroup) {
+            if (position == 0) {
+                viewHolder.onBind(CUSTOMIZED_ITEM_DYNAMIC_GROUP, true /* topMargin */,
+                        false /* bottomMargin */);
+            } else {
+                // When group item is added at the first(position == 0), devices will be added from
+                // the second item(position == 1). It means that the index of device list starts
+                // from "position - 1".
+                viewHolder.onBind(((List<MediaDevice>) (mController.getMediaDevices()))
+                                .get(position - 1),
+                        false /* topMargin */, position == size /* bottomMargin */);
+            }
         } else if (position < size) {
             viewHolder.onBind(((List<MediaDevice>) (mController.getMediaDevices())).get(position),
                     position == 0 /* topMargin */, position == (size - 1) /* bottomMargin */);
@@ -74,8 +88,9 @@
 
     @Override
     public int getItemCount() {
-        if (mController.isZeroMode()) {
-            // Add extra one for "pair new"
+        mInclueDynamicGroup = mController.getSelectedMediaDevice().size() > 1;
+        if (mController.isZeroMode() || mInclueDynamicGroup) {
+            // Add extra one for "pair new" or dynamic group
             return mController.getMediaDevices().size() + 1;
         }
         return mController.getMediaDevices().size();
@@ -107,7 +122,7 @@
         @Override
         void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin) {
             super.onBind(device, topMargin, bottomMargin);
-            final boolean currentlyConnected = isCurrentlyConnected(device);
+            final boolean currentlyConnected = !mInclueDynamicGroup && isCurrentlyConnected(device);
             if (currentlyConnected) {
                 mConnectedItem = mContainerLayout;
             }
@@ -167,6 +182,22 @@
                         Utils.getColorAccentDefaultColor(mContext), PorterDuff.Mode.SRC_IN));
                 mTitleIcon.setImageDrawable(d);
                 mContainerLayout.setOnClickListener(v -> onItemClick(CUSTOMIZED_ITEM_PAIR_NEW));
+            } else if (customizedItem == CUSTOMIZED_ITEM_DYNAMIC_GROUP) {
+                mConnectedItem = mContainerLayout;
+                mBottomDivider.setVisibility(View.GONE);
+                mCheckBox.setVisibility(View.GONE);
+                mDivider.setVisibility(View.VISIBLE);
+                mDivider.setTransitionAlpha(1);
+                mAddIcon.setVisibility(View.VISIBLE);
+                mAddIcon.setTransitionAlpha(1);
+                mAddIcon.setOnClickListener(v -> onEndItemClick());
+                mTitleIcon.setImageDrawable(getSpeakerDrawable());
+                final CharSequence sessionName = mController.getSessionName();
+                final CharSequence title = TextUtils.isEmpty(sessionName)
+                        ? mContext.getString(R.string.media_output_dialog_group) : sessionName;
+                setTwoLineLayout(title, true /* bFocused */, true /* showSeekBar */,
+                        false /* showProgressBar */, false /* showSubtitle */);
+                initSessionSeekbar();
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 536b759..f1d4804 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -19,7 +19,11 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
 import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
 import android.text.TextUtils;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -35,6 +39,7 @@
 import androidx.annotation.NonNull;
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.android.settingslib.bluetooth.BluetoothUtils;
 import com.android.settingslib.media.MediaDevice;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
@@ -47,6 +52,7 @@
 
     static final int CUSTOMIZED_ITEM_PAIR_NEW = 1;
     static final int CUSTOMIZED_ITEM_GROUP = 2;
+    static final int CUSTOMIZED_ITEM_DYNAMIC_GROUP = 3;
 
     final MediaOutputController mController;
 
@@ -223,6 +229,34 @@
             });
         }
 
+        void initSessionSeekbar() {
+            mSeekBar.setMax(mController.getSessionVolumeMax());
+            mSeekBar.setMin(0);
+            final int currentVolume = mController.getSessionVolume();
+            if (mSeekBar.getProgress() != currentVolume) {
+                mSeekBar.setProgress(currentVolume);
+            }
+            mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+                @Override
+                public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                    if (!fromUser) {
+                        return;
+                    }
+                    mController.adjustSessionVolume(progress);
+                }
+
+                @Override
+                public void onStartTrackingTouch(SeekBar seekBar) {
+                    mIsDragging = true;
+                }
+
+                @Override
+                public void onStopTrackingTouch(SeekBar seekBar) {
+                    mIsDragging = false;
+                }
+            });
+        }
+
         void playSwitchingAnim(@NonNull View from, @NonNull View to) {
             final float delta = (float) (mContext.getResources().getDimensionPixelSize(
                     R.dimen.media_output_dialog_title_anim_y_delta));
@@ -274,5 +308,15 @@
                         }
                     });
         }
+
+        Drawable getSpeakerDrawable() {
+            final Drawable drawable = mContext.getDrawable(R.drawable.ic_speaker_group_black_24dp)
+                    .mutate();
+            final ColorStateList list = mContext.getResources().getColorStateList(
+                    R.color.advanced_icon_color, mContext.getTheme());
+            drawable.setColorFilter(new PorterDuffColorFilter(list.getDefaultColor(),
+                    PorterDuff.Mode.SRC_IN));
+            return BluetoothUtils.buildAdvancedDrawable(mContext, drawable);
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java
index ceb4495..24e076b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java
@@ -16,22 +16,17 @@
 
 package com.android.systemui.media.dialog;
 
-import android.content.res.ColorStateList;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
 import android.graphics.drawable.Drawable;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.TypedValue;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.SeekBar;
 
 import androidx.annotation.NonNull;
 
-import com.android.settingslib.bluetooth.BluetoothUtils;
 import com.android.settingslib.media.MediaDevice;
 import com.android.systemui.R;
 
@@ -155,34 +150,6 @@
             }
         }
 
-        private void initSessionSeekbar() {
-            mSeekBar.setMax(mController.getSessionVolumeMax());
-            mSeekBar.setMin(0);
-            final int currentVolume = mController.getSessionVolume();
-            if (mSeekBar.getProgress() != currentVolume) {
-                mSeekBar.setProgress(currentVolume);
-            }
-            mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
-                @Override
-                public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-                    if (!fromUser) {
-                        return;
-                    }
-                    mController.adjustSessionVolume(progress);
-                }
-
-                @Override
-                public void onStartTrackingTouch(SeekBar seekBar) {
-                    mIsDragging = true;
-                }
-
-                @Override
-                public void onStopTrackingTouch(SeekBar seekBar) {
-                    mIsDragging = false;
-                }
-            });
-        }
-
         private Drawable getDisabledCheckboxDrawable() {
             final Drawable drawable = mContext.getDrawable(R.drawable.ic_check_box_blue_24dp)
                     .mutate();
@@ -198,16 +165,6 @@
             return drawable;
         }
 
-        private Drawable getSpeakerDrawable() {
-            final Drawable drawable = mContext.getDrawable(R.drawable.ic_speaker_group_black_24dp)
-                    .mutate();
-            final ColorStateList list = mContext.getResources().getColorStateList(
-                            R.color.advanced_icon_color, mContext.getTheme());
-            drawable.setColorFilter(new PorterDuffColorFilter(list.getDefaultColor(),
-                    PorterDuff.Mode.SRC_IN));
-            return BluetoothUtils.buildAdvancedDrawable(mContext, drawable);
-        }
-
         private boolean isDeviceIncluded(List<MediaDevice> deviceList, MediaDevice targetDevice) {
             for (MediaDevice device : deviceList) {
                 if (TextUtils.equals(device.getId(), targetDevice.getId())) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 5218886..6e21642 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -50,6 +50,7 @@
     private static final String TEST_DEVICE_NAME_2 = "test_device_name_2";
     private static final String TEST_DEVICE_ID_1 = "test_device_id_1";
     private static final String TEST_DEVICE_ID_2 = "test_device_id_2";
+    private static final String TEST_SESSION_NAME = "test_session_name";
 
     // Mock
     private MediaOutputController mMediaOutputController = mock(MediaOutputController.class);
@@ -102,6 +103,14 @@
     }
 
     @Test
+    public void getItemCount_withDynamicGroup_containExtraOneForGroup() {
+        when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(mMediaDevices);
+        when(mMediaOutputController.isZeroMode()).thenReturn(false);
+
+        assertThat(mMediaOutputAdapter.getItemCount()).isEqualTo(mMediaDevices.size() + 1);
+    }
+
+    @Test
     public void onBindViewHolder_zeroMode_bindPairNew_verifyView() {
         when(mMediaOutputController.isZeroMode()).thenReturn(true);
         mMediaOutputAdapter.onBindViewHolder(mViewHolder, 2);
@@ -118,6 +127,45 @@
     }
 
     @Test
+    public void onBindViewHolder_bindGroup_withSessionName_verifyView() {
+        when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(mMediaDevices);
+        when(mMediaOutputController.isZeroMode()).thenReturn(false);
+        when(mMediaOutputController.getSessionName()).thenReturn(TEST_SESSION_NAME);
+        mMediaOutputAdapter.getItemCount();
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+        assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mDivider.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mAddIcon.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mTwoLineTitleText.getText()).isEqualTo(TEST_SESSION_NAME);
+    }
+
+    @Test
+    public void onBindViewHolder_bindGroup_noSessionName_verifyView() {
+        when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(mMediaDevices);
+        when(mMediaOutputController.isZeroMode()).thenReturn(false);
+        when(mMediaOutputController.getSessionName()).thenReturn(null);
+        mMediaOutputAdapter.getItemCount();
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+        assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mDivider.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mAddIcon.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mTwoLineTitleText.getText()).isEqualTo(mContext.getString(
+                R.string.media_output_dialog_group));
+    }
+
+    @Test
     public void onBindViewHolder_bindConnectedDevice_verifyView() {
         mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);