diff options
3 files changed, 150 insertions, 31 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index 626e185dec15..8c2cb6ded678 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -97,7 +97,7 @@ import kotlin.Unit; * A view controller used for Media Playback. */ public class MediaControlPanel { - private static final String TAG = "MediaControlPanel"; + protected static final String TAG = "MediaControlPanel"; private static final float DISABLED_ALPHA = 0.38f; private static final String EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME = "com.google" @@ -106,7 +106,7 @@ public class MediaControlPanel { "com.google.android.apps.gsa.smartspace.extra.SMARTSPACE_INTENT"; private static final String KEY_SMARTSPACE_ARTIST_NAME = "artist_name"; private static final String KEY_SMARTSPACE_OPEN_IN_FOREGROUND = "KEY_OPEN_IN_FOREGROUND"; - private static final String KEY_SMARTSPACE_APP_NAME = "KEY_SMARTSPACE_APP_NAME"; + protected static final String KEY_SMARTSPACE_APP_NAME = "KEY_SMARTSPACE_APP_NAME"; // Event types logged by smartspace private static final int SMARTSPACE_CARD_CLICK_EVENT = 760; @@ -149,6 +149,7 @@ public class MediaControlPanel { private RecommendationViewHolder mRecommendationViewHolder; private String mKey; private MediaData mMediaData; + private SmartspaceMediaData mRecommendationData; private MediaViewController mMediaViewController; private MediaSession.Token mToken; private MediaController mController; @@ -563,6 +564,8 @@ public class MediaControlPanel { }); } + // We may want to look into unifying this with bindRecommendationContentDescription if/when we + // do a refactor of this class. private void bindPlayerContentDescription(MediaData data) { if (mMediaViewHolder == null) { return; @@ -583,6 +586,26 @@ public class MediaControlPanel { mMediaViewHolder.getPlayer().setContentDescription(contentDescription); } + private void bindRecommendationContentDescription(SmartspaceMediaData data) { + if (mRecommendationViewHolder == null) { + return; + } + + CharSequence contentDescription; + if (mMediaViewController.isGutsVisible()) { + contentDescription = + mRecommendationViewHolder.getGutsViewHolder().getGutsText().getText(); + } else if (data != null) { + contentDescription = mContext.getString( + R.string.controls_media_smartspace_rec_description, + data.getAppName(mContext)); + } else { + contentDescription = null; + } + + mRecommendationViewHolder.getRecommendations().setContentDescription(contentDescription); + } + private void bindArtworkAndColors(MediaData data, boolean updateBackground) { final int reqId = mArtworkNextBindRequestId++; if (updateBackground) { @@ -969,6 +992,7 @@ public class MediaControlPanel { return; } + mRecommendationData = data; mSmartspaceId = SmallHash.hash(data.getTargetId()); mPackageName = data.getPackageName(); mInstanceId = data.getInstanceId(); @@ -984,6 +1008,12 @@ public class MediaControlPanel { return; } + CharSequence appName = data.getAppName(mContext); + if (appName == null) { + Log.w(TAG, "Fail to get media recommendation's app name"); + return; + } + PackageManager packageManager = mContext.getPackageManager(); // Set up media source app's logo. Drawable icon = packageManager.getApplicationIcon(applicationInfo); @@ -991,28 +1021,11 @@ public class MediaControlPanel { headerLogoImageView.setImageDrawable(icon); fetchAndUpdateRecommendationColors(icon); - // Set up media source app's label text. - CharSequence appName = getAppName(data.getCardAction()); - if (TextUtils.isEmpty(appName)) { - Intent launchIntent = - packageManager.getLaunchIntentForPackage(data.getPackageName()); - if (launchIntent != null) { - ActivityInfo launchActivity = launchIntent.resolveActivityInfo(packageManager, 0); - appName = launchActivity.loadLabel(packageManager); - } else { - Log.w(TAG, "Package " + data.getPackageName() - + " does not have a main launcher activity. Fallback to full app name"); - appName = packageManager.getApplicationLabel(applicationInfo); - } - } - // Set up media rec card's tap action if applicable. TransitionLayout recommendationCard = mRecommendationViewHolder.getRecommendations(); setSmartspaceRecItemOnClickListener(recommendationCard, data.getCardAction(), /* interactedSubcardRank */ -1); - // Set up media rec card's accessibility label. - recommendationCard.setContentDescription( - mContext.getString(R.string.controls_media_smartspace_rec_description, appName)); + bindRecommendationContentDescription(data); List<ImageView> mediaCoverItems = mRecommendationViewHolder.getMediaCoverItems(); List<ViewGroup> mediaCoverContainers = mRecommendationViewHolder.getMediaCoverContainers(); @@ -1196,6 +1209,8 @@ public class MediaControlPanel { mMediaViewController.closeGuts(immediate); if (mMediaViewHolder != null) { bindPlayerContentDescription(mMediaData); + } else if (mRecommendationViewHolder != null) { + bindRecommendationContentDescription(mRecommendationData); } } @@ -1212,6 +1227,8 @@ public class MediaControlPanel { mMediaViewController.openGuts(); if (mMediaViewHolder != null) { bindPlayerContentDescription(mMediaData); + } else if (mRecommendationViewHolder != null) { + bindRecommendationContentDescription(mRecommendationData); } mLogger.logLongPressOpen(mUid, mPackageName, mInstanceId); } @@ -1327,17 +1344,6 @@ public class MediaControlPanel { }); } - /** Returns the upstream app name if available. */ - @Nullable - private String getAppName(SmartspaceAction action) { - if (action == null || action.getIntent() == null - || action.getIntent().getExtras() == null) { - return null; - } - - return action.getIntent().getExtras().getString(KEY_SMARTSPACE_APP_NAME); - } - /** Returns if the Smartspace action will open the activity in foreground. */ private boolean shouldSmartspaceRecItemOpenInForeground(SmartspaceAction action) { if (action == null || action.getIntent() == null diff --git a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt index 50a96f601443..c8f17d93bcc8 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt @@ -17,8 +17,13 @@ package com.android.systemui.media import android.app.smartspace.SmartspaceAction +import android.content.Context import android.content.Intent +import android.content.pm.PackageManager +import android.text.TextUtils +import android.util.Log import com.android.internal.logging.InstanceId +import com.android.systemui.media.MediaControlPanel.KEY_SMARTSPACE_APP_NAME /** State of a Smartspace media recommendations view. */ data class SmartspaceMediaData( @@ -67,6 +72,32 @@ data class SmartspaceMediaData( * Returns the list of [recommendations] that have valid data. */ fun getValidRecommendations() = recommendations.filter { it.icon != null } + + /** Returns the upstream app name if available. */ + fun getAppName(context: Context): CharSequence? { + val nameFromAction = cardAction?.intent?.extras?.getString(KEY_SMARTSPACE_APP_NAME) + if (!TextUtils.isEmpty(nameFromAction)) { + return nameFromAction + } + + val packageManager = context.packageManager + packageManager.getLaunchIntentForPackage(packageName)?.let { + val launchActivity = it.resolveActivityInfo(packageManager, 0) + return launchActivity.loadLabel(packageManager) + } + + Log.w( + TAG, + "Package $packageName does not have a main launcher activity. " + + "Fallback to full app name") + return try { + val applicationInfo = packageManager.getApplicationInfo(packageName, /* flags= */ 0) + packageManager.getApplicationLabel(applicationInfo) + } catch (e: PackageManager.NameNotFoundException) { + null + } + } } const val NUM_REQUIRED_RECOMMENDATIONS = 3 +private val TAG = SmartspaceMediaData::class.simpleName!! diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt index 1310d69d0d0f..18aae0edd846 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -58,6 +58,7 @@ import com.android.systemui.ActivityIntentHelper import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastSender +import com.android.systemui.media.MediaControlPanel.KEY_SMARTSPACE_APP_NAME import com.android.systemui.media.dialog.MediaOutputDialogFactory import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager @@ -102,6 +103,7 @@ private const val SESSION_KEY = "SESSION_KEY" private const val SESSION_ARTIST = "SESSION_ARTIST" private const val SESSION_TITLE = "SESSION_TITLE" private const val DISABLED_DEVICE_NAME = "DISABLED_DEVICE_NAME" +private const val REC_APP_NAME = "REC APP NAME" @SmallTest @RunWith(AndroidTestingRunner::class) @@ -262,6 +264,7 @@ public class MediaControlPanelTest : SysuiTestCase() { // Set valid recommendation data val extras = Bundle() + extras.putString(KEY_SMARTSPACE_APP_NAME, REC_APP_NAME) val intent = Intent().apply { putExtras(extras) setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) @@ -1275,6 +1278,85 @@ public class MediaControlPanelTest : SysuiTestCase() { verify(mediaDataManager).dismissSmartspaceRecommendation(eq(mediaKey), anyLong()) } + @Test + fun recommendation_gutsOpen_contentDescriptionIsForGuts() { + whenever(mediaViewController.isGutsVisible).thenReturn(true) + player.attachRecommendation(recommendationViewHolder) + + val gutsTextString = "gutsText" + whenever(gutsText.text).thenReturn(gutsTextString) + player.bindRecommendation(smartspaceData) + + val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java) + verify(viewHolder.player).contentDescription = descriptionCaptor.capture() + val description = descriptionCaptor.value.toString() + + assertThat(description).isEqualTo(gutsTextString) + } + + @Test + fun recommendation_gutsClosed_contentDescriptionIsForPlayer() { + whenever(mediaViewController.isGutsVisible).thenReturn(false) + player.attachRecommendation(recommendationViewHolder) + + player.bindRecommendation(smartspaceData) + + val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java) + verify(viewHolder.player).contentDescription = descriptionCaptor.capture() + val description = descriptionCaptor.value.toString() + + assertThat(description).contains(REC_APP_NAME) + } + + @Test + fun recommendation_gutsChangesFromOpenToClosed_contentDescriptionUpdated() { + // Start out open + whenever(mediaViewController.isGutsVisible).thenReturn(true) + whenever(gutsText.text).thenReturn("gutsText") + player.attachRecommendation(recommendationViewHolder) + player.bindRecommendation(smartspaceData) + + // Update to closed by long pressing + val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java) + verify(viewHolder.player).onLongClickListener = captor.capture() + reset(viewHolder.player) + + whenever(mediaViewController.isGutsVisible).thenReturn(false) + captor.value.onLongClick(viewHolder.player) + + // Then content description is now the player content description + val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java) + verify(viewHolder.player).contentDescription = descriptionCaptor.capture() + val description = descriptionCaptor.value.toString() + + assertThat(description).contains(REC_APP_NAME) + } + + @Test + fun recommendation_gutsChangesFromClosedToOpen_contentDescriptionUpdated() { + // Start out closed + whenever(mediaViewController.isGutsVisible).thenReturn(false) + val gutsTextString = "gutsText" + whenever(gutsText.text).thenReturn(gutsTextString) + player.attachRecommendation(recommendationViewHolder) + player.bindRecommendation(smartspaceData) + + // Update to open by long pressing + val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java) + verify(viewHolder.player).onLongClickListener = captor.capture() + reset(viewHolder.player) + + whenever(mediaViewController.isGutsVisible).thenReturn(true) + captor.value.onLongClick(viewHolder.player) + + // Then content description is now the guts content description + val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java) + verify(viewHolder.player).contentDescription = descriptionCaptor.capture() + val description = descriptionCaptor.value.toString() + + assertThat(description).isEqualTo(gutsTextString) + } + /* ***** END guts tests for the recommendations ***** */ @Test |