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_width="wrap_content"
|
||||||
android:layout_height="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 -->
|
<!-- Artist name -->
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/header_artist"
|
android:id="@+id/header_artist"
|
||||||
|
|||||||
@@ -1055,6 +1055,7 @@
|
|||||||
<dimen name="qs_media_disabled_seekbar_height">1dp</dimen>
|
<dimen name="qs_media_disabled_seekbar_height">1dp</dimen>
|
||||||
<dimen name="qs_media_enabled_seekbar_height">2dp</dimen>
|
<dimen name="qs_media_enabled_seekbar_height">2dp</dimen>
|
||||||
<dimen name="qs_media_app_icon_size">24dp</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_enabled_seekbar_vertical_padding">15dp</dimen>
|
||||||
<dimen name="qs_media_session_disabled_seekbar_vertical_padding">16dp</dimen>
|
<dimen name="qs_media_session_disabled_seekbar_vertical_padding">16dp</dimen>
|
||||||
|
|||||||
@@ -66,6 +66,21 @@
|
|||||||
app:layout_constraintTop_toBottomOf="@id/icon"
|
app:layout_constraintTop_toBottomOf="@id/icon"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintHorizontal_bias="0" />
|
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
|
<Constraint
|
||||||
android:id="@+id/header_artist"
|
android:id="@+id/header_artist"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@@ -75,9 +90,8 @@
|
|||||||
app:layout_constraintEnd_toStartOf="@id/action_button_guideline"
|
app:layout_constraintEnd_toStartOf="@id/action_button_guideline"
|
||||||
app:layout_constrainedWidth="true"
|
app:layout_constrainedWidth="true"
|
||||||
app:layout_constraintTop_toBottomOf="@id/header_title"
|
app:layout_constraintTop_toBottomOf="@id/header_title"
|
||||||
app:layout_constraintStart_toStartOf="@id/header_title"
|
app:layout_constraintStart_toEndOf="@id/media_explicit_indicator"
|
||||||
app:layout_constraintVertical_bias="0"
|
app:layout_constraintVertical_bias="0" />
|
||||||
app:layout_constraintHorizontal_bias="0" />
|
|
||||||
|
|
||||||
<Constraint
|
<Constraint
|
||||||
android:id="@+id/actionPlayPause"
|
android:id="@+id/actionPlayPause"
|
||||||
|
|||||||
@@ -58,6 +58,21 @@
|
|||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintBottom_toTopOf="@id/header_artist"
|
app:layout_constraintBottom_toTopOf="@id/header_artist"
|
||||||
app:layout_constraintHorizontal_bias="0" />
|
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
|
<Constraint
|
||||||
android:id="@+id/header_artist"
|
android:id="@+id/header_artist"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@@ -67,10 +82,9 @@
|
|||||||
android:layout_marginTop="0dp"
|
android:layout_marginTop="0dp"
|
||||||
app:layout_constrainedWidth="true"
|
app:layout_constrainedWidth="true"
|
||||||
app:layout_constraintEnd_toStartOf="@id/actionPlayPause"
|
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_constraintBottom_toTopOf="@id/media_action_barrier_top"
|
||||||
app:layout_constraintVertical_bias="0"
|
app:layout_constraintVertical_bias="0" />
|
||||||
app:layout_constraintHorizontal_bias="0" />
|
|
||||||
|
|
||||||
<Constraint
|
<Constraint
|
||||||
android:id="@+id/actionPlayPause"
|
android:id="@+id/actionPlayPause"
|
||||||
|
|||||||
@@ -336,6 +336,9 @@ object Flags {
|
|||||||
val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE =
|
val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE =
|
||||||
unreleasedFlag(910, "media_ttt_receiver_success_ripple", teamfood = true)
|
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
|
// 1000 - dock
|
||||||
val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
|
val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,10 @@ data class MediaData(
|
|||||||
val instanceId: InstanceId,
|
val instanceId: InstanceId,
|
||||||
|
|
||||||
/** The UID of the app, used for logging */
|
/** The UID of the app, used for logging */
|
||||||
val appUid: Int
|
val appUid: Int,
|
||||||
|
|
||||||
|
/** Whether explicit indicator exists */
|
||||||
|
val isExplicit: Boolean = false,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
/** Media is playing on the local device */
|
/** Media is playing on the local device */
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import android.widget.ImageView
|
|||||||
import android.widget.SeekBar
|
import android.widget.SeekBar
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.constraintlayout.widget.Barrier
|
import androidx.constraintlayout.widget.Barrier
|
||||||
|
import com.android.internal.widget.CachingIconView
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
import com.android.systemui.media.controls.models.GutsViewHolder
|
import com.android.systemui.media.controls.models.GutsViewHolder
|
||||||
import com.android.systemui.surfaceeffects.ripple.MultiRippleView
|
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 appIcon = itemView.requireViewById<ImageView>(R.id.icon)
|
||||||
val titleText = itemView.requireViewById<TextView>(R.id.header_title)
|
val titleText = itemView.requireViewById<TextView>(R.id.header_title)
|
||||||
val artistText = itemView.requireViewById<TextView>(R.id.header_artist)
|
val artistText = itemView.requireViewById<TextView>(R.id.header_artist)
|
||||||
|
val explicitIndicator = itemView.requireViewById<CachingIconView>(R.id.media_explicit_indicator)
|
||||||
|
|
||||||
// Output switcher
|
// Output switcher
|
||||||
val seamless = itemView.requireViewById<ViewGroup>(R.id.media_seamless)
|
val seamless = itemView.requireViewById<ViewGroup>(R.id.media_seamless)
|
||||||
@@ -123,6 +125,7 @@ class MediaViewHolder constructor(itemView: View) {
|
|||||||
R.id.app_name,
|
R.id.app_name,
|
||||||
R.id.header_title,
|
R.id.header_title,
|
||||||
R.id.header_artist,
|
R.id.header_artist,
|
||||||
|
R.id.media_explicit_indicator,
|
||||||
R.id.media_seamless,
|
R.id.media_seamless,
|
||||||
R.id.media_progress_bar,
|
R.id.media_progress_bar,
|
||||||
R.id.actionPlayPause,
|
R.id.actionPlayPause,
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import android.os.Process
|
|||||||
import android.os.UserHandle
|
import android.os.UserHandle
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.service.notification.StatusBarNotification
|
import android.service.notification.StatusBarNotification
|
||||||
|
import android.support.v4.media.MediaMetadataCompat
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.media.utils.MediaConstants
|
import androidx.media.utils.MediaConstants
|
||||||
@@ -660,6 +661,10 @@ class MediaDataManager(
|
|||||||
val currentEntry = mediaEntries.get(packageName)
|
val currentEntry = mediaEntries.get(packageName)
|
||||||
val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
|
val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
|
||||||
val appUid = currentEntry?.appUid ?: Process.INVALID_UID
|
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 mediaAction = getResumeMediaAction(resumeAction)
|
||||||
val lastActive = systemClock.elapsedRealtime()
|
val lastActive = systemClock.elapsedRealtime()
|
||||||
@@ -689,7 +694,8 @@ class MediaDataManager(
|
|||||||
hasCheckedForResume = true,
|
hasCheckedForResume = true,
|
||||||
lastActive = lastActive,
|
lastActive = lastActive,
|
||||||
instanceId = instanceId,
|
instanceId = instanceId,
|
||||||
appUid = appUid
|
appUid = appUid,
|
||||||
|
isExplicit = isExplicit,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -750,6 +756,15 @@ class MediaDataManager(
|
|||||||
song = HybridGroupManager.resolveTitle(notif)
|
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
|
// Artist name
|
||||||
var artist: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_ARTIST)
|
var artist: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_ARTIST)
|
||||||
if (artist == null) {
|
if (artist == null) {
|
||||||
@@ -851,7 +866,8 @@ class MediaDataManager(
|
|||||||
isClearable = sbn.isClearable(),
|
isClearable = sbn.isClearable(),
|
||||||
lastActive = lastActive,
|
lastActive = lastActive,
|
||||||
instanceId = instanceId,
|
instanceId = instanceId,
|
||||||
appUid = appUid
|
appUid = appUid,
|
||||||
|
isExplicit = isExplicit,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ import android.os.Process;
|
|||||||
import android.os.Trace;
|
import android.os.Trace;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -68,6 +67,7 @@ import com.android.internal.annotations.VisibleForTesting;
|
|||||||
import com.android.internal.graphics.ColorUtils;
|
import com.android.internal.graphics.ColorUtils;
|
||||||
import com.android.internal.jank.InteractionJankMonitor;
|
import com.android.internal.jank.InteractionJankMonitor;
|
||||||
import com.android.internal.logging.InstanceId;
|
import com.android.internal.logging.InstanceId;
|
||||||
|
import com.android.internal.widget.CachingIconView;
|
||||||
import com.android.settingslib.widget.AdaptiveIcon;
|
import com.android.settingslib.widget.AdaptiveIcon;
|
||||||
import com.android.systemui.ActivityIntentHelper;
|
import com.android.systemui.ActivityIntentHelper;
|
||||||
import com.android.systemui.R;
|
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.animation.TransitionLayout;
|
||||||
import com.android.systemui.util.time.SystemClock;
|
import com.android.systemui.util.time.SystemClock;
|
||||||
|
|
||||||
|
import dagger.Lazy;
|
||||||
|
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -120,7 +122,7 @@ import java.util.concurrent.Executor;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import dagger.Lazy;
|
import kotlin.Triple;
|
||||||
import kotlin.Unit;
|
import kotlin.Unit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -398,10 +400,11 @@ public class MediaControlPanel {
|
|||||||
|
|
||||||
TextView titleText = mMediaViewHolder.getTitleText();
|
TextView titleText = mMediaViewHolder.getTitleText();
|
||||||
TextView artistText = mMediaViewHolder.getArtistText();
|
TextView artistText = mMediaViewHolder.getArtistText();
|
||||||
|
CachingIconView explicitIndicator = mMediaViewHolder.getExplicitIndicator();
|
||||||
AnimatorSet enter = loadAnimator(R.anim.media_metadata_enter,
|
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,
|
AnimatorSet exit = loadAnimator(R.anim.media_metadata_exit,
|
||||||
Interpolators.EMPHASIZED_ACCELERATE, titleText, artistText);
|
Interpolators.EMPHASIZED_ACCELERATE, titleText, artistText, explicitIndicator);
|
||||||
|
|
||||||
MultiRippleView multiRippleView = vh.getMultiRippleView();
|
MultiRippleView multiRippleView = vh.getMultiRippleView();
|
||||||
mMultiRippleController = new MultiRippleController(multiRippleView);
|
mMultiRippleController = new MultiRippleController(multiRippleView);
|
||||||
@@ -664,11 +667,15 @@ public class MediaControlPanel {
|
|||||||
private boolean bindSongMetadata(MediaData data) {
|
private boolean bindSongMetadata(MediaData data) {
|
||||||
TextView titleText = mMediaViewHolder.getTitleText();
|
TextView titleText = mMediaViewHolder.getTitleText();
|
||||||
TextView artistText = mMediaViewHolder.getArtistText();
|
TextView artistText = mMediaViewHolder.getArtistText();
|
||||||
|
ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
|
||||||
|
ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
|
||||||
return mMetadataAnimationHandler.setNext(
|
return mMetadataAnimationHandler.setNext(
|
||||||
Pair.create(data.getSong(), data.getArtist()),
|
new Triple(data.getSong(), data.getArtist(), data.isExplicit()),
|
||||||
() -> {
|
() -> {
|
||||||
titleText.setText(data.getSong());
|
titleText.setText(data.getSong());
|
||||||
artistText.setText(data.getArtist());
|
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)
|
// refreshState is required here to resize the text views (and prevent ellipsis)
|
||||||
mMediaViewController.refreshState();
|
mMediaViewController.refreshState();
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ constructor(
|
|||||||
setOf(
|
setOf(
|
||||||
R.id.header_title,
|
R.id.header_title,
|
||||||
R.id.header_artist,
|
R.id.header_artist,
|
||||||
|
R.id.media_explicit_indicator,
|
||||||
R.id.actionPlayPause,
|
R.id.actionPlayPause,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -42,4 +42,7 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) {
|
|||||||
* [android.app.StatusBarManager.registerNearbyMediaDevicesProvider] for more information.
|
* [android.app.StatusBarManager.registerNearbyMediaDevicesProvider] for more information.
|
||||||
*/
|
*/
|
||||||
fun areNearbyMediaDevicesEnabled() = featureFlags.isEnabled(Flags.MEDIA_NEARBY_DEVICES)
|
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,
|
USER_ID, true, APP, null, ARTIST, TITLE, null,
|
||||||
new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, true, null,
|
new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, true, null,
|
||||||
MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L,
|
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);
|
mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -228,6 +228,7 @@ class MediaDataManagerTest : SysuiTestCase() {
|
|||||||
whenever(mediaSmartspaceTarget.iconGrid).thenReturn(validRecommendationList)
|
whenever(mediaSmartspaceTarget.iconGrid).thenReturn(validRecommendationList)
|
||||||
whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(1234L)
|
whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(1234L)
|
||||||
whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(false)
|
whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(false)
|
||||||
|
whenever(mediaFlags.isExplicitIndicatorEnabled()).thenReturn(true)
|
||||||
whenever(logger.getNewInstanceId()).thenReturn(instanceIdSequence.newInstanceId())
|
whenever(logger.getNewInstanceId()).thenReturn(instanceIdSequence.newInstanceId())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,6 +300,60 @@ class MediaDataManagerTest : SysuiTestCase() {
|
|||||||
assertThat(backgroundExecutor.numPending()).isEqualTo(1)
|
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
|
@Test
|
||||||
fun testOnMetaDataLoaded_callsListener() {
|
fun testOnMetaDataLoaded_callsListener() {
|
||||||
addNotificationAndLoad()
|
addNotificationAndLoad()
|
||||||
@@ -602,6 +657,53 @@ class MediaDataManagerTest : SysuiTestCase() {
|
|||||||
verify(logger).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
|
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
|
@Test
|
||||||
fun testResumptionDisabled_dismissesResumeControls() {
|
fun testResumptionDisabled_dismissesResumeControls() {
|
||||||
// WHEN there are resume controls and resumption is switched off
|
// 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.lifecycle.LiveData
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import com.android.internal.logging.InstanceId
|
import com.android.internal.logging.InstanceId
|
||||||
|
import com.android.internal.widget.CachingIconView
|
||||||
import com.android.systemui.ActivityIntentHelper
|
import com.android.systemui.ActivityIntentHelper
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
@@ -154,6 +155,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
@Mock private lateinit var albumView: ImageView
|
@Mock private lateinit var albumView: ImageView
|
||||||
private lateinit var titleText: TextView
|
private lateinit var titleText: TextView
|
||||||
private lateinit var artistText: TextView
|
private lateinit var artistText: TextView
|
||||||
|
private lateinit var explicitIndicator: CachingIconView
|
||||||
private lateinit var seamless: ViewGroup
|
private lateinit var seamless: ViewGroup
|
||||||
private lateinit var seamlessButton: View
|
private lateinit var seamlessButton: View
|
||||||
@Mock private lateinit var seamlessBackground: RippleDrawable
|
@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_SURFACE_RIPPLE, false)
|
||||||
this.set(Flags.UMO_TURBULENCE_NOISE, false)
|
this.set(Flags.UMO_TURBULENCE_NOISE, false)
|
||||||
this.set(Flags.MEDIA_FALSING_PENALTY, true)
|
this.set(Flags.MEDIA_FALSING_PENALTY, true)
|
||||||
|
this.set(Flags.MEDIA_EXPLICIT_INDICATOR, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmField @Rule val mockito = MockitoJUnit.rule()
|
@JvmField @Rule val mockito = MockitoJUnit.rule()
|
||||||
@@ -350,6 +353,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
appIcon = ImageView(context)
|
appIcon = ImageView(context)
|
||||||
titleText = TextView(context)
|
titleText = TextView(context)
|
||||||
artistText = TextView(context)
|
artistText = TextView(context)
|
||||||
|
explicitIndicator = CachingIconView(context).also { it.id = R.id.media_explicit_indicator }
|
||||||
seamless = FrameLayout(context)
|
seamless = FrameLayout(context)
|
||||||
seamless.foreground = seamlessBackground
|
seamless.foreground = seamlessBackground
|
||||||
seamlessButton = View(context)
|
seamlessButton = View(context)
|
||||||
@@ -396,6 +400,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
whenever(albumView.foreground).thenReturn(mock(Drawable::class.java))
|
whenever(albumView.foreground).thenReturn(mock(Drawable::class.java))
|
||||||
whenever(viewHolder.titleText).thenReturn(titleText)
|
whenever(viewHolder.titleText).thenReturn(titleText)
|
||||||
whenever(viewHolder.artistText).thenReturn(artistText)
|
whenever(viewHolder.artistText).thenReturn(artistText)
|
||||||
|
whenever(viewHolder.explicitIndicator).thenReturn(explicitIndicator)
|
||||||
whenever(seamlessBackground.getDrawable(0)).thenReturn(mock(GradientDrawable::class.java))
|
whenever(seamlessBackground.getDrawable(0)).thenReturn(mock(GradientDrawable::class.java))
|
||||||
whenever(viewHolder.seamless).thenReturn(seamless)
|
whenever(viewHolder.seamless).thenReturn(seamless)
|
||||||
whenever(viewHolder.seamlessButton).thenReturn(seamlessButton)
|
whenever(viewHolder.seamlessButton).thenReturn(seamlessButton)
|
||||||
@@ -1019,6 +1024,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun bindText() {
|
fun bindText() {
|
||||||
|
useRealConstraintSets()
|
||||||
player.attachPlayer(viewHolder)
|
player.attachPlayer(viewHolder)
|
||||||
player.bindPlayer(mediaData, PACKAGE)
|
player.bindPlayer(mediaData, PACKAGE)
|
||||||
|
|
||||||
@@ -1036,12 +1042,44 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
handler.onAnimationEnd(mockAnimator)
|
handler.onAnimationEnd(mockAnimator)
|
||||||
assertThat(titleText.getText()).isEqualTo(TITLE)
|
assertThat(titleText.getText()).isEqualTo(TITLE)
|
||||||
assertThat(artistText.getText()).isEqualTo(ARTIST)
|
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
|
// Rebinding should not trigger animation
|
||||||
player.bindPlayer(mediaData, PACKAGE)
|
player.bindPlayer(mediaData, PACKAGE)
|
||||||
verify(mockAnimator, times(2)).start()
|
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
|
@Test
|
||||||
fun bindTextInterrupted() {
|
fun bindTextInterrupted() {
|
||||||
val data0 = mediaData.copy(artist = "ARTIST_0")
|
val data0 = mediaData.copy(artist = "ARTIST_0")
|
||||||
|
|||||||
Reference in New Issue
Block a user