[Media] Update recommendation a11y content description based on whether
guts is open or not. Fixes: 229574744 Test: manual Test: MediaControlPanelTest Change-Id: Ibdca22552fbb2333253a76be7a6f46a6a411f72d
This commit is contained in:
@@ -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) {
|
||||
@@ -965,6 +988,7 @@ public class MediaControlPanel {
|
||||
return;
|
||||
}
|
||||
|
||||
mRecommendationData = data;
|
||||
mSmartspaceId = SmallHash.hash(data.getTargetId());
|
||||
mPackageName = data.getPackageName();
|
||||
mInstanceId = data.getInstanceId();
|
||||
@@ -980,6 +1004,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);
|
||||
@@ -987,28 +1017,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();
|
||||
@@ -1192,6 +1205,8 @@ public class MediaControlPanel {
|
||||
mMediaViewController.closeGuts(immediate);
|
||||
if (mMediaViewHolder != null) {
|
||||
bindPlayerContentDescription(mMediaData);
|
||||
} else if (mRecommendationViewHolder != null) {
|
||||
bindRecommendationContentDescription(mRecommendationData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1208,6 +1223,8 @@ public class MediaControlPanel {
|
||||
mMediaViewController.openGuts();
|
||||
if (mMediaViewHolder != null) {
|
||||
bindPlayerContentDescription(mMediaData);
|
||||
} else if (mRecommendationViewHolder != null) {
|
||||
bindRecommendationContentDescription(mRecommendationData);
|
||||
}
|
||||
mLogger.logLongPressOpen(mUid, mPackageName, mInstanceId);
|
||||
}
|
||||
@@ -1323,17 +1340,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
|
||||
|
||||
@@ -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!!
|
||||
|
||||
@@ -59,6 +59,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)
|
||||
@@ -1274,6 +1277,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
|
||||
|
||||
Reference in New Issue
Block a user