[Media Rec] Use the same long press menu for media player and media

recommendations.

This defines a common GutsViewHolder that holds all the relevant views
for the long press menu. Both MediaViewHolder and
RecommendationViewHolder have a GutsViewHolder to perform common guts
behavior.

Fixes: 228961120
Bug: 223603970
Test: manual (see screenshots in bug)
Test: MediaControlPanelTest
Change-Id: I957e015697e3a4f0c55a2cba336cad4849a3dfff
This commit is contained in:
Caitlin Cassidy
2022-04-14 20:47:49 +00:00
parent ab3d4e4143
commit 320a27d5bf
10 changed files with 382 additions and 307 deletions

View File

@@ -0,0 +1,105 @@
<?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.
-->
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" >
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:layout_marginStart="@dimen/qs_media_padding"
android:layout_marginEnd="@dimen/qs_media_padding"
android:id="@+id/remove_text"
android:fontFamily="@*android:string/config_headlineFontFamily"
android:singleLine="true"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:text="@string/controls_media_close_session"
android:gravity="center_horizontal|top"
app:layout_constraintTop_toBottomOf="@id/settings"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/cancel" />
<ImageButton
android:id="@+id/settings"
android:src="@drawable/ic_settings"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginEnd="4dp"
android:background="@drawable/qs_media_light_source"
android:contentDescription="@string/controls_media_settings_button"
android:layout_gravity="top"
app:layout_constraintWidth_min="@dimen/min_clickable_item_size"
app:layout_constraintHeight_min="@dimen/min_clickable_item_size"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
</ImageButton>
<FrameLayout
android:id="@+id/dismiss"
android:background="@drawable/qs_media_light_source"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/qs_media_padding"
android:layout_marginEnd="@dimen/qs_media_action_spacing"
android:layout_marginBottom="@dimen/qs_media_padding"
app:layout_constrainedWidth="true"
app:layout_constraintWidth_min="@dimen/min_clickable_item_size"
app:layout_constraintHeight_min="@dimen/min_clickable_item_size"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/cancel"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/remove_text">
<TextView
android:id="@+id/dismiss_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|top"
style="@style/MediaPlayer.SolidButton"
android:background="@drawable/qs_media_solid_button"
android:text="@string/controls_media_dismiss_button" />
</FrameLayout>
<FrameLayout
android:id="@+id/cancel"
android:background="@drawable/qs_media_light_source"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/qs_media_action_spacing"
android:layout_marginEnd="@dimen/qs_media_padding"
android:layout_marginBottom="@dimen/qs_media_padding"
app:layout_constrainedWidth="true"
app:layout_constraintWidth_min="@dimen/min_clickable_item_size"
app:layout_constraintHeight_min="@dimen/min_clickable_item_size"
app:layout_constraintStart_toEndOf="@id/dismiss"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/remove_text">
<TextView
android:id="@+id/cancel_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|top"
style="@style/MediaPlayer.OutlineButton"
android:text="@string/cancel" />
</FrameLayout>
</merge>

View File

@@ -300,87 +300,7 @@
android:layout_marginBottom="@dimen/qs_media_padding"
android:layout_marginTop="0dp" />
<!-- Long press menu -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:layout_marginStart="@dimen/qs_media_padding"
android:layout_marginEnd="@dimen/qs_media_padding"
android:id="@+id/remove_text"
android:fontFamily="@*android:string/config_headlineFontFamily"
android:singleLine="true"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:text="@string/controls_media_close_session"
android:gravity="center_horizontal|top"
app:layout_constraintTop_toBottomOf="@id/settings"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/cancel" />
<include
layout="@layout/media_long_press_menu" />
<ImageButton
android:id="@+id/settings"
android:src="@drawable/ic_settings"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginEnd="4dp"
android:background="@drawable/qs_media_light_source"
android:contentDescription="@string/controls_media_settings_button"
android:layout_gravity="top"
app:layout_constraintWidth_min="@dimen/min_clickable_item_size"
app:layout_constraintHeight_min="@dimen/min_clickable_item_size"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
</ImageButton>
<FrameLayout
android:id="@+id/dismiss"
android:background="@drawable/qs_media_light_source"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/qs_media_padding"
android:layout_marginEnd="@dimen/qs_media_action_spacing"
android:layout_marginBottom="@dimen/qs_media_padding"
app:layout_constrainedWidth="true"
app:layout_constraintWidth_min="@dimen/min_clickable_item_size"
app:layout_constraintHeight_min="@dimen/min_clickable_item_size"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/cancel"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/remove_text">
<TextView
android:id="@+id/dismiss_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|top"
style="@style/MediaPlayer.SolidButton"
android:background="@drawable/qs_media_solid_button"
android:text="@string/controls_media_dismiss_button" />
</FrameLayout>
<FrameLayout
android:id="@+id/cancel"
android:background="@drawable/qs_media_light_source"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/qs_media_action_spacing"
android:layout_marginEnd="@dimen/qs_media_padding"
android:layout_marginBottom="@dimen/qs_media_padding"
app:layout_constrainedWidth="true"
app:layout_constraintWidth_min="@dimen/min_clickable_item_size"
app:layout_constraintHeight_min="@dimen/min_clickable_item_size"
app:layout_constraintStart_toEndOf="@id/dismiss"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/remove_text">
<TextView
android:id="@+id/cancel_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|top"
style="@style/MediaPlayer.OutlineButton"
android:text="@string/cancel" />
</FrameLayout>
</com.android.systemui.util.animation.TransitionLayout>

View File

@@ -167,97 +167,7 @@
android:layout_marginBottom="@dimen/qs_media_padding"
/>
<!-- Long press menu -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/qs_media_padding"
android:layout_marginStart="@dimen/qs_media_padding"
android:layout_marginEnd="@dimen/qs_media_padding"
android:id="@+id/remove_text"
android:fontFamily="@*android:string/config_headlineFontFamily"
android:singleLine="true"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:text="@string/controls_media_close_session"
android:gravity="center_horizontal|top"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/cancel"/>
<include
layout="@layout/media_long_press_menu" />
<FrameLayout
android:id="@+id/settings"
android:background="@drawable/qs_media_light_source"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/qs_media_padding"
android:layout_marginEnd="@dimen/qs_media_action_spacing"
android:layout_marginBottom="@dimen/qs_media_padding"
app:layout_constrainedWidth="true"
app:layout_constraintWidth_min="48dp"
app:layout_constraintHeight_min="48dp"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/cancel"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/remove_text">
<TextView
android:id="@+id/settings_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|bottom"
style="@style/MediaPlayer.OutlineButton"
android:text="@string/controls_media_settings_button" />
</FrameLayout>
<FrameLayout
android:id="@+id/cancel"
android:background="@drawable/qs_media_light_source"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/qs_media_action_spacing"
android:layout_marginEnd="@dimen/qs_media_action_spacing"
android:layout_marginBottom="@dimen/qs_media_padding"
app:layout_constrainedWidth="true"
app:layout_constraintWidth_min="48dp"
app:layout_constraintHeight_min="48dp"
app:layout_constraintStart_toEndOf="@id/settings"
app:layout_constraintEnd_toStartOf="@id/dismiss"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/remove_text">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|bottom"
style="@style/MediaPlayer.OutlineButton"
android:text="@string/cancel" />
</FrameLayout>
<FrameLayout
android:id="@+id/dismiss"
android:background="@drawable/qs_media_light_source"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/qs_media_action_spacing"
android:layout_marginEnd="@dimen/qs_media_padding"
android:layout_marginBottom="@dimen/qs_media_padding"
app:layout_constrainedWidth="true"
app:layout_constraintWidth_min="48dp"
app:layout_constraintHeight_min="48dp"
app:layout_constraintStart_toEndOf="@id/cancel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/remove_text">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|bottom"
style="@style/MediaPlayer.OutlineButton"
android:text="@string/controls_media_dismiss_button"
/>
</FrameLayout>
</com.android.systemui.util.animation.TransitionLayout>

View File

@@ -103,7 +103,7 @@ class ColorSchemeTransition internal constructor(
mediaViewHolder.albumView.backgroundTintList = colorList
mediaViewHolder.seamlessIcon.imageTintList = colorList
mediaViewHolder.seamlessText.setTextColor(surfaceColor)
mediaViewHolder.dismissText.setTextColor(surfaceColor)
mediaViewHolder.gutsViewHolder.setSurfaceColor(surfaceColor)
}
val accentPrimary = colorTransitionFactory(
@@ -113,9 +113,7 @@ class ColorSchemeTransition internal constructor(
val accentColorList = ColorStateList.valueOf(accentPrimary)
mediaViewHolder.actionPlayPause.backgroundTintList = accentColorList
mediaViewHolder.seamlessButton.backgroundTintList = accentColorList
mediaViewHolder.settings.imageTintList = accentColorList
mediaViewHolder.cancelText.backgroundTintList = accentColorList
mediaViewHolder.dismissText.backgroundTintList = accentColorList
mediaViewHolder.gutsViewHolder.setAccentPrimaryColor(accentPrimary)
}
val textPrimary = colorTransitionFactory(
@@ -126,13 +124,12 @@ class ColorSchemeTransition internal constructor(
val textColorList = ColorStateList.valueOf(textPrimary)
mediaViewHolder.seekBar.thumb.setTintList(textColorList)
mediaViewHolder.seekBar.progressTintList = textColorList
mediaViewHolder.longPressText.setTextColor(textColorList)
mediaViewHolder.cancelText.setTextColor(textColorList)
mediaViewHolder.scrubbingElapsedTimeView.setTextColor(textColorList)
mediaViewHolder.scrubbingTotalTimeView.setTextColor(textColorList)
for (button in mediaViewHolder.getTransparentActionButtons()) {
button.imageTintList = textColorList
}
mediaViewHolder.gutsViewHolder.setTextPrimaryColor(textPrimary)
}
val textPrimaryInverse = colorTransitionFactory(

View File

@@ -0,0 +1,88 @@
/*
* 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.
*/
package com.android.systemui.media
import android.content.res.ColorStateList
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.TextView
import com.android.systemui.R
import com.android.systemui.monet.ColorScheme
/**
* A view holder for the guts menu of a media player. The guts are shown when the user long-presses
* on the media player.
*
* Both [MediaViewHolder] and [RecommendationViewHolder] use the same guts menu layout, so this
* class helps share logic between the two.
*/
class GutsViewHolder constructor(itemView: View) {
val gutsText: TextView = itemView.requireViewById(R.id.remove_text)
val cancel: View = itemView.requireViewById(R.id.cancel)
val cancelText: TextView = itemView.requireViewById(R.id.cancel_text)
val dismiss: ViewGroup = itemView.requireViewById(R.id.dismiss)
val dismissText: TextView = itemView.requireViewById(R.id.dismiss_text)
val settings: ImageButton = itemView.requireViewById(R.id.settings)
/** Marquees the main text of the guts menu. */
fun marquee(start: Boolean, delay: Long, tag: String) {
val gutsTextHandler = gutsText.handler
if (gutsTextHandler == null) {
Log.d(tag, "marquee while longPressText.getHandler() is null", Exception())
return
}
gutsTextHandler.postDelayed( { gutsText.isSelected = start }, delay)
}
/** Sets the right colors on all the guts views based on the given [ColorScheme]. */
fun setColors(colorScheme: ColorScheme) {
setSurfaceColor(surfaceFromScheme(colorScheme))
setTextPrimaryColor(textPrimaryFromScheme(colorScheme))
setAccentPrimaryColor(accentPrimaryFromScheme(colorScheme))
}
/** Sets the surface color on all guts views that use it. */
fun setSurfaceColor(surfaceColor: Int) {
dismissText.setTextColor(surfaceColor)
}
/** Sets the primary accent color on all guts views that use it. */
fun setAccentPrimaryColor(accentPrimary: Int) {
val accentColorList = ColorStateList.valueOf(accentPrimary)
settings.imageTintList = accentColorList
cancelText.backgroundTintList = accentColorList
dismissText.backgroundTintList = accentColorList
}
/** Sets the primary text color on all guts views that use it. */
fun setTextPrimaryColor(textPrimary: Int) {
val textColorList = ColorStateList.valueOf(textPrimary)
gutsText.setTextColor(textColorList)
cancelText.setTextColor(textColorList)
}
companion object {
val ids = setOf(
R.id.remove_text,
R.id.cancel,
R.id.dismiss,
R.id.settings
)
}
}

View File

@@ -336,17 +336,6 @@ public class MediaControlPanel {
return true;
}
});
vh.getCancel().setOnClickListener(v -> {
if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
closeGuts();
}
});
vh.getSettings().setOnClickListener(v -> {
if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
mLogger.logLongPressSettings(mUid, mPackageName, mInstanceId);
mActivityStarter.startActivity(SETTINGS_INTENT, true /* dismissShade */);
}
});
TextView titleText = mMediaViewHolder.getTitleText();
TextView artistText = mMediaViewHolder.getArtistText();
@@ -391,17 +380,6 @@ public class MediaControlPanel {
return true;
}
});
mRecommendationViewHolder.getCancel().setOnClickListener(v -> {
if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
closeGuts();
}
});
mRecommendationViewHolder.getSettings().setOnClickListener(v -> {
if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
mLogger.logLongPressSettings(mUid, mPackageName, mInstanceId);
mActivityStarter.startActivity(SETTINGS_INTENT, true /* dismissShade */);
}
});
}
/** Bind this player view based on the data given. */
@@ -461,7 +439,7 @@ public class MediaControlPanel {
mBackgroundExecutor.execute(() -> mSeekBarViewModel.updateController(controller));
bindOutputSwitcherChip(data);
bindLongPressMenu(data);
bindGutsMenuForPlayer(data);
bindScrubbingTime(data);
bindActionButtons(data);
@@ -531,24 +509,8 @@ public class MediaControlPanel {
});
}
private void bindLongPressMenu(MediaData data) {
boolean isDismissible = data.isClearable();
String dismissText;
if (isDismissible) {
dismissText = mContext.getString(R.string.controls_media_close_session, data.getApp());
} else {
dismissText = mContext.getString(R.string.controls_media_active_session);
}
mMediaViewHolder.getLongPressText().setText(dismissText);
// Dismiss button
mMediaViewHolder.getDismissText().setAlpha(isDismissible ? 1 : DISABLED_ALPHA);
mMediaViewHolder.getDismiss().setEnabled(isDismissible);
mMediaViewHolder.getDismiss().setOnClickListener(v -> {
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
logSmartspaceCardReported(SMARTSPACE_CARD_DISMISS_EVENT);
mLogger.logLongPressDismiss(mUid, mPackageName, mInstanceId);
private void bindGutsMenuForPlayer(MediaData data) {
Runnable onDismissClickedRunnable = () -> {
if (mKey != null) {
closeGuts();
if (!mMediaDataManagerLazy.get().dismissMediaData(mKey,
@@ -561,7 +523,13 @@ public class MediaControlPanel {
Log.w(TAG, "Dismiss media with null notification. Token uid="
+ data.getToken().getUid());
}
});
};
bindGutsMenuCommon(
/* isDismissible= */ data.isClearable(),
data.getApp(),
mMediaViewHolder.getGutsViewHolder(),
onDismissClickedRunnable);
}
private boolean bindSongMetadata(MediaData data) {
@@ -1087,14 +1055,9 @@ public class MediaControlPanel {
}
mSmartspaceMediaItemsCount = uiComponentIndex;
// Set up long press to show guts setting panel.
mRecommendationViewHolder.getDismiss().setOnClickListener(v -> {
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
mLogger.logLongPressDismiss(mUid, mPackageName, mInstanceId);
logSmartspaceCardReported(
761 // SMARTSPACE_CARD_DISMISS
);
// Guts
Runnable onDismissClickedRunnable = () -> {
closeGuts();
mMediaDataManagerLazy.get().dismissSmartspaceRecommendation(
data.getTargetId(), MediaViewController.GUTS_ANIMATION_DURATION + 100L);
@@ -1114,7 +1077,12 @@ public class MediaControlPanel {
} else {
mBroadcastSender.sendBroadcast(dismissIntent);
}
});
};
bindGutsMenuCommon(
/* isDismissible= */ true,
appName.toString(),
mRecommendationViewHolder.getGutsViewHolder(),
onDismissClickedRunnable);
mController = null;
if (mMetadataAnimationHandler == null || !mMetadataAnimationHandler.isRunning()) {
@@ -1145,6 +1113,49 @@ public class MediaControlPanel {
(title) -> title.setTextColor(textPrimaryColor));
mRecommendationViewHolder.getMediaSubtitles().forEach(
(subtitle) -> subtitle.setTextColor(textSecondaryColor));
mRecommendationViewHolder.getGutsViewHolder().setColors(colorScheme);
}
private void bindGutsMenuCommon(
boolean isDismissible,
String appName,
GutsViewHolder gutsViewHolder,
Runnable onDismissClickedRunnable) {
// Text
String text;
if (isDismissible) {
text = mContext.getString(R.string.controls_media_close_session, appName);
} else {
text = mContext.getString(R.string.controls_media_active_session);
}
gutsViewHolder.getGutsText().setText(text);
// Dismiss button
gutsViewHolder.getDismissText().setAlpha(isDismissible ? 1 : DISABLED_ALPHA);
gutsViewHolder.getDismiss().setEnabled(isDismissible);
gutsViewHolder.getDismiss().setOnClickListener(v -> {
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
logSmartspaceCardReported(SMARTSPACE_CARD_DISMISS_EVENT);
mLogger.logLongPressDismiss(mUid, mPackageName, mInstanceId);
onDismissClickedRunnable.run();
});
// Cancel button
gutsViewHolder.getCancel().setOnClickListener(v -> {
if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
closeGuts();
}
});
// Settings button
gutsViewHolder.getSettings().setOnClickListener(v -> {
if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
mLogger.logLongPressSettings(mUid, mPackageName, mInstanceId);
mActivityStarter.startActivity(SETTINGS_INTENT, /* dismissShade= */true);
}
});
}
/**

View File

@@ -261,10 +261,7 @@ class MediaViewController @Inject constructor(
TYPE.PLAYER -> MediaViewHolder.controlsIds
TYPE.RECOMMENDATION -> RecommendationViewHolder.controlsIds
}
val gutsIds = when (type) {
TYPE.PLAYER -> MediaViewHolder.gutsIds
TYPE.RECOMMENDATION -> RecommendationViewHolder.gutsIds
}
val gutsIds = GutsViewHolder.ids
controlsIds.forEach { id ->
viewState.widgetStates.get(id)?.let { state ->
// Make sure to use the unmodified state if guts are not visible.

View File

@@ -16,7 +16,6 @@
package com.android.systemui.media
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -56,13 +55,7 @@ class MediaViewHolder constructor(itemView: View) {
val scrubbingTotalTimeView: TextView =
itemView.requireViewById(R.id.media_scrubbing_total_time)
// Settings screen
val longPressText = itemView.requireViewById<TextView>(R.id.remove_text)
val cancel = itemView.requireViewById<View>(R.id.cancel)
val cancelText = itemView.requireViewById<TextView>(R.id.cancel_text)
val dismiss = itemView.requireViewById<ViewGroup>(R.id.dismiss)
val dismissText = itemView.requireViewById<TextView>(R.id.dismiss_text)
val settings = itemView.requireViewById<ImageButton>(R.id.settings)
val gutsViewHolder = GutsViewHolder(itemView)
// Action Buttons
val actionPlayPause = itemView.requireViewById<ImageButton>(R.id.actionPlayPause)
@@ -79,9 +72,9 @@ class MediaViewHolder constructor(itemView: View) {
init {
(player.background as IlluminationDrawable).let {
it.registerLightSource(seamless)
it.registerLightSource(cancel)
it.registerLightSource(dismiss)
it.registerLightSource(settings)
it.registerLightSource(gutsViewHolder.cancel)
it.registerLightSource(gutsViewHolder.dismiss)
it.registerLightSource(gutsViewHolder.settings)
it.registerLightSource(actionPlayPause)
it.registerLightSource(actionNext)
it.registerLightSource(actionPrev)
@@ -122,12 +115,7 @@ class MediaViewHolder constructor(itemView: View) {
}
fun marquee(start: Boolean, delay: Long) {
val longPressTextHandler = longPressText.getHandler()
if (longPressTextHandler == null) {
Log.d(TAG, "marquee while longPressText.getHandler() is null", Exception())
return
}
longPressTextHandler.postDelayed({ longPressText.setSelected(start) }, delay)
gutsViewHolder.marquee(start, delay, TAG)
}
companion object {
@@ -172,12 +160,7 @@ class MediaViewHolder constructor(itemView: View) {
R.id.media_scrubbing_elapsed_time,
R.id.media_scrubbing_total_time
)
val gutsIds = setOf(
R.id.remove_text,
R.id.cancel,
R.id.dismiss,
R.id.settings
)
// Buttons used for notification-based actions
val genericButtonIds = setOf(

View File

@@ -24,6 +24,8 @@ import android.widget.TextView
import com.android.systemui.R
import com.android.systemui.util.animation.TransitionLayout
private const val TAG = "RecommendationViewHolder"
/** ViewHolder for a Smartspace media recommendation. */
class RecommendationViewHolder private constructor(itemView: View) {
@@ -52,26 +54,19 @@ class RecommendationViewHolder private constructor(itemView: View) {
itemView.requireViewById(R.id.media_subtitle3)
)
// Settings/Guts screen
val longPressText = itemView.requireViewById<TextView>(R.id.remove_text)
val cancel = itemView.requireViewById<View>(R.id.cancel)
val dismiss = itemView.requireViewById<ViewGroup>(R.id.dismiss)
val dismissLabel = dismiss.getChildAt(0)
val settings = itemView.requireViewById<View>(R.id.settings)
val settingsText = itemView.requireViewById<TextView>(R.id.settings_text)
val gutsViewHolder = GutsViewHolder(itemView)
init {
(recommendations.background as IlluminationDrawable).let { background ->
mediaCoverContainers.forEach { background.registerLightSource(it) }
background.registerLightSource(cancel)
background.registerLightSource(dismiss)
background.registerLightSource(dismissLabel)
background.registerLightSource(settings)
background.registerLightSource(gutsViewHolder.cancel)
background.registerLightSource(gutsViewHolder.dismiss)
background.registerLightSource(gutsViewHolder.settings)
}
}
fun marquee(start: Boolean, delay: Long) {
longPressText.getHandler().postDelayed({ longPressText.setSelected(start) }, delay)
gutsViewHolder.marquee(start, delay, TAG)
}
companion object {
@@ -104,14 +99,12 @@ class RecommendationViewHolder private constructor(itemView: View) {
R.id.media_cover1_container,
R.id.media_cover2_container,
R.id.media_cover3_container,
)
// Res Ids for the components on the guts panel.
val gutsIds = setOf(
R.id.remove_text,
R.id.cancel,
R.id.dismiss,
R.id.settings
R.id.media_title1,
R.id.media_title2,
R.id.media_title3,
R.id.media_subtitle1,
R.id.media_subtitle2,
R.id.media_subtitle3
)
}
}
}

View File

@@ -25,7 +25,6 @@ import org.mockito.Mockito.`when` as whenever
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.graphics.Color
import android.graphics.drawable.Animatable2
import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.GradientDrawable
@@ -35,7 +34,6 @@ import android.media.MediaMetadata
import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.os.Bundle
import android.os.Handler
import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -111,6 +109,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var broadcastSender: BroadcastSender
@Mock private lateinit var gutsViewHolder: GutsViewHolder
@Mock private lateinit var viewHolder: MediaViewHolder
@Mock private lateinit var view: TransitionLayout
@Mock private lateinit var seekBarViewModel: SeekBarViewModel
@@ -145,8 +144,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
private lateinit var scrubbingElapsedTimeView: TextView
private lateinit var scrubbingTotalTimeView: TextView
private lateinit var actionsTopBarrier: Barrier
@Mock private lateinit var longPressText: TextView
@Mock private lateinit var handler: Handler
@Mock private lateinit var gutsText: TextView
@Mock private lateinit var mockAnimator: AnimatorSet
private lateinit var settings: ImageButton
private lateinit var cancel: View
@@ -228,6 +226,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
}
}
initGutsViewHolderMocks()
initMediaViewHolderMocks()
// Create media session
@@ -272,6 +271,20 @@ public class MediaControlPanelTest : SysuiTestCase() {
)
}
private fun initGutsViewHolderMocks() {
settings = ImageButton(context)
cancel = View(context)
cancelText = TextView(context)
dismiss = FrameLayout(context)
dismissText = TextView(context)
whenever(gutsViewHolder.gutsText).thenReturn(gutsText)
whenever(gutsViewHolder.settings).thenReturn(settings)
whenever(gutsViewHolder.cancel).thenReturn(cancel)
whenever(gutsViewHolder.cancelText).thenReturn(cancelText)
whenever(gutsViewHolder.dismiss).thenReturn(dismiss)
whenever(gutsViewHolder.dismissText).thenReturn(dismissText)
}
/**
* Initialize elements in media view holder
*/
@@ -292,11 +305,6 @@ public class MediaControlPanelTest : SysuiTestCase() {
seamlessIcon = ImageView(context)
seamlessText = TextView(context)
seekBar = SeekBar(context).also { it.id = R.id.media_progress_bar }
settings = ImageButton(context)
cancel = View(context)
cancelText = TextView(context)
dismiss = FrameLayout(context)
dismissText = TextView(context)
action0 = ImageButton(context).also { it.setId(R.id.action0) }
action1 = ImageButton(context).also { it.setId(R.id.action1) }
@@ -341,6 +349,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
whenever(viewHolder.scrubbingElapsedTimeView).thenReturn(scrubbingElapsedTimeView)
whenever(viewHolder.scrubbingTotalTimeView).thenReturn(scrubbingTotalTimeView)
whenever(viewHolder.gutsViewHolder).thenReturn(gutsViewHolder)
// Transition View
whenever(view.parent).thenReturn(transitionParent)
whenever(view.rootView).thenReturn(transitionParent)
@@ -363,15 +373,6 @@ public class MediaControlPanelTest : SysuiTestCase() {
whenever(viewHolder.action4).thenReturn(action4)
whenever(viewHolder.getAction(R.id.action4)).thenReturn(action4)
// Long press menu
whenever(viewHolder.longPressText).thenReturn(longPressText)
whenever(longPressText.handler).thenReturn(handler)
whenever(viewHolder.settings).thenReturn(settings)
whenever(viewHolder.cancel).thenReturn(cancel)
whenever(viewHolder.cancelText).thenReturn(cancelText)
whenever(viewHolder.dismiss).thenReturn(dismiss)
whenever(viewHolder.dismissText).thenReturn(dismissText)
whenever(viewHolder.actionsTopBarrier).thenReturn(actionsTopBarrier)
}
@@ -404,10 +405,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
listOf(recSubtitle1, recSubtitle2, recSubtitle3)
)
// Long press menu
whenever(recommendationViewHolder.settings).thenReturn(settings)
whenever(recommendationViewHolder.cancel).thenReturn(cancel)
whenever(recommendationViewHolder.dismiss).thenReturn(dismiss)
whenever(recommendationViewHolder.gutsViewHolder).thenReturn(gutsViewHolder)
val actionIcon = Icon.createWithResource(context, R.drawable.ic_android)
whenever(smartspaceAction.icon).thenReturn(actionIcon)
@@ -968,8 +966,10 @@ public class MediaControlPanelTest : SysuiTestCase() {
assertThat(seamless.isEnabled()).isFalse()
}
/* ***** Guts tests for the player ***** */
@Test
fun longClick_gutsClosed() {
fun player_longClickWhenGutsClosed_gutsOpens() {
player.attachPlayer(viewHolder)
player.bindPlayer(mediaData, KEY)
whenever(mediaViewController.isGutsVisible).thenReturn(false)
@@ -983,7 +983,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
}
@Test
fun longClick_gutsOpen() {
fun player_longClickWhenGutsOpen_gutsCloses() {
player.attachPlayer(viewHolder)
whenever(mediaViewController.isGutsVisible).thenReturn(true)
@@ -996,8 +996,9 @@ public class MediaControlPanelTest : SysuiTestCase() {
}
@Test
fun cancelButtonClick_animation() {
fun player_cancelButtonClick_animation() {
player.attachPlayer(viewHolder)
player.bindPlayer(mediaData, KEY)
cancel.callOnClick()
@@ -1005,7 +1006,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
}
@Test
fun settingsButtonClick() {
fun player_settingsButtonClick() {
player.attachPlayer(viewHolder)
player.bindPlayer(mediaData, KEY)
@@ -1019,7 +1020,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
}
@Test
fun dismissButtonClick() {
fun player_dismissButtonClick() {
val mediaKey = "key for dismissal"
player.attachPlayer(viewHolder)
val state = mediaData.copy(notificationKey = KEY)
@@ -1032,7 +1033,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
}
@Test
fun dismissButtonDisabled() {
fun player_dismissButtonDisabled() {
val mediaKey = "key for dismissal"
player.attachPlayer(viewHolder)
val state = mediaData.copy(isClearable = false, notificationKey = KEY)
@@ -1042,7 +1043,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
}
@Test
fun dismissButtonClick_notInManager() {
fun player_dismissButtonClick_notInManager() {
val mediaKey = "key for dismissal"
whenever(mediaDataManager.dismissMediaData(eq(mediaKey), anyLong())).thenReturn(false)
@@ -1057,6 +1058,76 @@ public class MediaControlPanelTest : SysuiTestCase() {
verify(mediaCarouselController).removePlayer(eq(mediaKey), eq(false), eq(false))
}
/* ***** END guts tests for the player ***** */
/* ***** Guts tests for the recommendations ***** */
@Test
fun recommendations_longClickWhenGutsClosed_gutsOpens() {
player.attachRecommendation(recommendationViewHolder)
player.bindRecommendation(smartspaceData)
whenever(mediaViewController.isGutsVisible).thenReturn(false)
val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
verify(viewHolder.player).onLongClickListener = captor.capture()
captor.value.onLongClick(viewHolder.player)
verify(mediaViewController).openGuts()
verify(logger).logLongPressOpen(anyInt(), eq(PACKAGE), eq(instanceId))
}
@Test
fun recommendations_longClickWhenGutsOpen_gutsCloses() {
player.attachRecommendation(recommendationViewHolder)
player.bindRecommendation(smartspaceData)
whenever(mediaViewController.isGutsVisible).thenReturn(true)
val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
verify(viewHolder.player).onLongClickListener = captor.capture()
captor.value.onLongClick(viewHolder.player)
verify(mediaViewController, never()).openGuts()
verify(mediaViewController).closeGuts(false)
}
@Test
fun recommendations_cancelButtonClick_animation() {
player.attachRecommendation(recommendationViewHolder)
player.bindRecommendation(smartspaceData)
cancel.callOnClick()
verify(mediaViewController).closeGuts(false)
}
@Test
fun recommendations_settingsButtonClick() {
player.attachRecommendation(recommendationViewHolder)
player.bindRecommendation(smartspaceData)
settings.callOnClick()
verify(logger).logLongPressSettings(anyInt(), eq(PACKAGE), eq(instanceId))
val captor = ArgumentCaptor.forClass(Intent::class.java)
verify(activityStarter).startActivity(captor.capture(), eq(true))
assertThat(captor.value.action).isEqualTo(ACTION_MEDIA_CONTROLS_SETTINGS)
}
@Test
fun recommendations_dismissButtonClick() {
val mediaKey = "key for dismissal"
player.attachRecommendation(recommendationViewHolder)
player.bindRecommendation(smartspaceData.copy(targetId = mediaKey))
assertThat(dismiss.isEnabled).isEqualTo(true)
dismiss.callOnClick()
verify(logger).logLongPressDismiss(anyInt(), eq(PACKAGE), eq(instanceId))
verify(mediaDataManager).dismissSmartspaceRecommendation(eq(mediaKey), anyLong())
}
/* ***** END guts tests for the recommendations ***** */
@Test
fun actionPlayPauseClick_isLogged() {
val semanticActions = MediaButton(