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
This commit is contained in:
@@ -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>
|
||||
@@ -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"
|
||||
|
||||
@@ -1046,6 +1046,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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -327,6 +327,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")
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -45,6 +45,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
|
||||
@@ -660,6 +661,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()
|
||||
@@ -689,7 +694,8 @@ class MediaDataManager(
|
||||
hasCheckedForResume = true,
|
||||
lastActive = lastActive,
|
||||
instanceId = instanceId,
|
||||
appUid = appUid
|
||||
appUid = appUid,
|
||||
isExplicit = isExplicit,
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -750,6 +756,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) {
|
||||
@@ -851,7 +866,8 @@ class MediaDataManager(
|
||||
isClearable = sbn.isClearable(),
|
||||
lastActive = lastActive,
|
||||
instanceId = instanceId,
|
||||
appUid = appUid
|
||||
appUid = appUid,
|
||||
isExplicit = isExplicit,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -50,7 +50,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;
|
||||
@@ -68,6 +67,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;
|
||||
@@ -113,6 +113,8 @@ import com.android.systemui.util.ColorUtilKt;
|
||||
import com.android.systemui.util.animation.TransitionLayout;
|
||||
import com.android.systemui.util.time.SystemClock;
|
||||
|
||||
import dagger.Lazy;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -120,7 +122,7 @@ import java.util.concurrent.Executor;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.Lazy;
|
||||
import kotlin.Triple;
|
||||
import kotlin.Unit;
|
||||
|
||||
/**
|
||||
@@ -398,10 +400,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);
|
||||
@@ -664,11 +667,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();
|
||||
|
||||
@@ -80,6 +80,7 @@ constructor(
|
||||
setOf(
|
||||
R.id.header_title,
|
||||
R.id.header_artist,
|
||||
R.id.media_explicit_indicator,
|
||||
R.id.actionPlayPause,
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -299,6 +300,60 @@ class MediaDataManagerTest : SysuiTestCase() {
|
||||
assertThat(backgroundExecutor.numPending()).isEqualTo(1)
|
||||
}
|
||||
|
||||
@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()
|
||||
@@ -602,6 +657,53 @@ class MediaDataManagerTest : SysuiTestCase() {
|
||||
verify(logger).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
@@ -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,12 +1042,44 @@ 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)
|
||||
verify(mockAnimator, times(2)).start()
|
||||
}
|
||||
|
||||
@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")
|
||||
|
||||
Reference in New Issue
Block a user