From 0c4db3170d99d7a29b857e19708a72c2b45c826c Mon Sep 17 00:00:00 2001 From: timhypeng Date: Mon, 3 Feb 2020 14:58:12 +0800 Subject: [PATCH] Add title and icon in output switcher panel header -title shows artist -subtitle shows album -add test cases Bug: 147776885 Test: make -j42 RunSettingsRoboTests Change-Id: Ib33e5550e668d8cc5d70051ea2e7dd74d61c767a --- .../settings/panel/MediaOutputPanel.java | 83 ++++++++++++++ .../android/settings/panel/PanelContent.java | 10 +- .../android/settings/panel/PanelFragment.java | 8 +- .../settings/panel/FakePanelContent.java | 12 +- .../settings/panel/MediaOutputPanelTest.java | 105 +++++++++++++++++- .../settings/panel/PanelFragmentTest.java | 5 +- 6 files changed, 207 insertions(+), 16 deletions(-) diff --git a/src/com/android/settings/panel/MediaOutputPanel.java b/src/com/android/settings/panel/MediaOutputPanel.java index c42906d1302..d6030cc4096 100644 --- a/src/com/android/settings/panel/MediaOutputPanel.java +++ b/src/com/android/settings/panel/MediaOutputPanel.java @@ -22,9 +22,22 @@ import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_SLICE import android.app.settings.SettingsEnums; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.media.MediaMetadata; +import android.media.session.MediaController; +import android.media.session.MediaSessionManager; import android.net.Uri; +import android.text.TextUtils; +import android.util.Log; + +import androidx.core.graphics.drawable.IconCompat; import com.android.settings.R; +import com.android.settings.Utils; import java.util.ArrayList; import java.util.List; @@ -38,9 +51,14 @@ import java.util.List; */ public class MediaOutputPanel implements PanelContent { + private static final String TAG = "MediaOutputPanel"; + private final Context mContext; private final String mPackageName; + private MediaSessionManager mMediaSessionManager; + private MediaController mMediaController; + public static MediaOutputPanel create(Context context, String packageName) { return new MediaOutputPanel(context, packageName); } @@ -48,13 +66,78 @@ public class MediaOutputPanel implements PanelContent { private MediaOutputPanel(Context context, String packageName) { mContext = context.getApplicationContext(); mPackageName = packageName; + if (mPackageName != null) { + mMediaSessionManager = mContext.getSystemService(MediaSessionManager.class); + for (MediaController controller : mMediaSessionManager.getActiveSessions(null)) { + if (TextUtils.equals(controller.getPackageName(), mPackageName)) { + mMediaController = controller; + break; + } + } + } + if (mMediaController == null) { + Log.e(TAG, "Unable to find " + mPackageName + " media controller"); + } } @Override public CharSequence getTitle() { + if (mMediaController != null) { + final MediaMetadata metadata = mMediaController.getMetadata(); + if (metadata != null) { + return metadata.getString(MediaMetadata.METADATA_KEY_ARTIST); + } + } + return mContext.getText(R.string.media_volume_title); + } + + @Override + public CharSequence getSubTitle() { + if (mMediaController != null) { + final MediaMetadata metadata = mMediaController.getMetadata(); + if (metadata != null) { + return metadata.getString(MediaMetadata.METADATA_KEY_ALBUM); + } + } return mContext.getText(R.string.media_output_panel_title); } + @Override + public IconCompat getIcon() { + if (mMediaController == null) { + return IconCompat.createWithResource(mContext, R.drawable.ic_media_stream).setTint( + Utils.getColorAccentDefaultColor(mContext)); + } + final MediaMetadata metadata = mMediaController.getMetadata(); + if (metadata != null) { + final Bitmap bitmap = metadata.getDescription().getIconBitmap(); + if (bitmap != null) { + return IconCompat.createWithBitmap(bitmap); + } + } + Log.d(TAG, "Media meta data does not contain icon information"); + return getPackageIcon(); + } + + private IconCompat getPackageIcon() { + try { + final Drawable drawable = mContext.getPackageManager().getApplicationIcon(mPackageName); + if (drawable instanceof BitmapDrawable) { + return IconCompat.createWithBitmap(((BitmapDrawable) drawable).getBitmap()); + } + final Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + + return IconCompat.createWithBitmap(bitmap); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Package is not found. Unable to get package icon."); + } + return null; + } + @Override public List getSlices() { final List uris = new ArrayList<>(); diff --git a/src/com/android/settings/panel/PanelContent.java b/src/com/android/settings/panel/PanelContent.java index badaeb1ba46..56704694f99 100644 --- a/src/com/android/settings/panel/PanelContent.java +++ b/src/com/android/settings/panel/PanelContent.java @@ -19,6 +19,8 @@ package com.android.settings.panel; import android.content.Intent; import android.net.Uri; +import androidx.core.graphics.drawable.IconCompat; + import com.android.settingslib.core.instrumentation.Instrumentable; import java.util.List; @@ -28,13 +30,11 @@ import java.util.List; */ public interface PanelContent extends Instrumentable { - int ICON_UNAVAILABLE = -1; - /** - * @return a icon resource for the title of the Panel. + * @return a icon for the title of the Panel. */ - default int getIcon() { - return ICON_UNAVAILABLE; + default IconCompat getIcon() { + return null; } /** diff --git a/src/com/android/settings/panel/PanelFragment.java b/src/com/android/settings/panel/PanelFragment.java index 40706fe6b86..8fda894adaf 100644 --- a/src/com/android/settings/panel/PanelFragment.java +++ b/src/com/android/settings/panel/PanelFragment.java @@ -38,6 +38,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.graphics.drawable.IconCompat; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.lifecycle.LiveData; @@ -185,20 +186,19 @@ public class PanelFragment extends Fragment { mMetricsProvider = FeatureFactory.getFactory(activity).getMetricsFeatureProvider(); mPanelSlices.setLayoutManager(new LinearLayoutManager((activity))); - // Add predraw listener to remove the animation and while we wait for Slices to load. mLayoutView.getViewTreeObserver().addOnPreDrawListener(mOnPreDrawListener); // Start loading Slices. When finished, the Panel will animate in. loadAllSlices(); - final int iconRes = mPanel.getIcon(); - if (iconRes == PanelContent.ICON_UNAVAILABLE) { + final IconCompat icon = mPanel.getIcon(); + if (icon == null) { mTitleView.setText(mPanel.getTitle()); } else { mTitleView.setVisibility(View.GONE); mPanelHeader.setVisibility(View.VISIBLE); - mTitleIcon.setImageResource(iconRes); + mTitleIcon.setImageIcon(icon.toIcon(getContext())); mHeaderTitle.setText(mPanel.getTitle()); mHeaderSubtitle.setText(mPanel.getSubTitle()); } diff --git a/tests/robotests/src/com/android/settings/panel/FakePanelContent.java b/tests/robotests/src/com/android/settings/panel/FakePanelContent.java index 3b4d2c42a3e..8888093493a 100644 --- a/tests/robotests/src/com/android/settings/panel/FakePanelContent.java +++ b/tests/robotests/src/com/android/settings/panel/FakePanelContent.java @@ -22,6 +22,8 @@ import android.app.settings.SettingsEnums; import android.content.Intent; import android.net.Uri; +import androidx.core.graphics.drawable.IconCompat; + import java.util.Arrays; import java.util.List; @@ -41,11 +43,11 @@ public class FakePanelContent implements PanelContent { public static final Intent INTENT = new Intent(); private CharSequence mSubTitle; - private int mIconRes = -1; + private IconCompat mIcon; @Override - public int getIcon() { - return mIconRes; + public IconCompat getIcon() { + return mIcon; } @Override @@ -53,8 +55,8 @@ public class FakePanelContent implements PanelContent { return mSubTitle; } - public void setIcon(int iconRes) { - mIconRes = iconRes; + public void setIcon(IconCompat icon) { + mIcon = icon; } public void setSubTitle(CharSequence subTitle) { diff --git a/tests/robotests/src/com/android/settings/panel/MediaOutputPanelTest.java b/tests/robotests/src/com/android/settings/panel/MediaOutputPanelTest.java index b4110376226..fb7f0ae62b9 100644 --- a/tests/robotests/src/com/android/settings/panel/MediaOutputPanelTest.java +++ b/tests/robotests/src/com/android/settings/panel/MediaOutputPanelTest.java @@ -20,28 +20,59 @@ import static com.android.settings.media.MediaOutputSlice.MEDIA_PACKAGE_NAME; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.media.MediaMetadata; +import android.media.session.MediaController; +import android.media.session.MediaSessionManager; import android.net.Uri; +import com.android.settings.R; import com.android.settings.slices.CustomSliceRegistry; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import java.util.ArrayList; import java.util.List; @RunWith(RobolectricTestRunner.class) public class MediaOutputPanelTest { private static final String TEST_PACKAGENAME = "com.test.packagename"; + private static final String TEST_ARTIST = "test_artist"; + private static final String TEST_ALBUM = "test_album"; + + @Mock + private MediaSessionManager mMediaSessionManager; + @Mock + private MediaController mMediaController; + @Mock + private MediaMetadata mMediaMetadata; private MediaOutputPanel mPanel; + private Context mContext; + private List mMediaControllers = new ArrayList<>(); @Before public void setUp() { - mPanel = MediaOutputPanel.create(RuntimeEnvironment.application, TEST_PACKAGENAME); + MockitoAnnotations.initMocks(this); + + mContext = spy(RuntimeEnvironment.application); + mMediaControllers.add(mMediaController); + when(mMediaController.getPackageName()).thenReturn(TEST_PACKAGENAME); + when(mMediaSessionManager.getActiveSessions(any())).thenReturn(mMediaControllers); + when(mContext.getApplicationContext()).thenReturn(mContext); + when(mContext.getSystemService(MediaSessionManager.class)).thenReturn(mMediaSessionManager); + mPanel = MediaOutputPanel.create(mContext, TEST_PACKAGENAME); } @Test @@ -62,4 +93,76 @@ public class MediaOutputPanelTest { public void getSeeMoreIntent_isNull() { assertThat(mPanel.getSeeMoreIntent()).isNull(); } + + @Test + public void getTitle_withMetadata_returnArtistName() { + when(mMediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST)).thenReturn(TEST_ARTIST); + when(mMediaController.getMetadata()).thenReturn(mMediaMetadata); + + assertThat(mPanel.getTitle()).isEqualTo(TEST_ARTIST); + } + + @Test + public void getTitle_noMetadata_returnDefaultString() { + when(mMediaController.getMetadata()).thenReturn(null); + + assertThat(mPanel.getTitle()).isEqualTo(mContext.getText(R.string.media_volume_title)); + } + + @Test + public void getTitle_noPackageName_returnDefaultString() { + mPanel = MediaOutputPanel.create(mContext, null); + when(mMediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST)).thenReturn(TEST_ARTIST); + when(mMediaController.getMetadata()).thenReturn(mMediaMetadata); + + assertThat(mPanel.getTitle()).isEqualTo(mContext.getText(R.string.media_volume_title)); + } + + @Test + public void getTitle_noController_defaultString() { + mMediaControllers.clear(); + when(mMediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST)).thenReturn(TEST_ARTIST); + when(mMediaController.getMetadata()).thenReturn(mMediaMetadata); + mPanel = MediaOutputPanel.create(mContext, TEST_PACKAGENAME); + + assertThat(mPanel.getTitle()).isEqualTo(mContext.getText(R.string.media_volume_title)); + } + + @Test + public void getSubTitle_withMetadata_returnAlbumName() { + when(mMediaMetadata.getString(MediaMetadata.METADATA_KEY_ALBUM)).thenReturn(TEST_ALBUM); + when(mMediaController.getMetadata()).thenReturn(mMediaMetadata); + + assertThat(mPanel.getSubTitle()).isEqualTo(TEST_ALBUM); + } + + @Test + public void getSubTitle_noMetadata_returnDefaultString() { + when(mMediaController.getPackageName()).thenReturn(TEST_PACKAGENAME); + when(mMediaController.getMetadata()).thenReturn(null); + + assertThat(mPanel.getSubTitle()).isEqualTo(mContext.getText( + R.string.media_output_panel_title)); + } + + @Test + public void getSubTitle_noPackageName_returnDefaultString() { + mPanel = MediaOutputPanel.create(mContext, null); + when(mMediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST)).thenReturn(TEST_ARTIST); + when(mMediaController.getMetadata()).thenReturn(mMediaMetadata); + + assertThat(mPanel.getSubTitle()).isEqualTo(mContext.getText( + R.string.media_output_panel_title)); + } + + @Test + public void getSubTitle_noController_returnDefaultString() { + mMediaControllers.clear(); + mPanel = MediaOutputPanel.create(mContext, TEST_PACKAGENAME); + when(mMediaMetadata.getString(MediaMetadata.METADATA_KEY_ALBUM)).thenReturn(TEST_ALBUM); + when(mMediaController.getMetadata()).thenReturn(mMediaMetadata); + + assertThat(mPanel.getSubTitle()).isEqualTo(mContext.getText( + R.string.media_output_panel_title)); + } } diff --git a/tests/robotests/src/com/android/settings/panel/PanelFragmentTest.java b/tests/robotests/src/com/android/settings/panel/PanelFragmentTest.java index ee4131e1abe..1976557c0a4 100644 --- a/tests/robotests/src/com/android/settings/panel/PanelFragmentTest.java +++ b/tests/robotests/src/com/android/settings/panel/PanelFragmentTest.java @@ -33,6 +33,8 @@ import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.core.graphics.drawable.IconCompat; + import com.android.settings.R; import com.android.settings.testutils.FakeFeatureFactory; @@ -150,7 +152,8 @@ public class PanelFragmentTest { @Test public void supportIcon_displayIconHeaderLayout() { - mFakePanelContent.setIcon(R.drawable.ic_android); + final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.ic_android); + mFakePanelContent.setIcon(icon); mFakePanelContent.setSubTitle(SUBTITLE); final ActivityController activityController = Robolectric.buildActivity(FakeSettingsPanelActivity.class);