diff options
author | 2024-11-21 20:29:13 +0000 | |
---|---|---|
committer | 2024-11-21 20:29:13 +0000 | |
commit | 3fa794b61228a0804c8f23f414c982551a03be05 (patch) | |
tree | 8231a8bd390400b242a2401a9d8e05fecbe09491 | |
parent | 0e40126b9472bbc9e42edb4622534260996726ff (diff) | |
parent | a9d1a4ce68d84d22e5a91ac412104961a52db3ad (diff) |
Merge "Polish notification dismiss button" into main
9 files changed, 289 insertions, 54 deletions
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml index 565d584875ac..57959361bd48 100644 --- a/core/res/res/layout/notification_template_header.xml +++ b/core/res/res/layout/notification_template_header.xml @@ -13,7 +13,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License --> -<!-- extends FrameLayout --> +<!-- extends RelativeLayout --> <NotificationHeaderView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/notification_header" @@ -62,7 +62,7 @@ android:layout_height="match_parent" android:layout_alignParentStart="true" android:layout_centerVertical="true" - android:layout_toStartOf="@id/notification_buttons_column" + android:layout_toStartOf="@id/expand_button" android:layout_alignWithParentIfMissing="true" android:clipChildren="false" android:gravity="center_vertical" @@ -83,28 +83,17 @@ android:focusable="false" /> - <LinearLayout - android:id="@+id/notification_buttons_column" + <include layout="@layout/notification_expand_button" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentEnd="true" - android:orientation="vertical" - > - - <include layout="@layout/notification_close_button" - android:layout_width="@dimen/notification_close_button_size" - android:layout_height="@dimen/notification_close_button_size" - android:layout_gravity="end" - android:layout_marginEnd="20dp" - /> - - <include layout="@layout/notification_expand_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentEnd="true" - android:layout_centerVertical="true" - /> + android:layout_centerVertical="true" + android:layout_alignParentEnd="true" /> - </LinearLayout> + <include layout="@layout/notification_close_button" + android:id="@+id/close_button" + android:layout_width="@dimen/notification_close_button_size" + android:layout_height="@dimen/notification_close_button_size" + android:layout_alignParentTop="true" + android:layout_alignParentEnd="true" /> </NotificationHeaderView> diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml index 29f14a4a93fc..227f84bb2eed 100644 --- a/core/res/res/layout/notification_template_material_base.xml +++ b/core/res/res/layout/notification_template_material_base.xml @@ -157,39 +157,27 @@ android:maxDrawableHeight="@dimen/notification_right_icon_size" /> - <LinearLayout - android:id="@+id/notification_buttons_column" + <FrameLayout + android:id="@+id/expand_button_touch_container" android:layout_width="wrap_content" android:layout_height="match_parent" - android:layout_alignParentEnd="true" - android:orientation="vertical" + android:minWidth="@dimen/notification_content_margin_end" > - <include layout="@layout/notification_close_button" - android:layout_width="@dimen/notification_close_button_size" - android:layout_height="@dimen/notification_close_button_size" - android:layout_gravity="end" - android:layout_marginEnd="20dp" - /> - - <FrameLayout - android:id="@+id/expand_button_touch_container" + <include layout="@layout/notification_expand_button" android:layout_width="wrap_content" - android:layout_height="0dp" - android:layout_weight="1" - android:minWidth="@dimen/notification_content_margin_end" - > - - <include layout="@layout/notification_expand_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical|end" - /> - - </FrameLayout> + android:layout_height="wrap_content" + android:layout_gravity="center_vertical|end" + /> - </LinearLayout> + </FrameLayout> </LinearLayout> + <include layout="@layout/notification_close_button" + android:id="@+id/close_button" + android:layout_width="@dimen/notification_close_button_size" + android:layout_height="@dimen/notification_close_button_size" + android:layout_gravity="top|end" /> + </FrameLayout> diff --git a/core/res/res/layout/notification_template_material_media.xml b/core/res/res/layout/notification_template_material_media.xml index 6e9d17fc31d4..5459fa895f82 100644 --- a/core/res/res/layout/notification_template_material_media.xml +++ b/core/res/res/layout/notification_template_material_media.xml @@ -15,6 +15,7 @@ ~ limitations under the License --> +<!-- extends FrameLayout --> <com.android.internal.widget.MediaNotificationView android:id="@+id/status_bar_latest_event_content" xmlns:android="http://schemas.android.com/apk/res/android" @@ -191,4 +192,11 @@ </FrameLayout> </LinearLayout> + + <include layout="@layout/notification_close_button" + android:id="@+id/close_button" + android:layout_width="@dimen/notification_close_button_size" + android:layout_height="@dimen/notification_close_button_size" + android:layout_gravity="top|end" /> + </com.android.internal.widget.MediaNotificationView> diff --git a/core/res/res/layout/notification_template_material_messaging.xml b/core/res/res/layout/notification_template_material_messaging.xml index 1eae41da1e81..2b3b7d873f4f 100644 --- a/core/res/res/layout/notification_template_material_messaging.xml +++ b/core/res/res/layout/notification_template_material_messaging.xml @@ -195,6 +195,12 @@ </LinearLayout> + <include layout="@layout/notification_close_button" + android:id="@+id/close_button" + android:layout_width="@dimen/notification_close_button_size" + android:layout_height="@dimen/notification_close_button_size" + android:layout_gravity="top|end" /> + </com.android.internal.widget.NotificationMaxHeightFrameLayout> <LinearLayout diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 94ffb48e28c1..fc24f4599b25 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -5111,7 +5111,6 @@ <java-symbol type="layout" name="notification_expand_button"/> <java-symbol type="id" name="close_button" /> <java-symbol type="layout" name="notification_close_button"/> - <java-symbol type="id" name="notification_buttons_column" /> <java-symbol type="bool" name="config_supportsMicToggle" /> <java-symbol type="bool" name="config_supportsCamToggle" /> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index fb62f8000c7c..0480212e4d59 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -36,6 +36,7 @@ import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Path; import android.graphics.Point; +import android.graphics.Rect; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.Drawable; @@ -108,6 +109,7 @@ import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderVi import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction; import com.android.systemui.statusbar.notification.row.wrapper.NotificationCompactMessagingTemplateViewWrapper; import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; +import com.android.systemui.statusbar.notification.shared.NotificationAddXOnHoverToDismiss; import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization; import com.android.systemui.statusbar.notification.shared.TransparentHeaderFix; import com.android.systemui.statusbar.notification.stack.AmbientState; @@ -125,6 +127,7 @@ import com.android.systemui.statusbar.policy.SmartReplyConstants; import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent; import com.android.systemui.util.Compile; import com.android.systemui.util.DumpUtilsKt; +import com.android.systemui.util.ListenerSet; import com.android.systemui.wmshell.BubblesManager; import java.io.PrintWriter; @@ -430,6 +433,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private float mBottomRoundnessDuringLaunchAnimation; private float mSmallRoundness; + private ListenerSet<DismissButtonTargetVisibilityListener> + mDismissButtonTargetVisibilityListeners + = new ListenerSet(); + public NotificationContentView[] getLayouts() { return Arrays.copyOf(mLayouts, mLayouts.length); } @@ -739,6 +746,73 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } + public interface DismissButtonTargetVisibilityListener { + // Called when the notification dismiss button's target visibility changes. + // NOTE: This can be called when the dismiss button already has the target visibility. + void onTargetVisibilityChanged(boolean targetVisible); + } + + public void addDismissButtonTargetStateListener( + DismissButtonTargetVisibilityListener listener) { + if (NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode()) { + return; + } + + mDismissButtonTargetVisibilityListeners.addIfAbsent(listener); + } + + public void removeDismissButtonTargetStateListener( + DismissButtonTargetVisibilityListener listener) { + if (NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode()) { + return; + } + + mDismissButtonTargetVisibilityListeners.remove(listener); + } + + @Override + public boolean onInterceptHoverEvent(MotionEvent event) { + if (!NotificationAddXOnHoverToDismiss.isEnabled()) { + return super.onInterceptHoverEvent(event); + } + + // Do not bother checking the dismiss button's target visibility if the notification cannot + // be dismissed. + if (!canEntryBeDismissed()) { + return false; + } + + final Boolean targetVisible = getDismissButtonTargetVisibilityIfAny(event); + if (targetVisible != null) { + for (DismissButtonTargetVisibilityListener listener : + mDismissButtonTargetVisibilityListeners) { + listener.onTargetVisibilityChanged(targetVisible.booleanValue()); + } + } + + // Do not consume the hover event so that children still have a chance to process it. + return false; + } + + private @Nullable Boolean getDismissButtonTargetVisibilityIfAny(MotionEvent event) { + // Returns the dismiss button's target visibility resulted by `event`. Returns null if the + // target visibility should not change. + + if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) { + // The notification dismiss button should be hidden when the hover exit event is located + // outside of the notification. NOTE: The hover exit event can be inside the + // notification if hover moves from one hoverable child to another. + final Rect localBounds = new Rect(0, 0, this.getWidth(), this.getActualHeight()); + if (!localBounds.contains((int) event.getX(), (int) event.getY())) { + return Boolean.FALSE; + } + } else if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER) { + return Boolean.TRUE; + } + + return null; + } + private void updateLimitsForView(NotificationContentView layout) { View contractedView = layout.getContractedChild(); boolean customView = contractedView != null @@ -2218,6 +2292,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mTranslateableViews.remove(mGutsStub); // We don't handle focus highlight in this view, it's done in background drawable instead setDefaultFocusHighlightEnabled(false); + + if (NotificationAddXOnHoverToDismiss.isEnabled()) { + addDismissButtonTargetStateListener(findViewById(R.id.backgroundNormal)); + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java index d0db5145e0ff..34ef63944f14 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java @@ -21,7 +21,9 @@ import static com.android.systemui.util.ColorUtilKt.hexColorString; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Canvas; +import android.graphics.Path; import android.graphics.PorterDuff; +import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.LayerDrawable; @@ -36,6 +38,7 @@ import com.android.internal.util.ContrastColorUtil; import com.android.settingslib.Utils; import com.android.systemui.Dumpable; import com.android.systemui.res.R; +import com.android.systemui.statusbar.notification.shared.NotificationAddXOnHoverToDismiss; import com.android.systemui.util.DrawableDumpKt; import java.io.PrintWriter; @@ -44,7 +47,8 @@ import java.util.Arrays; /** * A view that can be used for both the dimmed and normal background of an notification. */ -public class NotificationBackgroundView extends View implements Dumpable { +public class NotificationBackgroundView extends View implements Dumpable, + ExpandableNotificationRow.DismissButtonTargetVisibilityListener { private final boolean mDontModifyCorners; private Drawable mBackground; @@ -66,6 +70,11 @@ public class NotificationBackgroundView extends View implements Dumpable { private final ColorStateList mLightColoredStatefulColors; private final ColorStateList mDarkColoredStatefulColors; private final int mNormalColor; + private final int convexR = 9; + private final int concaveR = 22; + + // True only if the dismiss button is visible. + private boolean mDrawDismissButtonCutout = false; public NotificationBackgroundView(Context context, AttributeSet attrs) { super(context, attrs); @@ -80,6 +89,18 @@ public class NotificationBackgroundView extends View implements Dumpable { } @Override + public void onTargetVisibilityChanged(boolean targetVisible) { + if (NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode()) { + return; + } + + if (mDrawDismissButtonCutout != targetVisible) { + mDrawDismissButtonCutout = targetVisible; + invalidate(); + } + } + + @Override protected void onDraw(Canvas canvas) { if (mClipTopAmount + mClipBottomAmount < getActualHeight() || mExpandAnimationRunning) { canvas.save(); @@ -87,12 +108,87 @@ public class NotificationBackgroundView extends View implements Dumpable { canvas.clipRect(0, mClipTopAmount, getWidth(), getActualHeight() - mClipBottomAmount); } - draw(canvas, mBackground); + + if (!NotificationAddXOnHoverToDismiss.isEnabled()) { + draw(canvas, mBackground); + canvas.restore(); + return; + } + + Rect backgroundBounds = null; + if (mBackground != null || mDrawDismissButtonCutout) { + backgroundBounds = calculateBackgroundBounds(); + } + + if (mDrawDismissButtonCutout) { + canvas.clipPath(calculateDismissButtonCutoutPath(backgroundBounds)); + } + + if (mBackground != null) { + mBackground.setBounds(backgroundBounds); + mBackground.draw(canvas); + } + canvas.restore(); } } + private Path calculateDismissButtonCutoutPath(Rect backgroundBounds) { + // TODO(b/365585705): Adapt to RTL after the UX design is finalized. + + NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode(); + + Path path = new Path(); + + final int left = backgroundBounds.left; + final int right = backgroundBounds.right; + final int top = backgroundBounds.top; + final int bottom = backgroundBounds.bottom; + + // Generate the path clockwise from the left-top corner. + path.moveTo(left, top); + path.lineTo(right - 2 * convexR - concaveR, top); + path.quadTo(right - convexR - concaveR, top, right - convexR - concaveR, + top + convexR); + path.quadTo(right - convexR - concaveR, top + convexR + concaveR, right - convexR, + top + convexR + concaveR); + path.quadTo(right, top + convexR + concaveR, right, top + 2 * convexR + concaveR); + path.lineTo(right, bottom); + path.lineTo(left, bottom); + path.lineTo(left, top); + + return path; + } + + private Rect calculateBackgroundBounds() { + NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode(); + + int top = 0; + int bottom = getActualHeight(); + if (mBottomIsRounded + && mBottomAmountClips + && !mExpandAnimationRunning) { + bottom -= mClipBottomAmount; + } + final boolean isRtl = isLayoutRtl(); + final int width = getWidth(); + final int actualWidth = getActualWidth(); + + int left = isRtl ? width - actualWidth : 0; + int right = isRtl ? width : actualWidth; + + if (mExpandAnimationRunning) { + // Horizontally center this background view inside of the container + left = (int) ((width - actualWidth) / 2.0f); + right = (int) (left + actualWidth); + } + + return new Rect(left, top, right, bottom); + } + private void draw(Canvas canvas, Drawable drawable) { + NotificationAddXOnHoverToDismiss.assertInLegacyMode(); + if (drawable != null) { int top = 0; int bottom = getActualHeight(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java index b622defbef98..e9eecdd8a26f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java @@ -16,6 +16,9 @@ package com.android.systemui.statusbar.notification.row.wrapper; +import static android.view.View.GONE; +import static android.view.View.VISIBLE; + import static com.android.systemui.statusbar.notification.TransformState.TRANSFORM_Y; import android.app.Notification; @@ -48,6 +51,7 @@ import com.android.systemui.statusbar.notification.Roundable; import com.android.systemui.statusbar.notification.RoundableState; import com.android.systemui.statusbar.notification.TransformState; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.shared.NotificationAddXOnHoverToDismiss; import java.util.Stack; @@ -115,6 +119,10 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple resolveHeaderViews(); addFeedbackOnClickListener(row); addCloseButtonOnClickListener(row); + + if (NotificationAddXOnHoverToDismiss.isEnabled()) { + mRow.addDismissButtonTargetStateListener(mHoverListener); + } } @Override @@ -166,13 +174,34 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple } } + private ExpandableNotificationRow.DismissButtonTargetVisibilityListener mHoverListener = new + ExpandableNotificationRow.DismissButtonTargetVisibilityListener() { + @Override + public void onTargetVisibilityChanged(boolean targetVisible) { + NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode(); + + if (mCloseButton != null) { + mCloseButton.setVisibility(targetVisible ? VISIBLE : GONE); + } + } + }; + + @Override + public void setRemoved() { + super.setRemoved(); + + if (NotificationAddXOnHoverToDismiss.isEnabled()) { + mRow.removeDismissButtonTargetStateListener(mHoverListener); + } + } + /** * Shows the given feedback icon, or hides the icon if null. */ @Override public void setFeedbackIcon(@Nullable FeedbackIcon icon) { if (mFeedbackIcon != null) { - mFeedbackIcon.setVisibility(icon != null ? View.VISIBLE : View.GONE); + mFeedbackIcon.setVisibility(icon != null ? VISIBLE : GONE); if (icon != null) { if (mFeedbackIcon instanceof ImageButton) { ((ImageButton) mFeedbackIcon).setImageResource(icon.getIconRes()); @@ -266,7 +295,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple boolean expandable, View.OnClickListener onClickListener, boolean requestLayout) { - mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE); + mExpandButton.setVisibility(expandable ? VISIBLE : GONE); mExpandButton.setOnClickListener(expandable ? onClickListener : null); if (mAltExpandTarget != null) { mAltExpandTarget.setOnClickListener(expandable ? onClickListener : null); @@ -294,7 +323,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple @Override public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) { if (mAudiblyAlertedIcon != null) { - mAudiblyAlertedIcon.setVisibility(audiblyAlerted ? View.VISIBLE : View.GONE); + mAudiblyAlertedIcon.setVisibility(audiblyAlerted ? VISIBLE : GONE); } } @@ -371,6 +400,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple ((DateTimeView) timeView).setTime(whenMillis); } } + protected void addTransformedViews(View... views) { for (View view : views) { if (view != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAddXOnHoverToDismiss.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAddXOnHoverToDismiss.kt new file mode 100644 index 000000000000..0961874b2276 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAddXOnHoverToDismiss.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 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.statusbar.notification.shared + +import com.android.systemui.Flags +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for reading or using the notification dismiss button on hover flag state. */ +@Suppress("NOTHING_TO_INLINE") +object NotificationAddXOnHoverToDismiss { + const val FLAG_NAME = Flags.FLAG_NOTIFICATION_ADD_X_ON_HOVER_TO_DISMISS + + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + @JvmStatic + inline val isEnabled + get() = Flags.notificationAddXOnHoverToDismiss() + + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + @JvmStatic + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) +} |