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
This commit is contained in:
@@ -22,9 +22,22 @@ import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_SLICE
|
|||||||
import android.app.settings.SettingsEnums;
|
import android.app.settings.SettingsEnums;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
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.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.R;
|
||||||
|
import com.android.settings.Utils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -38,9 +51,14 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class MediaOutputPanel implements PanelContent {
|
public class MediaOutputPanel implements PanelContent {
|
||||||
|
|
||||||
|
private static final String TAG = "MediaOutputPanel";
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final String mPackageName;
|
private final String mPackageName;
|
||||||
|
|
||||||
|
private MediaSessionManager mMediaSessionManager;
|
||||||
|
private MediaController mMediaController;
|
||||||
|
|
||||||
public static MediaOutputPanel create(Context context, String packageName) {
|
public static MediaOutputPanel create(Context context, String packageName) {
|
||||||
return new MediaOutputPanel(context, packageName);
|
return new MediaOutputPanel(context, packageName);
|
||||||
}
|
}
|
||||||
@@ -48,13 +66,78 @@ public class MediaOutputPanel implements PanelContent {
|
|||||||
private MediaOutputPanel(Context context, String packageName) {
|
private MediaOutputPanel(Context context, String packageName) {
|
||||||
mContext = context.getApplicationContext();
|
mContext = context.getApplicationContext();
|
||||||
mPackageName = packageName;
|
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
|
@Override
|
||||||
public CharSequence getTitle() {
|
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);
|
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
|
@Override
|
||||||
public List<Uri> getSlices() {
|
public List<Uri> getSlices() {
|
||||||
final List<Uri> uris = new ArrayList<>();
|
final List<Uri> uris = new ArrayList<>();
|
||||||
|
@@ -19,6 +19,8 @@ package com.android.settings.panel;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import androidx.core.graphics.drawable.IconCompat;
|
||||||
|
|
||||||
import com.android.settingslib.core.instrumentation.Instrumentable;
|
import com.android.settingslib.core.instrumentation.Instrumentable;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -28,13 +30,11 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public interface PanelContent extends Instrumentable {
|
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() {
|
default IconCompat getIcon() {
|
||||||
return ICON_UNAVAILABLE;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -38,6 +38,7 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.graphics.drawable.IconCompat;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
@@ -185,20 +186,19 @@ public class PanelFragment extends Fragment {
|
|||||||
mMetricsProvider = FeatureFactory.getFactory(activity).getMetricsFeatureProvider();
|
mMetricsProvider = FeatureFactory.getFactory(activity).getMetricsFeatureProvider();
|
||||||
|
|
||||||
mPanelSlices.setLayoutManager(new LinearLayoutManager((activity)));
|
mPanelSlices.setLayoutManager(new LinearLayoutManager((activity)));
|
||||||
|
|
||||||
// Add predraw listener to remove the animation and while we wait for Slices to load.
|
// Add predraw listener to remove the animation and while we wait for Slices to load.
|
||||||
mLayoutView.getViewTreeObserver().addOnPreDrawListener(mOnPreDrawListener);
|
mLayoutView.getViewTreeObserver().addOnPreDrawListener(mOnPreDrawListener);
|
||||||
|
|
||||||
// Start loading Slices. When finished, the Panel will animate in.
|
// Start loading Slices. When finished, the Panel will animate in.
|
||||||
loadAllSlices();
|
loadAllSlices();
|
||||||
|
|
||||||
final int iconRes = mPanel.getIcon();
|
final IconCompat icon = mPanel.getIcon();
|
||||||
if (iconRes == PanelContent.ICON_UNAVAILABLE) {
|
if (icon == null) {
|
||||||
mTitleView.setText(mPanel.getTitle());
|
mTitleView.setText(mPanel.getTitle());
|
||||||
} else {
|
} else {
|
||||||
mTitleView.setVisibility(View.GONE);
|
mTitleView.setVisibility(View.GONE);
|
||||||
mPanelHeader.setVisibility(View.VISIBLE);
|
mPanelHeader.setVisibility(View.VISIBLE);
|
||||||
mTitleIcon.setImageResource(iconRes);
|
mTitleIcon.setImageIcon(icon.toIcon(getContext()));
|
||||||
mHeaderTitle.setText(mPanel.getTitle());
|
mHeaderTitle.setText(mPanel.getTitle());
|
||||||
mHeaderSubtitle.setText(mPanel.getSubTitle());
|
mHeaderSubtitle.setText(mPanel.getSubTitle());
|
||||||
}
|
}
|
||||||
|
@@ -22,6 +22,8 @@ import android.app.settings.SettingsEnums;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import androidx.core.graphics.drawable.IconCompat;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -41,11 +43,11 @@ public class FakePanelContent implements PanelContent {
|
|||||||
public static final Intent INTENT = new Intent();
|
public static final Intent INTENT = new Intent();
|
||||||
|
|
||||||
private CharSequence mSubTitle;
|
private CharSequence mSubTitle;
|
||||||
private int mIconRes = -1;
|
private IconCompat mIcon;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getIcon() {
|
public IconCompat getIcon() {
|
||||||
return mIconRes;
|
return mIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -53,8 +55,8 @@ public class FakePanelContent implements PanelContent {
|
|||||||
return mSubTitle;
|
return mSubTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setIcon(int iconRes) {
|
public void setIcon(IconCompat icon) {
|
||||||
mIconRes = iconRes;
|
mIcon = icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSubTitle(CharSequence subTitle) {
|
public void setSubTitle(CharSequence subTitle) {
|
||||||
|
@@ -20,28 +20,59 @@ import static com.android.settings.media.MediaOutputSlice.MEDIA_PACKAGE_NAME;
|
|||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
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 android.net.Uri;
|
||||||
|
|
||||||
|
import com.android.settings.R;
|
||||||
import com.android.settings.slices.CustomSliceRegistry;
|
import com.android.settings.slices.CustomSliceRegistry;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
import org.robolectric.RobolectricTestRunner;
|
import org.robolectric.RobolectricTestRunner;
|
||||||
import org.robolectric.RuntimeEnvironment;
|
import org.robolectric.RuntimeEnvironment;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner.class)
|
@RunWith(RobolectricTestRunner.class)
|
||||||
public class MediaOutputPanelTest {
|
public class MediaOutputPanelTest {
|
||||||
|
|
||||||
private static final String TEST_PACKAGENAME = "com.test.packagename";
|
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 MediaOutputPanel mPanel;
|
||||||
|
private Context mContext;
|
||||||
|
private List<MediaController> mMediaControllers = new ArrayList<>();
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
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
|
@Test
|
||||||
@@ -62,4 +93,76 @@ public class MediaOutputPanelTest {
|
|||||||
public void getSeeMoreIntent_isNull() {
|
public void getSeeMoreIntent_isNull() {
|
||||||
assertThat(mPanel.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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -33,6 +33,8 @@ import android.view.View;
|
|||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.core.graphics.drawable.IconCompat;
|
||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.testutils.FakeFeatureFactory;
|
import com.android.settings.testutils.FakeFeatureFactory;
|
||||||
|
|
||||||
@@ -150,7 +152,8 @@ public class PanelFragmentTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void supportIcon_displayIconHeaderLayout() {
|
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);
|
mFakePanelContent.setSubTitle(SUBTITLE);
|
||||||
final ActivityController<FakeSettingsPanelActivity> activityController =
|
final ActivityController<FakeSettingsPanelActivity> activityController =
|
||||||
Robolectric.buildActivity(FakeSettingsPanelActivity.class);
|
Robolectric.buildActivity(FakeSettingsPanelActivity.class);
|
||||||
|
Reference in New Issue
Block a user