diff options
10 files changed, 382 insertions, 307 deletions
diff --git a/packages/SystemUI/res/layout/media_long_press_menu.xml b/packages/SystemUI/res/layout/media_long_press_menu.xml new file mode 100644 index 000000000000..99c5e4707338 --- /dev/null +++ b/packages/SystemUI/res/layout/media_long_press_menu.xml @@ -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>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml index 665edac65afc..7962e22d6b7f 100644 --- a/packages/SystemUI/res/layout/media_session_view.xml +++ b/packages/SystemUI/res/layout/media_session_view.xml @@ -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> diff --git a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml index 5510f24870bb..659a578aa61f 100644 --- a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml +++ b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml @@ -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"/> - - <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> + <include + layout="@layout/media_long_press_menu" /> - <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> diff --git a/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt index 5023cf685457..5a214d1cd5e0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt +++ b/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt @@ -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( diff --git a/packages/SystemUI/src/com/android/systemui/media/GutsViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/GutsViewHolder.kt new file mode 100644 index 000000000000..1a48a84aff23 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/GutsViewHolder.kt @@ -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 + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index 3faccdb6ffda..af54e966ed9c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -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); + } + }); } /** diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt index 327268bcbd47..1437c965512e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt @@ -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. diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt index b8b731868e5c..5c93cdaeb0da 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt @@ -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( diff --git a/packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt index a83984036f60..52ac4e0682a3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt @@ -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 ) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt index 4e130d10f6ec..83fb82c1c493 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -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( |