Merge "Add an explicit media indicator icon to SysUI media controls" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
4fd565a8ae
@@ -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"
|
||||
|
||||
@@ -1055,6 +1055,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"
|
||||
|
||||
@@ -336,6 +336,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();
|
||||
|
||||
@@ -77,6 +77,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