diff options
| author | 2022-12-09 10:20:53 +0000 | |
|---|---|---|
| committer | 2023-01-10 16:18:12 +0000 | |
| commit | 4af16d82cf5a20376f386db37a62f36cde16d010 (patch) | |
| tree | d431285198f60063a5faeb09d222ac2a6890d493 | |
| parent | 3980bbe199e359d85b0421bc698432887d974c36 (diff) | |
Add an explicit media indicator icon to SysUI media controls
Checks for the value given by the key for explicit in MediaContants. And
applies all implementation necessary to show the explicit indicator as
an icon next to the artist name.
Bug: 221291466
Test: Manual - Checked apps that add explicit indicator to media.
Test: Checked with the media tester app.
Test: atest MediaDataManagerTest
Test: atest MediaControlPanelTest
Change-Id: I0721f098760e717b9ae32a8791fe68d5bac6e65f
15 files changed, 252 insertions, 14 deletions
diff --git a/packages/SystemUI/res/drawable/ic_media_explicit_indicator.xml b/packages/SystemUI/res/drawable/ic_media_explicit_indicator.xml new file mode 100644 index 000000000000..08c5aaf56bf7 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_media_explicit_indicator.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="13dp" + android:height="13dp" + android:viewportWidth="48" + android:viewportHeight="48" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M18.3,34H29.65V31H21.3V25.7H29.65V22.7H21.3V17.35H29.65V14.35H18.3ZM9,42Q7.8,42 6.9,41.1Q6,40.2 6,39V9Q6,7.8 6.9,6.9Q7.8,6 9,6H39Q40.2,6 41.1,6.9Q42,7.8 42,9V39Q42,40.2 41.1,41.1Q40.2,42 39,42ZM9,39H39Q39,39 39,39Q39,39 39,39V9Q39,9 39,9Q39,9 39,9H9Q9,9 9,9Q9,9 9,9V39Q9,39 9,39Q9,39 9,39ZM9,9Q9,9 9,9Q9,9 9,9V39Q9,39 9,39Q9,39 9,39Q9,39 9,39Q9,39 9,39V9Q9,9 9,9Q9,9 9,9Z"/> +</vector> diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml index 95aefab328df..abc83379950a 100644 --- a/packages/SystemUI/res/layout/media_session_view.xml +++ b/packages/SystemUI/res/layout/media_session_view.xml @@ -147,6 +147,14 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" /> + <!-- Explicit Indicator --> + <com.android.internal.widget.CachingIconView + android:id="@+id/media_explicit_indicator" + android:layout_width="@dimen/qs_media_explicit_indicator_icon_size" + android:layout_height="@dimen/qs_media_explicit_indicator_icon_size" + android:src="@drawable/ic_media_explicit_indicator" + /> + <!-- Artist name --> <TextView android:id="@+id/header_artist" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index ae7ab9e199e4..ffe9254ca813 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1050,6 +1050,7 @@ <dimen name="qs_media_disabled_seekbar_height">1dp</dimen> <dimen name="qs_media_enabled_seekbar_height">2dp</dimen> <dimen name="qs_media_app_icon_size">24dp</dimen> + <dimen name="qs_media_explicit_indicator_icon_size">13dp</dimen> <dimen name="qs_media_session_enabled_seekbar_vertical_padding">15dp</dimen> <dimen name="qs_media_session_disabled_seekbar_vertical_padding">16dp</dimen> diff --git a/packages/SystemUI/res/xml/media_session_collapsed.xml b/packages/SystemUI/res/xml/media_session_collapsed.xml index 1eb621e0368b..d9c81af54a12 100644 --- a/packages/SystemUI/res/xml/media_session_collapsed.xml +++ b/packages/SystemUI/res/xml/media_session_collapsed.xml @@ -66,6 +66,21 @@ app:layout_constraintTop_toBottomOf="@id/icon" app:layout_constraintStart_toStartOf="parent" app:layout_constraintHorizontal_bias="0" /> + + <Constraint + android:id="@+id/media_explicit_indicator" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/qs_media_info_spacing" + android:layout_marginBottom="@dimen/qs_media_padding" + android:layout_marginTop="0dp" + app:layout_constraintStart_toStartOf="@id/header_title" + app:layout_constraintEnd_toStartOf="@id/header_artist" + app:layout_constraintTop_toTopOf="@id/header_artist" + app:layout_constraintBottom_toTopOf="@id/media_action_barrier_top" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintHorizontal_chainStyle="packed" /> + <Constraint android:id="@+id/header_artist" android:layout_width="wrap_content" @@ -75,9 +90,8 @@ app:layout_constraintEnd_toStartOf="@id/action_button_guideline" app:layout_constrainedWidth="true" app:layout_constraintTop_toBottomOf="@id/header_title" - app:layout_constraintStart_toStartOf="@id/header_title" - app:layout_constraintVertical_bias="0" - app:layout_constraintHorizontal_bias="0" /> + app:layout_constraintStart_toEndOf="@id/media_explicit_indicator" + app:layout_constraintVertical_bias="0" /> <Constraint android:id="@+id/actionPlayPause" diff --git a/packages/SystemUI/res/xml/media_session_expanded.xml b/packages/SystemUI/res/xml/media_session_expanded.xml index 7de0a5e0e8c4..0cdc0f9505bc 100644 --- a/packages/SystemUI/res/xml/media_session_expanded.xml +++ b/packages/SystemUI/res/xml/media_session_expanded.xml @@ -58,6 +58,21 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintBottom_toTopOf="@id/header_artist" app:layout_constraintHorizontal_bias="0" /> + + <Constraint + android:id="@+id/media_explicit_indicator" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/qs_media_info_spacing" + android:layout_marginBottom="@dimen/qs_media_padding" + android:layout_marginTop="0dp" + app:layout_constraintStart_toStartOf="@id/header_title" + app:layout_constraintEnd_toStartOf="@id/header_artist" + app:layout_constraintTop_toTopOf="@id/header_artist" + app:layout_constraintBottom_toTopOf="@id/media_action_barrier_top" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintHorizontal_chainStyle="packed"/> + <Constraint android:id="@+id/header_artist" android:layout_width="wrap_content" @@ -67,10 +82,9 @@ android:layout_marginTop="0dp" app:layout_constrainedWidth="true" app:layout_constraintEnd_toStartOf="@id/actionPlayPause" - app:layout_constraintStart_toStartOf="@id/header_title" + app:layout_constraintStart_toEndOf="@id/media_explicit_indicator" app:layout_constraintBottom_toTopOf="@id/media_action_barrier_top" - app:layout_constraintVertical_bias="0" - app:layout_constraintHorizontal_bias="0" /> + app:layout_constraintVertical_bias="0" /> <Constraint android:id="@+id/actionPlayPause" diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index e4e8d59df066..686be863d035 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -332,6 +332,9 @@ object Flags { val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE = unreleasedFlag(910, "media_ttt_receiver_success_ripple", teamfood = true) + // TODO(b/263512203): Tracking Bug + val MEDIA_EXPLICIT_INDICATOR = unreleasedFlag(911, "media_explicit_indicator", teamfood = true) + // 1000 - dock val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging") diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt index f006442906e7..be18cbec7163 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt @@ -88,7 +88,10 @@ data class MediaData( val instanceId: InstanceId, /** The UID of the app, used for logging */ - val appUid: Int + val appUid: Int, + + /** Whether explicit indicator exists */ + val isExplicit: Boolean = false, ) { companion object { /** Media is playing on the local device */ diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt index a8f39fa9a456..1c8bfd1fc468 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt @@ -24,6 +24,7 @@ import android.widget.ImageView import android.widget.SeekBar import android.widget.TextView import androidx.constraintlayout.widget.Barrier +import com.android.internal.widget.CachingIconView import com.android.systemui.R import com.android.systemui.media.controls.models.GutsViewHolder import com.android.systemui.surfaceeffects.ripple.MultiRippleView @@ -44,6 +45,7 @@ class MediaViewHolder constructor(itemView: View) { val appIcon = itemView.requireViewById<ImageView>(R.id.icon) val titleText = itemView.requireViewById<TextView>(R.id.header_title) val artistText = itemView.requireViewById<TextView>(R.id.header_artist) + val explicitIndicator = itemView.requireViewById<CachingIconView>(R.id.media_explicit_indicator) // Output switcher val seamless = itemView.requireViewById<ViewGroup>(R.id.media_seamless) @@ -123,6 +125,7 @@ class MediaViewHolder constructor(itemView: View) { R.id.app_name, R.id.header_title, R.id.header_artist, + R.id.media_explicit_indicator, R.id.media_seamless, R.id.media_progress_bar, R.id.actionPlayPause, diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt index 1cc8a1353a34..9f28d4607ab5 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt @@ -46,6 +46,7 @@ import android.os.Process import android.os.UserHandle import android.provider.Settings import android.service.notification.StatusBarNotification +import android.support.v4.media.MediaMetadataCompat import android.text.TextUtils import android.util.Log import androidx.media.utils.MediaConstants @@ -661,6 +662,10 @@ class MediaDataManager( val currentEntry = mediaEntries.get(packageName) val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId() val appUid = currentEntry?.appUid ?: Process.INVALID_UID + val isExplicit = + desc.extras?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) == + MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT && + mediaFlags.isExplicitIndicatorEnabled() val mediaAction = getResumeMediaAction(resumeAction) val lastActive = systemClock.elapsedRealtime() @@ -690,7 +695,8 @@ class MediaDataManager( hasCheckedForResume = true, lastActive = lastActive, instanceId = instanceId, - appUid = appUid + appUid = appUid, + isExplicit = isExplicit, ) ) } @@ -751,6 +757,15 @@ class MediaDataManager( song = HybridGroupManager.resolveTitle(notif) } + // Explicit Indicator + var isExplicit = false + if (mediaFlags.isExplicitIndicatorEnabled()) { + val mediaMetadataCompat = MediaMetadataCompat.fromMediaMetadata(metadata) + isExplicit = + mediaMetadataCompat?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) == + MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT + } + // Artist name var artist: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_ARTIST) if (artist == null) { @@ -852,7 +867,8 @@ class MediaDataManager( isClearable = sbn.isClearable(), lastActive = lastActive, instanceId = instanceId, - appUid = appUid + appUid = appUid, + isExplicit = isExplicit, ) ) } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java index ee0147f55536..9d1ebb664c10 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java @@ -51,7 +51,6 @@ import android.os.Process; import android.os.Trace; import android.text.TextUtils; import android.util.Log; -import android.util.Pair; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; @@ -69,6 +68,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.ColorUtils; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.InstanceId; +import com.android.internal.widget.CachingIconView; import com.android.settingslib.widget.AdaptiveIcon; import com.android.systemui.ActivityIntentHelper; import com.android.systemui.R; @@ -123,6 +123,7 @@ import java.util.concurrent.Executor; import javax.inject.Inject; +import kotlin.Triple; import kotlin.Unit; /** @@ -400,10 +401,11 @@ public class MediaControlPanel { TextView titleText = mMediaViewHolder.getTitleText(); TextView artistText = mMediaViewHolder.getArtistText(); + CachingIconView explicitIndicator = mMediaViewHolder.getExplicitIndicator(); AnimatorSet enter = loadAnimator(R.anim.media_metadata_enter, - Interpolators.EMPHASIZED_DECELERATE, titleText, artistText); + Interpolators.EMPHASIZED_DECELERATE, titleText, artistText, explicitIndicator); AnimatorSet exit = loadAnimator(R.anim.media_metadata_exit, - Interpolators.EMPHASIZED_ACCELERATE, titleText, artistText); + Interpolators.EMPHASIZED_ACCELERATE, titleText, artistText, explicitIndicator); MultiRippleView multiRippleView = vh.getMultiRippleView(); mMultiRippleController = new MultiRippleController(multiRippleView); @@ -668,11 +670,15 @@ public class MediaControlPanel { private boolean bindSongMetadata(MediaData data) { TextView titleText = mMediaViewHolder.getTitleText(); TextView artistText = mMediaViewHolder.getArtistText(); + ConstraintSet expandedSet = mMediaViewController.getExpandedLayout(); + ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout(); return mMetadataAnimationHandler.setNext( - Pair.create(data.getSong(), data.getArtist()), + new Triple(data.getSong(), data.getArtist(), data.isExplicit()), () -> { titleText.setText(data.getSong()); artistText.setText(data.getArtist()); + setVisibleAndAlpha(expandedSet, R.id.media_explicit_indicator, data.isExplicit()); + setVisibleAndAlpha(collapsedSet, R.id.media_explicit_indicator, data.isExplicit()); // refreshState is required here to resize the text views (and prevent ellipsis) mMediaViewController.refreshState(); diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt index 322421318cb8..d2c8e15868dc 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt @@ -80,6 +80,7 @@ constructor( setOf( R.id.header_title, R.id.header_artist, + R.id.media_explicit_indicator, R.id.actionPlayPause, ) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt index 8d4931a5d08c..5bc35caed515 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt @@ -42,4 +42,7 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) { * [android.app.StatusBarManager.registerNearbyMediaDevicesProvider] for more information. */ fun areNearbyMediaDevicesEnabled() = featureFlags.isEnabled(Flags.MEDIA_NEARBY_DEVICES) + + /** Check whether we show explicit indicator on UMO */ + fun isExplicitIndicatorEnabled() = featureFlags.isEnabled(Flags.MEDIA_EXPLICIT_INDICATOR) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java index 4d2d0f05b76a..c0639f34484c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java @@ -79,7 +79,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { USER_ID, true, APP, null, ARTIST, TITLE, null, new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, true, null, MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L, - InstanceId.fakeInstanceId(-1), -1); + InstanceId.fakeInstanceId(-1), -1, false); mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME, null, false); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt index 52b694fac07c..c24c8c7f7cf6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt @@ -228,6 +228,7 @@ class MediaDataManagerTest : SysuiTestCase() { whenever(mediaSmartspaceTarget.iconGrid).thenReturn(validRecommendationList) whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(1234L) whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(false) + whenever(mediaFlags.isExplicitIndicatorEnabled()).thenReturn(true) whenever(logger.getNewInstanceId()).thenReturn(instanceIdSequence.newInstanceId()) } @@ -300,6 +301,60 @@ class MediaDataManagerTest : SysuiTestCase() { } @Test + fun testLoadMetadata_withExplicitIndicator() { + val metadata = + MediaMetadata.Builder().run { + putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST) + putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE) + putLong( + MediaConstants.METADATA_KEY_IS_EXPLICIT, + MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT + ) + build() + } + whenever(mediaControllerFactory.create(anyObject())).thenReturn(controller) + whenever(controller.metadata).thenReturn(metadata) + + mediaDataManager.addListener(listener) + mediaDataManager.onNotificationAdded(KEY, mediaNotification) + + assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) + assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(null), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) + assertThat(mediaDataCaptor.value!!.isExplicit).isTrue() + } + + @Test + fun testOnMetaDataLoaded_withoutExplicitIndicator() { + whenever(mediaControllerFactory.create(anyObject())).thenReturn(controller) + whenever(controller.metadata).thenReturn(metadataBuilder.build()) + + mediaDataManager.addListener(listener) + mediaDataManager.onNotificationAdded(KEY, mediaNotification) + + assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) + assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(null), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) + assertThat(mediaDataCaptor.value!!.isExplicit).isFalse() + } + + @Test fun testOnMetaDataLoaded_callsListener() { addNotificationAndLoad() verify(logger) @@ -603,6 +658,53 @@ class MediaDataManagerTest : SysuiTestCase() { } @Test + fun testAddResumptionControls_withExplicitIndicator() { + val bundle = Bundle() + // WHEN resumption controls are added with explicit indicator + bundle.putLong( + MediaConstants.METADATA_KEY_IS_EXPLICIT, + MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT + ) + val desc = + MediaDescription.Builder().run { + setTitle(SESSION_TITLE) + setExtras(bundle) + build() + } + val currentTime = clock.elapsedRealtime() + mediaDataManager.addResumptionControls( + USER_ID, + desc, + Runnable {}, + session.sessionToken, + APP_NAME, + pendingIntent, + PACKAGE_NAME + ) + assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) + assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) + // THEN the media data indicates that it is for resumption + verify(listener) + .onMediaDataLoaded( + eq(PACKAGE_NAME), + eq(null), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) + val data = mediaDataCaptor.value + assertThat(data.resumption).isTrue() + assertThat(data.song).isEqualTo(SESSION_TITLE) + assertThat(data.app).isEqualTo(APP_NAME) + assertThat(data.actions).hasSize(1) + assertThat(data.semanticActions!!.playOrPause).isNotNull() + assertThat(data.lastActive).isAtLeast(currentTime) + assertThat(data.isExplicit).isTrue() + verify(logger).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) + } + + @Test fun testResumptionDisabled_dismissesResumeControls() { // WHEN there are resume controls and resumption is switched off val desc = diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt index b65f5cb51aaf..cfb19fc32bec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt @@ -54,6 +54,7 @@ import androidx.constraintlayout.widget.ConstraintSet import androidx.lifecycle.LiveData import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId +import com.android.internal.widget.CachingIconView import com.android.systemui.ActivityIntentHelper import com.android.systemui.R import com.android.systemui.SysuiTestCase @@ -154,6 +155,7 @@ public class MediaControlPanelTest : SysuiTestCase() { @Mock private lateinit var albumView: ImageView private lateinit var titleText: TextView private lateinit var artistText: TextView + private lateinit var explicitIndicator: CachingIconView private lateinit var seamless: ViewGroup private lateinit var seamlessButton: View @Mock private lateinit var seamlessBackground: RippleDrawable @@ -216,6 +218,7 @@ public class MediaControlPanelTest : SysuiTestCase() { this.set(Flags.UMO_SURFACE_RIPPLE, false) this.set(Flags.UMO_TURBULENCE_NOISE, false) this.set(Flags.MEDIA_FALSING_PENALTY, true) + this.set(Flags.MEDIA_EXPLICIT_INDICATOR, true) } @JvmField @Rule val mockito = MockitoJUnit.rule() @@ -350,6 +353,7 @@ public class MediaControlPanelTest : SysuiTestCase() { appIcon = ImageView(context) titleText = TextView(context) artistText = TextView(context) + explicitIndicator = CachingIconView(context).also { it.id = R.id.media_explicit_indicator } seamless = FrameLayout(context) seamless.foreground = seamlessBackground seamlessButton = View(context) @@ -396,6 +400,7 @@ public class MediaControlPanelTest : SysuiTestCase() { whenever(albumView.foreground).thenReturn(mock(Drawable::class.java)) whenever(viewHolder.titleText).thenReturn(titleText) whenever(viewHolder.artistText).thenReturn(artistText) + whenever(viewHolder.explicitIndicator).thenReturn(explicitIndicator) whenever(seamlessBackground.getDrawable(0)).thenReturn(mock(GradientDrawable::class.java)) whenever(viewHolder.seamless).thenReturn(seamless) whenever(viewHolder.seamlessButton).thenReturn(seamlessButton) @@ -1019,6 +1024,7 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun bindText() { + useRealConstraintSets() player.attachPlayer(viewHolder) player.bindPlayer(mediaData, PACKAGE) @@ -1036,6 +1042,8 @@ public class MediaControlPanelTest : SysuiTestCase() { handler.onAnimationEnd(mockAnimator) assertThat(titleText.getText()).isEqualTo(TITLE) assertThat(artistText.getText()).isEqualTo(ARTIST) + assertThat(expandedSet.getVisibility(explicitIndicator.id)).isEqualTo(ConstraintSet.GONE) + assertThat(collapsedSet.getVisibility(explicitIndicator.id)).isEqualTo(ConstraintSet.GONE) // Rebinding should not trigger animation player.bindPlayer(mediaData, PACKAGE) @@ -1043,6 +1051,36 @@ public class MediaControlPanelTest : SysuiTestCase() { } @Test + fun bindTextWithExplicitIndicator() { + useRealConstraintSets() + val mediaDataWitExp = mediaData.copy(isExplicit = true) + player.attachPlayer(viewHolder) + player.bindPlayer(mediaDataWitExp, PACKAGE) + + // Capture animation handler + val captor = argumentCaptor<Animator.AnimatorListener>() + verify(mockAnimator, times(2)).addListener(captor.capture()) + val handler = captor.value + + // Validate text views unchanged but animation started + assertThat(titleText.getText()).isEqualTo("") + assertThat(artistText.getText()).isEqualTo("") + verify(mockAnimator, times(1)).start() + + // Binding only after animator runs + handler.onAnimationEnd(mockAnimator) + assertThat(titleText.getText()).isEqualTo(TITLE) + assertThat(artistText.getText()).isEqualTo(ARTIST) + assertThat(expandedSet.getVisibility(explicitIndicator.id)).isEqualTo(ConstraintSet.VISIBLE) + assertThat(collapsedSet.getVisibility(explicitIndicator.id)) + .isEqualTo(ConstraintSet.VISIBLE) + + // Rebinding should not trigger animation + player.bindPlayer(mediaData, PACKAGE) + verify(mockAnimator, times(3)).start() + } + + @Test fun bindTextInterrupted() { val data0 = mediaData.copy(artist = "ARTIST_0") val data1 = mediaData.copy(artist = "ARTIST_1") |