diff options
26 files changed, 656 insertions, 260 deletions
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index e137acf4dc70..9b42fa2012e1 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -5817,7 +5817,7 @@ public class Notification implements Parcelable PorterDuff.Mode.SRC_ATOP); } - contentView.setInt(R.id.notification_header, "setOriginalIconColor", + contentView.setInt(R.id.icon, "setOriginalIconColor", colorable ? color : NotificationHeaderView.NO_COLOR); } diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java index 18e0132e2c4e..0359f3b4fde7 100644 --- a/core/java/android/view/NotificationHeaderView.java +++ b/core/java/android/view/NotificationHeaderView.java @@ -65,7 +65,6 @@ public class NotificationHeaderView extends ViewGroup { private View mMicIcon; private View mAppOps; private View mAudiblyAlertedIcon; - private int mIconColor; private boolean mExpanded; private boolean mShowExpandButtonAtEnd; private boolean mShowWorkBadgeAtEnd; @@ -315,13 +314,8 @@ public class NotificationHeaderView extends ViewGroup { updateTouchListener(); } - @RemotableViewMethod - public void setOriginalIconColor(int color) { - mIconColor = color; - } - public int getOriginalIconColor() { - return mIconColor; + return mIcon.getOriginalIconColor(); } public int getOriginalNotificationColor() { diff --git a/core/java/com/android/internal/widget/CachingIconView.java b/core/java/com/android/internal/widget/CachingIconView.java index bd0623e1144e..84cde1b84e14 100644 --- a/core/java/com/android/internal/widget/CachingIconView.java +++ b/core/java/com/android/internal/widget/CachingIconView.java @@ -46,6 +46,9 @@ public class CachingIconView extends ImageView { private boolean mForceHidden; private int mDesiredVisibility; private Consumer<Integer> mOnVisibilityChangedListener; + private Consumer<Boolean> mOnForceHiddenChangedListener; + private int mIconColor; + private boolean mWillBeForceHidden; @UnsupportedAppUsage public CachingIconView(Context context, @Nullable AttributeSet attrs) { @@ -184,10 +187,18 @@ public class CachingIconView extends ImageView { /** * Set the icon to be forcibly hidden, even when it's visibility is changed to visible. + * This is necessary since we still want to keep certain views hidden when their visibility + * is modified from other sources like the shelf. */ public void setForceHidden(boolean forceHidden) { - mForceHidden = forceHidden; - updateVisibility(); + if (forceHidden != mForceHidden) { + mForceHidden = forceHidden; + mWillBeForceHidden = false; + updateVisibility(); + if (mOnForceHiddenChangedListener != null) { + mOnForceHiddenChangedListener.accept(forceHidden); + } + } } @Override @@ -209,4 +220,38 @@ public class CachingIconView extends ImageView { public void setOnVisibilityChangedListener(Consumer<Integer> listener) { mOnVisibilityChangedListener = listener; } + + public void setOnForceHiddenChangedListener(Consumer<Boolean> listener) { + mOnForceHiddenChangedListener = listener; + } + + + public boolean isForceHidden() { + return mForceHidden; + } + + @RemotableViewMethod + public void setOriginalIconColor(int color) { + mIconColor = color; + } + + public int getOriginalIconColor() { + return mIconColor; + } + + /** + * @return if the view will be forceHidden after an animation + */ + public boolean willBeForceHidden() { + return mWillBeForceHidden; + } + + /** + * Set that this view will be force hidden after an animation + * + * @param forceHidden if it will be forcehidden + */ + public void setWillBeForceHidden(boolean forceHidden) { + mWillBeForceHidden = forceHidden; + } } diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index bedf55d52391..1336ec412cdb 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -18,6 +18,8 @@ package com.android.internal.widget; import static com.android.internal.widget.MessagingGroup.IMAGE_DISPLAY_LOCATION_EXTERNAL; import static com.android.internal.widget.MessagingGroup.IMAGE_DISPLAY_LOCATION_INLINE; +import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_IN; +import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_OUT; import android.annotation.AttrRes; import android.annotation.NonNull; @@ -106,7 +108,7 @@ public class ConversationLayout extends FrameLayout private CharSequence mNameReplacement; private boolean mIsCollapsed; private ImageResolver mImageResolver; - private ImageView mConversationIcon; + private CachingIconView mConversationIcon; private View mConversationIconContainer; private int mConversationIconTopPadding; private int mConversationIconTopPaddingExpandedGroup; @@ -114,7 +116,7 @@ public class ConversationLayout extends FrameLayout private int mExpandedGroupMessagePaddingNoAppName; private TextView mConversationText; private View mConversationIconBadge; - private ImageView mConversationIconBadgeBg; + private CachingIconView mConversationIconBadgeBg; private Icon mLargeIcon; private View mExpandButtonContainer; private ViewGroup mExpandButtonAndContentContainer; @@ -125,7 +127,7 @@ public class ConversationLayout extends FrameLayout private int mConversationAvatarSize; private int mConversationAvatarSizeExpanded; private CachingIconView mIcon; - private View mImportanceRingView; + private CachingIconView mImportanceRingView; private int mExpandedGroupSideMargin; private int mExpandedGroupSideMarginFacePile; private View mConversationFacePile; @@ -140,11 +142,15 @@ public class ConversationLayout extends FrameLayout private int mContentMarginEnd; private Rect mMessagingClipRect; private ObservableTextView mAppName; + private ViewGroup mActions; + private int mConversationContentStart; + private int mInternalButtonPadding; private boolean mAppNameGone; private int mFacePileAvatarSize; private int mFacePileAvatarSizeExpandedGroup; private int mFacePileProtectionWidth; private int mFacePileProtectionWidthExpanded; + private boolean mImportantConversation; public ConversationLayout(@NonNull Context context) { super(context); @@ -168,6 +174,7 @@ public class ConversationLayout extends FrameLayout protected void onFinishInflate() { super.onFinishInflate(); mMessagingLinearLayout = findViewById(R.id.notification_messaging); + mActions = findViewById(R.id.actions); mMessagingLinearLayout.setMessagingLayout(this); mImageMessageContainer = findViewById(R.id.conversation_image_message_container); // We still want to clip, but only on the top, since views can temporarily out of bounds @@ -186,9 +193,41 @@ public class ConversationLayout extends FrameLayout mConversationIconBadge = findViewById(R.id.conversation_icon_badge); mConversationIconBadgeBg = findViewById(R.id.conversation_icon_badge_bg); mIcon.setOnVisibilityChangedListener((visibility) -> { - // Always keep the badge visibility in sync with the icon. This is necessary in cases - // Where the icon is being hidden externally like in group children. - mConversationIconBadge.setVisibility(visibility); + + // Let's hide the background directly or in an animated way + boolean isGone = visibility == GONE; + int oldVisibility = mConversationIconBadgeBg.getVisibility(); + boolean wasGone = oldVisibility == GONE; + if (wasGone != isGone) { + // Keep the badge gone state in sync with the icon. This is necessary in cases + // Where the icon is being hidden externally like in group children. + mConversationIconBadgeBg.animate().cancel(); + mConversationIconBadgeBg.setVisibility(visibility); + } + + // Let's handle the importance ring which can also be be gone normally + oldVisibility = mImportanceRingView.getVisibility(); + wasGone = oldVisibility == GONE; + visibility = !mImportantConversation ? GONE : visibility; + isGone = visibility == GONE; + if (wasGone != isGone) { + // Keep the badge visibility in sync with the icon. This is necessary in cases + // Where the icon is being hidden externally like in group children. + mImportanceRingView.animate().cancel(); + mImportanceRingView.setVisibility(visibility); + } + }); + // When the small icon is gone, hide the rest of the badge + mIcon.setOnForceHiddenChangedListener((forceHidden) -> { + animateViewForceHidden(mConversationIconBadgeBg, forceHidden); + animateViewForceHidden(mImportanceRingView, forceHidden); + }); + + // When the conversation icon is gone, hide the whole badge + mConversationIcon.setOnForceHiddenChangedListener((forceHidden) -> { + animateViewForceHidden(mConversationIconBadgeBg, forceHidden); + animateViewForceHidden(mImportanceRingView, forceHidden); + animateViewForceHidden(mIcon, forceHidden); }); mConversationText = findViewById(R.id.conversation_text); mExpandButtonContainer = findViewById(R.id.expand_button_container); @@ -238,6 +277,33 @@ public class ConversationLayout extends FrameLayout mAppName.setOnVisibilityChangedListener((visibility) -> { onAppNameVisibilityChanged(); }); + mConversationContentStart = getResources().getDimensionPixelSize( + R.dimen.conversation_content_start); + mInternalButtonPadding + = getResources().getDimensionPixelSize(R.dimen.button_padding_horizontal_material) + + getResources().getDimensionPixelSize(R.dimen.button_inset_horizontal_material); + } + + private void animateViewForceHidden(CachingIconView view, boolean forceHidden) { + boolean nowForceHidden = view.willBeForceHidden() || view.isForceHidden(); + if (forceHidden == nowForceHidden) { + // We are either already forceHidden or will be + return; + } + view.animate().cancel(); + view.setWillBeForceHidden(forceHidden); + view.animate() + .scaleX(forceHidden ? 0.5f : 1.0f) + .scaleY(forceHidden ? 0.5f : 1.0f) + .alpha(forceHidden ? 0.0f : 1.0f) + .setInterpolator(forceHidden ? ALPHA_OUT : ALPHA_IN) + .setDuration(160); + if (view.getVisibility() != VISIBLE) { + view.setForceHidden(forceHidden); + } else { + view.animate().withEndAction(() -> view.setForceHidden(forceHidden)); + } + view.animate().start(); } @RemotableViewMethod @@ -255,9 +321,14 @@ public class ConversationLayout extends FrameLayout */ @RemotableViewMethod public void setIsImportantConversation(boolean isImportantConversation) { + mImportantConversation = isImportantConversation; mImportanceRingView.setVisibility(isImportantConversation ? VISIBLE : GONE); } + public boolean isImportantConversation() { + return mImportantConversation; + } + /** * Set this layout to show the collapsed representation. * @@ -363,7 +434,6 @@ public class ConversationLayout extends FrameLayout private void updateConversationLayout() { // Set avatar and name CharSequence conversationText = mConversationTitle; - // TODO: display the secondary text somewhere if (mIsOneToOne) { // Let's resolve the icon / text from the last sender mConversationIcon.setVisibility(VISIBLE); @@ -418,6 +488,27 @@ public class ConversationLayout extends FrameLayout updateIconPositionAndSize(); updateImageMessages(); updatePaddingsBasedOnContentAvailability(); + updateActionListPadding(); + } + + private void updateActionListPadding() { + if (mActions == null) { + return; + } + View firstAction = mActions.getChildAt(0); + if (firstAction != null) { + // Let's visually position the first action where the content starts + int paddingStart = mConversationContentStart; + + MarginLayoutParams layoutParams = (MarginLayoutParams) firstAction.getLayoutParams(); + paddingStart -= layoutParams.getMarginStart(); + paddingStart -= mInternalButtonPadding; + + mActions.setPaddingRelative(paddingStart, + mActions.getPaddingTop(), + mActions.getPaddingEnd(), + mActions.getPaddingBottom()); + } } private void updateImageMessages() { diff --git a/core/java/com/android/internal/widget/MessagingPropertyAnimator.java b/core/java/com/android/internal/widget/MessagingPropertyAnimator.java index 7703cb0f13db..a3a75c098a00 100644 --- a/core/java/com/android/internal/widget/MessagingPropertyAnimator.java +++ b/core/java/com/android/internal/widget/MessagingPropertyAnimator.java @@ -32,7 +32,7 @@ import com.android.internal.R; */ public class MessagingPropertyAnimator implements View.OnLayoutChangeListener { private static final long APPEAR_ANIMATION_LENGTH = 210; - private static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f); + public static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f); public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f); private static final int TAG_TOP_ANIMATOR = R.id.tag_top_animator; private static final int TAG_TOP = R.id.tag_top_override; diff --git a/core/res/res/layout/notification_template_material_conversation.xml b/core/res/res/layout/notification_template_material_conversation.xml index 7cadecbbdb91..46d3d1326920 100644 --- a/core/res/res/layout/notification_template_material_conversation.xml +++ b/core/res/res/layout/notification_template_material_conversation.xml @@ -42,7 +42,7 @@ > <!-- Big icon: 52x52, 12dp padding left + top, 16dp padding right --> - <ImageView + <com.android.internal.widget.CachingIconView android:id="@+id/conversation_icon" android:layout_width="@dimen/conversation_avatar_size" android:layout_height="@dimen/conversation_avatar_size" @@ -64,11 +64,12 @@ android:layout_marginLeft="@dimen/conversation_badge_side_margin" android:layout_marginTop="@dimen/conversation_badge_side_margin" > - <ImageView + <com.android.internal.widget.CachingIconView android:id="@+id/conversation_icon_badge_bg" android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/conversation_badge_background" + android:forceHasOverlappingRendering="false" /> <com.android.internal.widget.CachingIconView android:id="@+id/icon" @@ -76,13 +77,15 @@ android:layout_height="match_parent" android:layout_margin="4dp" android:layout_gravity="center" + android:forceHasOverlappingRendering="false" /> - <ImageView + <com.android.internal.widget.CachingIconView android:id="@+id/conversation_icon_badge_ring" android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/conversation_badge_ring" android:visibility="gone" + android:forceHasOverlappingRendering="false" /> </FrameLayout> </FrameLayout> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 04c6a41833df..0fea372ea580 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3895,6 +3895,8 @@ <java-symbol type="dimen" name="conversation_icon_container_top_padding_small_avatar" /> <java-symbol type="dimen" name="conversation_icon_container_top_padding_no_app_name" /> <java-symbol type="layout" name="notification_template_material_conversation" /> + <java-symbol type="dimen" name="button_padding_horizontal_material" /> + <java-symbol type="dimen" name="button_inset_horizontal_material" /> <java-symbol type="layout" name="conversation_face_pile_layout" /> <!-- Intent resolver and share sheet --> diff --git a/packages/SystemUI/src/com/android/systemui/Interpolators.java b/packages/SystemUI/src/com/android/systemui/Interpolators.java index 2b3ea3ab56c0..6923079dd5c4 100644 --- a/packages/SystemUI/src/com/android/systemui/Interpolators.java +++ b/packages/SystemUI/src/com/android/systemui/Interpolators.java @@ -49,6 +49,8 @@ public class Interpolators { public static final Interpolator CUSTOM_40_40 = new PathInterpolator(0.4f, 0f, 0.6f, 1f); public static final Interpolator HEADS_UP_APPEAR = new HeadsUpAppearInterpolator(); public static final Interpolator ICON_OVERSHOT = new PathInterpolator(0.4f, 0f, 0.2f, 1.4f); + public static final Interpolator ICON_OVERSHOT_LESS + = new PathInterpolator(0.4f, 0f, 0.2f, 1.1f); public static final Interpolator PANEL_CLOSE_ACCELERATED = new PathInterpolator(0.3f, 0, 0.5f, 1); public static final Interpolator BOUNCE = new BounceInterpolator(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 651623b45f6a..d798692879f5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -27,6 +27,7 @@ import android.graphics.Rect; import android.os.SystemProperties; import android.util.AttributeSet; import android.util.Log; +import android.util.MathUtils; import android.view.DisplayCutout; import android.view.View; import android.view.ViewGroup; @@ -536,55 +537,39 @@ public class NotificationShelf extends ActivatableNotificationView implements // Let calculate how much the view is in the shelf float viewStart = view.getTranslationY(); int fullHeight = view.getActualHeight() + mPaddingBetweenElements; - float iconTransformDistance = getIntrinsicHeight() * 1.5f; - iconTransformDistance *= NotificationUtils.interpolate(1.f, 1.5f, expandAmount); - iconTransformDistance = Math.min(iconTransformDistance, fullHeight); + float iconTransformStart = calculateIconTransformationStart(view); + + float transformDistance = getIntrinsicHeight() * 1.5f; + transformDistance *= NotificationUtils.interpolate(1.f, 1.5f, expandAmount); + transformDistance = Math.min(transformDistance, fullHeight); + + // Let's make sure the transform distance is + // at most to the icon (relevant for conversations) + transformDistance = Math.min(viewStart + fullHeight - iconTransformStart, + transformDistance); + if (isLastChild) { fullHeight = Math.min(fullHeight, view.getMinHeight() - getIntrinsicHeight()); - iconTransformDistance = Math.min(iconTransformDistance, view.getMinHeight() + transformDistance = Math.min(transformDistance, view.getMinHeight() - getIntrinsicHeight()); } float viewEnd = viewStart + fullHeight; - // TODO: fix this check for anchor scrolling. - if (iconState != null && expandingAnimated && mAmbientState.getScrollY() == 0 - && !mAmbientState.isOnKeyguard() && !iconState.isLastExpandIcon) { - // We are expanding animated. Because we switch to a linear interpolation in this case, - // the last icon may be stuck in between the shelf position and the notification - // position, which looks pretty bad. We therefore optimize this case by applying a - // shorter transition such that the icon is either fully in the notification or we clamp - // it into the shelf if it's close enough. - // We need to persist this, since after the expansion, the behavior should still be the - // same. - float position = mAmbientState.getIntrinsicPadding() - + mHostLayout.getPositionInLinearLayout(view); - int maxShelfStart = mMaxLayoutHeight - getIntrinsicHeight(); - if (position < maxShelfStart && position + view.getIntrinsicHeight() >= maxShelfStart - && view.getTranslationY() < position) { - iconState.isLastExpandIcon = true; - iconState.customTransformHeight = NO_VALUE; - // Let's check if we're close enough to snap into the shelf - boolean forceInShelf = mMaxLayoutHeight - getIntrinsicHeight() - position - < getIntrinsicHeight(); - if (!forceInShelf) { - // We are overlapping the shelf but not enough, so the icon needs to be - // repositioned - iconState.customTransformHeight = (int) (mMaxLayoutHeight - - getIntrinsicHeight() - position); - } - } - } + handleCustomTransformHeight(view, expandingAnimated, iconState); + float fullTransitionAmount; float transitionAmount; + float contentTransformationAmount; float shelfStart = getTranslationY(); - if (iconState != null && iconState.hasCustomTransformHeight()) { - fullHeight = iconState.customTransformHeight; - iconTransformDistance = iconState.customTransformHeight; - } boolean fullyInOrOut = true; if (viewEnd >= shelfStart && (!mAmbientState.isUnlockHintRunning() || view.isInShelf()) && (mAmbientState.isShadeExpanded() || (!view.isPinned() && !view.isHeadsUpAnimatingAway()))) { if (viewStart < shelfStart) { + if (iconState != null && iconState.hasCustomTransformHeight()) { + fullHeight = iconState.customTransformHeight; + transformDistance = iconState.customTransformHeight; + } + float fullAmount = (shelfStart - viewStart) / fullHeight; fullAmount = Math.min(1.0f, fullAmount); float interpolatedAmount = Interpolators.ACCELERATE_DECELERATE.getInterpolation( @@ -593,87 +578,163 @@ public class NotificationShelf extends ActivatableNotificationView implements interpolatedAmount, fullAmount, expandAmount); fullTransitionAmount = 1.0f - interpolatedAmount; - transitionAmount = (shelfStart - viewStart) / iconTransformDistance; - transitionAmount = Math.min(1.0f, transitionAmount); + if (isLastChild) { + // If it's the last child we should use all of the notification to transform + // instead of just to the icon, since that can be quite low. + transitionAmount = (shelfStart - viewStart) / transformDistance; + } else { + transitionAmount = (shelfStart - iconTransformStart) / transformDistance; + } + transitionAmount = MathUtils.constrain(transitionAmount, 0.0f, 1.0f); transitionAmount = 1.0f - transitionAmount; fullyInOrOut = false; } else { fullTransitionAmount = 1.0f; transitionAmount = 1.0f; } + + // Transforming the content + contentTransformationAmount = (shelfStart - viewStart) / transformDistance; + contentTransformationAmount = Math.min(1.0f, contentTransformationAmount); + contentTransformationAmount = 1.0f - contentTransformationAmount; } else { fullTransitionAmount = 0.0f; transitionAmount = 0.0f; + contentTransformationAmount = 0.0f; } if (iconState != null && fullyInOrOut && !expandingAnimated && iconState.isLastExpandIcon) { iconState.isLastExpandIcon = false; iconState.customTransformHeight = NO_VALUE; } + + // Update the content transformation amount + if (view.isAboveShelf() || view.showingPulsing() + || (!isLastChild && iconState != null && !iconState.translateContent)) { + contentTransformationAmount = 0.0f; + } + view.setContentTransformationAmount(contentTransformationAmount, isLastChild); + + // Update the positioning of the icon updateIconPositioning(view, transitionAmount, fullTransitionAmount, - iconTransformDistance, scrolling, scrollingFast, expandingAnimated, isLastChild); + transformDistance, scrolling, scrollingFast, expandingAnimated, isLastChild); + return fullTransitionAmount; } + /** + * @return the location where the transformation into the shelf should start. + */ + private float calculateIconTransformationStart(ExpandableView view) { + View target = view.getShelfTransformationTarget(); + if (target == null) { + return view.getTranslationY(); + } + float start = view.getTranslationY() + view.getRelativeTopPadding(target); + + // Let's not start the transformation right at the icon but by the padding before it. + start -= view.getShelfIcon().getTop(); + return start; + } + + private void handleCustomTransformHeight(ExpandableView view, boolean expandingAnimated, + NotificationIconContainer.IconState iconState) { + if (iconState != null && expandingAnimated && mAmbientState.getScrollY() == 0 + && !mAmbientState.isOnKeyguard() && !iconState.isLastExpandIcon) { + // We are expanding animated. Because we switch to a linear interpolation in this case, + // the last icon may be stuck in between the shelf position and the notification + // position, which looks pretty bad. We therefore optimize this case by applying a + // shorter transition such that the icon is either fully in the notification or we clamp + // it into the shelf if it's close enough. + // We need to persist this, since after the expansion, the behavior should still be the + // same. + float position = mAmbientState.getIntrinsicPadding() + + mHostLayout.getPositionInLinearLayout(view); + int maxShelfStart = mMaxLayoutHeight - getIntrinsicHeight(); + if (position < maxShelfStart && position + view.getIntrinsicHeight() >= maxShelfStart + && view.getTranslationY() < position) { + iconState.isLastExpandIcon = true; + iconState.customTransformHeight = NO_VALUE; + // Let's check if we're close enough to snap into the shelf + boolean forceInShelf = mMaxLayoutHeight - getIntrinsicHeight() - position + < getIntrinsicHeight(); + if (!forceInShelf) { + // We are overlapping the shelf but not enough, so the icon needs to be + // repositioned + iconState.customTransformHeight = (int) (mMaxLayoutHeight + - getIntrinsicHeight() - position); + } + } + } + } + private void updateIconPositioning(ExpandableView view, float iconTransitionAmount, float fullTransitionAmount, float iconTransformDistance, boolean scrolling, boolean scrollingFast, boolean expandingAnimated, boolean isLastChild) { StatusBarIconView icon = view.getShelfIcon(); NotificationIconContainer.IconState iconState = getIconState(icon); - float contentTransformationAmount; if (iconState == null) { - contentTransformationAmount = iconTransitionAmount; + return; + } + boolean forceInShelf = + iconState.isLastExpandIcon && !iconState.hasCustomTransformHeight(); + boolean clampInShelf = iconTransitionAmount > 0.5f || isTargetClipped(view); + float clampedAmount = clampInShelf ? 1.0f : 0.0f; + if (iconTransitionAmount == clampedAmount) { + iconState.noAnimations = (scrollingFast || expandingAnimated) && !forceInShelf; + iconState.useFullTransitionAmount = iconState.noAnimations + || (!ICON_ANMATIONS_WHILE_SCROLLING && iconTransitionAmount == 0.0f + && scrolling); + iconState.useLinearTransitionAmount = !ICON_ANMATIONS_WHILE_SCROLLING + && iconTransitionAmount == 0.0f && !mAmbientState.isExpansionChanging(); + iconState.translateContent = mMaxLayoutHeight - getTranslationY() + - getIntrinsicHeight() > 0; + } + if (!forceInShelf && (scrollingFast || (expandingAnimated + && iconState.useFullTransitionAmount && !ViewState.isAnimatingY(icon)))) { + iconState.cancelAnimations(icon); + iconState.useFullTransitionAmount = true; + iconState.noAnimations = true; + } + if (iconState.hasCustomTransformHeight()) { + iconState.useFullTransitionAmount = true; + } + if (iconState.isLastExpandIcon) { + iconState.translateContent = false; + } + float transitionAmount; + if (mAmbientState.isHiddenAtAll() && !view.isInShelf()) { + transitionAmount = mAmbientState.isFullyHidden() ? 1 : 0; + } else if (isLastChild || !USE_ANIMATIONS_WHEN_OPENING + || iconState.useFullTransitionAmount + || iconState.useLinearTransitionAmount) { + transitionAmount = iconTransitionAmount; } else { - boolean forceInShelf = - iconState.isLastExpandIcon && !iconState.hasCustomTransformHeight(); - float clampedAmount = iconTransitionAmount > 0.5f ? 1.0f : 0.0f; - if (clampedAmount == fullTransitionAmount) { - iconState.noAnimations = (scrollingFast || expandingAnimated) && !forceInShelf; - iconState.useFullTransitionAmount = iconState.noAnimations - || (!ICON_ANMATIONS_WHILE_SCROLLING && fullTransitionAmount == 0.0f - && scrolling); - iconState.useLinearTransitionAmount = !ICON_ANMATIONS_WHILE_SCROLLING - && fullTransitionAmount == 0.0f && !mAmbientState.isExpansionChanging(); - iconState.translateContent = mMaxLayoutHeight - getTranslationY() - - getIntrinsicHeight() > 0; - } - if (!forceInShelf && (scrollingFast || (expandingAnimated - && iconState.useFullTransitionAmount && !ViewState.isAnimatingY(icon)))) { - iconState.cancelAnimations(icon); - iconState.useFullTransitionAmount = true; - iconState.noAnimations = true; - } - if (iconState.hasCustomTransformHeight()) { - iconState.useFullTransitionAmount = true; - } - if (iconState.isLastExpandIcon) { - iconState.translateContent = false; - } - float transitionAmount; - if (mAmbientState.isHiddenAtAll() && !view.isInShelf()) { - transitionAmount = mAmbientState.isFullyHidden() ? 1 : 0; - } else if (isLastChild || !USE_ANIMATIONS_WHEN_OPENING - || iconState.useFullTransitionAmount - || iconState.useLinearTransitionAmount) { - transitionAmount = iconTransitionAmount; - } else { - // We take the clamped position instead - transitionAmount = clampedAmount; - iconState.needsCannedAnimation = iconState.clampedAppearAmount != clampedAmount - && !mNoAnimationsInThisFrame; - } - iconState.iconAppearAmount = !USE_ANIMATIONS_WHEN_OPENING - || iconState.useFullTransitionAmount - ? fullTransitionAmount - : transitionAmount; - iconState.clampedAppearAmount = clampedAmount; - contentTransformationAmount = !view.isAboveShelf() && !view.showingPulsing() - && (isLastChild || iconState.translateContent) - ? iconTransitionAmount - : 0.0f; - setIconTransformationAmount(view, transitionAmount, iconTransformDistance, - clampedAmount != transitionAmount, isLastChild); + // We take the clamped position instead + transitionAmount = clampedAmount; + iconState.needsCannedAnimation = iconState.clampedAppearAmount != clampedAmount + && !mNoAnimationsInThisFrame; } - view.setContentTransformationAmount(contentTransformationAmount, isLastChild); + iconState.iconAppearAmount = !USE_ANIMATIONS_WHEN_OPENING + || iconState.useFullTransitionAmount + ? fullTransitionAmount + : transitionAmount; + iconState.clampedAppearAmount = clampedAmount; + setIconTransformationAmount(view, transitionAmount, iconTransformDistance, + clampedAmount != transitionAmount, isLastChild); + } + + private boolean isTargetClipped(ExpandableView view) { + View target = view.getShelfTransformationTarget(); + if (target == null) { + return false; + } + // We should never clip the target, let's instead put it into the shelf! + float endOfTarget = view.getTranslationY() + + view.getContentTranslation() + + view.getRelativeTopPadding(target) + + target.getHeight(); + + return endOfTarget >= getTranslationY() - mPaddingBetweenElements; } private void setIconTransformationAmount(ExpandableView view, @@ -686,40 +747,63 @@ public class NotificationShelf extends ActivatableNotificationView implements StatusBarIconView icon = row.getShelfIcon(); NotificationIconContainer.IconState iconState = getIconState(icon); + View rowIcon = row.getShelfTransformationTarget(); - View rowIcon = row.getNotificationIcon(); - float notificationIconPosition = row.getTranslationY() + row.getContentTranslation(); - boolean stayingInShelf = row.isInShelf() && !row.isTransformingIntoShelf(); - if (usingLinearInterpolation && !stayingInShelf) { - // If we interpolate from the notification position, this might lead to a slightly - // odd interpolation, since the notification position changes as well. Let's interpolate - // from a fixed distance. We can only do this if we don't animate and the icon is - // always in the interpolated positon. - notificationIconPosition = getTranslationY() - iconTransformDistance; - } + // Let's resolve the relative positions of the icons float notificationIconSize = 0.0f; int iconTopPadding; + int iconStartPadding; if (rowIcon != null) { iconTopPadding = row.getRelativeTopPadding(rowIcon); + iconStartPadding = row.getRelativeStartPadding(rowIcon); notificationIconSize = rowIcon.getHeight(); } else { iconTopPadding = mIconAppearTopPadding; + iconStartPadding = 0; } - notificationIconPosition += iconTopPadding; - float shelfIconPosition = getTranslationY() + icon.getTop(); - float iconSize = mAmbientState.isFullyHidden() ? mHiddenShelfIconSize : mIconSize; - shelfIconPosition += (icon.getHeight() - icon.getIconScale() * iconSize) / 2.0f; + + float shelfIconSize = mAmbientState.isFullyHidden() ? mHiddenShelfIconSize : mIconSize; + shelfIconSize = shelfIconSize * icon.getIconScale(); + + // Get the icon correctly positioned in Y + float notificationIconPositionY = row.getTranslationY() + row.getContentTranslation(); + float targetYPosition = 0; + boolean stayingInShelf = row.isInShelf() && !row.isTransformingIntoShelf(); + if (usingLinearInterpolation && !stayingInShelf) { + // If we interpolate from the notification position, this might lead to a slightly + // odd interpolation, since the notification position changes as well. + // Let's instead interpolate directly to the top left of the notification + targetYPosition = NotificationUtils.interpolate( + Math.min(notificationIconPositionY + mIconAppearTopPadding + - getTranslationY(), 0), + 0, + transitionAmount); + } + notificationIconPositionY += iconTopPadding; + float shelfIconPositionY = getTranslationY() + icon.getTop(); + shelfIconPositionY += (icon.getHeight() - shelfIconSize) / 2.0f; float iconYTranslation = NotificationUtils.interpolate( - notificationIconPosition - shelfIconPosition, - 0, + notificationIconPositionY - shelfIconPositionY, + targetYPosition, transitionAmount); - float shelfIconSize = iconSize * icon.getIconScale(); + + // Get the icon correctly positioned in X + // Even in RTL it's the left, since we're inverting the location in post + float shelfIconPositionX = icon.getLeft(); + shelfIconPositionX += (1.0f - icon.getIconScale()) * icon.getWidth() / 2.0f; + float iconXTranslation = NotificationUtils.interpolate( + iconStartPadding - shelfIconPositionX, + mShelfIcons.getActualPaddingStart(), + transitionAmount); + + // Let's handle the case that there's no Icon float alpha = 1.0f; boolean noIcon = !row.isShowingIcon(); if (noIcon) { // The view currently doesn't have an icon, lets transform it in! alpha = transitionAmount; notificationIconSize = shelfIconSize / 2.0f; + iconXTranslation = mShelfIcons.getActualPaddingStart(); } // The notification size is different from the size in the shelf / statusbar float newSize = NotificationUtils.interpolate(notificationIconSize, shelfIconSize, @@ -735,6 +819,7 @@ public class NotificationShelf extends ActivatableNotificationView implements } iconState.alpha = alpha; iconState.yTranslation = iconYTranslation; + iconState.xTranslation = iconXTranslation; if (stayingInShelf) { iconState.iconAppearAmount = 1.0f; iconState.alpha = 1.0f; @@ -751,7 +836,7 @@ public class NotificationShelf extends ActivatableNotificationView implements int backgroundColor = getBackgroundColorWithoutTint(); int shelfColor = icon.getContrastedStaticDrawableColor(backgroundColor); if (!noIcon && shelfColor != StatusBarIconView.NO_COLOR) { - int iconColor = row.getVisibleNotificationHeader().getOriginalIconColor(); + int iconColor = row.getOriginalIconColor(); shelfColor = NotificationUtils.interpolateColors(iconColor, shelfColor, iconState.iconAppearAmount); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index 12add8c8d9cc..8cf8a2299922 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -159,7 +159,7 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi private boolean mDismissed; private Runnable mOnDismissListener; private boolean mIncreasedSize; - private boolean mTintIcons = true; + private boolean mShowsConversation; public StatusBarIconView(Context context, String slot, StatusBarNotification sbn) { this(context, slot, sbn, false); @@ -613,7 +613,7 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi } private void updateIconColor() { - if (!mTintIcons) { + if (mShowsConversation) { setColorFilter(null); return; } @@ -913,7 +913,11 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi } private void updatePivot() { - setPivotX((1 - mIconScale) / 2.0f * getWidth()); + if (isLayoutRtl()) { + setPivotX((1 + mIconScale) / 2.0f * getWidth()); + } else { + setPivotX((1 - mIconScale) / 2.0f * getWidth()); + } setPivotY((getHeight() - mIconScale * getWidth()) / 2.0f); } @@ -960,18 +964,26 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi } /** - * Sets whether the icon should be tinted. If the state differs from the supplied setting, this + * Sets whether this icon shows a person and should be tinted. + * If the state differs from the supplied setting, this * will update the icon colors. * - * @param shouldTint Whether the icon should be tinted. + * @param showsConversation Whether the icon shows a person */ - public void setTintIcons(boolean shouldTint) { - if (mTintIcons != shouldTint) { - mTintIcons = shouldTint; + public void setShowsConversation(boolean showsConversation) { + if (mShowsConversation != showsConversation) { + mShowsConversation = showsConversation; updateIconColor(); } } + /** + * @return if this icon shows a conversation + */ + public boolean showsConversation() { + return mShowsConversation; + } + public interface OnVisibilityChangedListener { void onVisibilityChanged(int newVisibility); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java index fe565529db32..1f9d3af70b4f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java @@ -96,7 +96,7 @@ public class PropertyAnimator { || previousAnimator.getAnimatedFraction() == 0)) { animator.setStartDelay(properties.delay); } - AnimatorListenerAdapter listener = properties.getAnimationFinishListener(); + AnimatorListenerAdapter listener = properties.getAnimationFinishListener(property); if (listener != null) { animator.addListener(listener); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt index bb0fcafdb354..da8ad2da5c87 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt @@ -100,7 +100,7 @@ class IconManager @Inject constructor( // TODO: This doesn't belong here shelfIcon.setOnVisibilityChangedListener { newVisibility: Int -> if (entry.row != null) { - entry.row.setIconsVisible(newVisibility != View.VISIBLE) + entry.row.setShelfIconVisible(newVisibility == View.VISIBLE) } } @@ -258,7 +258,7 @@ class IconManager @Inject constructor( iconDescriptor: StatusBarIcon, iconView: StatusBarIconView ) { - iconView.setTintIcons(shouldTintIconView(entry, iconView)) + iconView.setShowsConversation(showsConversation(entry, iconView, iconDescriptor)) if (!iconView.set(iconDescriptor)) { throw InflationException("Couldn't create icon $iconDescriptor") } @@ -312,15 +312,22 @@ class IconManager @Inject constructor( } /** - * Determines if this icon should be tinted based on the sensitivity of the icon, its context - * and the user's indicated sensitivity preference. + * Determines if this icon shows a conversation based on the sensitivity of the icon, its + * context and the user's indicated sensitivity preference. If we're using a fall back icon + * of the small icon, we don't consider this to be showing a conversation * - * @param iconView The icon that should/should not be tinted. + * @param iconView The icon that shows the conversation. */ - private fun shouldTintIconView(entry: NotificationEntry, iconView: StatusBarIconView): Boolean { + private fun showsConversation( + entry: NotificationEntry, + iconView: StatusBarIconView, + iconDescriptor: StatusBarIcon + ): Boolean { val usedInSensitiveContext = iconView === entry.icons.shelfIcon || iconView === entry.icons.aodIcon - return !isImportantConversation(entry) || usedInSensitiveContext && entry.isSensitive + val isSmallIcon = iconDescriptor.icon.equals(entry.sbn.notification.smallIcon) + return isImportantConversation(entry) && !isSmallIcon + && (!usedInSensitiveContext || !entry.isSensitive) } private fun isImportantConversation(entry: NotificationEntry): Boolean { 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 0ff058b31527..7deabf79a6dd 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 @@ -31,6 +31,7 @@ import android.animation.ObjectAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.Notification; import android.app.NotificationChannel; import android.content.Context; import android.content.pm.PackageInfo; @@ -311,7 +312,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private boolean mHeadsupDisappearRunning; private View mChildAfterViewWhenDismissed; private View mGroupParentWhenDismissed; - private boolean mIconsVisible = true; + private boolean mShelfIconVisible; private boolean mAboveShelf; private Runnable mOnDismissRunnable; private boolean mIsLowPriority; @@ -587,17 +588,24 @@ public class ExpandableNotificationRow extends ActivatableNotificationView ContrastColorUtil.getInstance(mContext)); int color = StatusBarIconView.NO_COLOR; if (colorize) { - NotificationHeaderView header = getVisibleNotificationHeader(); - if (header != null) { - color = header.getOriginalIconColor(); - } else { - color = mEntry.getContrastedColor(mContext, mIsLowPriority && !isExpanded(), - getBackgroundColorWithoutTint()); - } + color = getOriginalIconColor(); } expandedIcon.setStaticDrawableColor(color); } + public int getOriginalIconColor() { + if (mIsSummaryWithChildren && !shouldShowPublic()) { + return mChildrenContainer.getVisibleHeader().getOriginalIconColor(); + } + int color = getShowingLayout().getOriginalIconColor(); + if (color != Notification.COLOR_INVALID) { + return color; + } else { + return mEntry.getContrastedColor(mContext, mIsLowPriority && !isExpanded(), + getBackgroundColorWithoutTint()); + } + } + public void setAboveShelfChangedListener(AboveShelfChangedListener aboveShelfChangedListener) { mAboveShelfChangedListener = aboveShelfChangedListener; } @@ -1452,12 +1460,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mOnDismissRunnable = onDismissRunnable; } - public View getNotificationIcon() { - NotificationHeaderView notificationHeader = getVisibleNotificationHeader(); - if (notificationHeader != null) { - return notificationHeader.getIcon(); + @Override + public View getShelfTransformationTarget() { + if (mIsSummaryWithChildren && !shouldShowPublic()) { + return mChildrenContainer.getVisibleHeader().getIcon(); } - return null; + return getShowingLayout().getShelfTransformationTarget(); } /** @@ -1467,34 +1475,15 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (areGutsExposed()) { return false; } - return getVisibleNotificationHeader() != null; - } - - /** - * Set how much this notification is transformed into an icon. - * - * @param contentTransformationAmount A value from 0 to 1 indicating how much we are transformed - * to the content away - * @param isLastChild is this the last child in the list. If true, then the transformation is - * different since it's content fades out. - */ - public void setContentTransformationAmount(float contentTransformationAmount, - boolean isLastChild) { - boolean changeTransformation = isLastChild != mIsLastChild; - changeTransformation |= mContentTransformationAmount != contentTransformationAmount; - mIsLastChild = isLastChild; - mContentTransformationAmount = contentTransformationAmount; - if (changeTransformation) { - updateContentTransformation(); - } + return getShelfTransformationTarget() != null; } /** * Set the icons to be visible of this notification. */ - public void setIconsVisible(boolean iconsVisible) { - if (iconsVisible != mIconsVisible) { - mIconsVisible = iconsVisible; + public void setShelfIconVisible(boolean iconVisible) { + if (iconVisible != mShelfIconVisible) { + mShelfIconVisible = iconVisible; updateIconVisibilities(); } } @@ -1531,37 +1520,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } private void updateIconVisibilities() { - boolean visible = isChildInGroup() || mIconsVisible; + // The shelficon is never hidden for children in groups + boolean visible = !isChildInGroup() && mShelfIconVisible; for (NotificationContentView l : mLayouts) { - l.setIconsVisible(visible); + l.setShelfIconVisible(visible); } if (mChildrenContainer != null) { - mChildrenContainer.setIconsVisible(visible); - } - } - - /** - * Get the relative top padding of a view relative to this view. This recursively walks up the - * hierarchy and does the corresponding measuring. - * - * @param view the view to the the padding for. The requested view has to be a child of this - * notification. - * @return the toppadding - */ - public int getRelativeTopPadding(View view) { - int topPadding = 0; - while (view.getParent() instanceof ViewGroup) { - topPadding += view.getTop(); - view = (View) view.getParent(); - if (view instanceof ExpandableNotificationRow) { - return topPadding; - } + mChildrenContainer.setShelfIconVisible(visible); } - return topPadding; - } - - public float getContentTranslation() { - return mPrivateLayout.getTranslationY(); } public void setIsLowPriority(boolean isLowPriority) { @@ -2134,7 +2100,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } @Override - public StatusBarIconView getShelfIcon() { + public @NonNull StatusBarIconView getShelfIcon() { return getEntry().getIcons().getShelfIcon(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index a9f72ff9ea62..ee3b753ab926 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -66,6 +66,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { protected boolean mIsLastChild; protected int mContentShift; private final ExpandableViewState mViewState; + private float mContentTranslation; public ExpandableView(Context context, AttributeSet attrs) { super(context, attrs); @@ -636,6 +637,56 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { } /** + * @return get the transformation target of the shelf, which usually is the icon + */ + public View getShelfTransformationTarget() { + return null; + } + + /** + * Get the relative top padding of a view relative to this view. This recursively walks up the + * hierarchy and does the corresponding measuring. + * + * @param view the view to get the padding for. The requested view has to be a child of this + * notification. + * @return the toppadding + */ + public int getRelativeTopPadding(View view) { + int topPadding = 0; + while (view.getParent() instanceof ViewGroup) { + topPadding += view.getTop(); + view = (View) view.getParent(); + if (view == this) { + return topPadding; + } + } + return topPadding; + } + + + /** + * Get the relative start padding of a view relative to this view. This recursively walks up the + * hierarchy and does the corresponding measuring. + * + * @param view the view to get the padding for. The requested view has to be a child of this + * notification. + * @return the start padding + */ + public int getRelativeStartPadding(View view) { + boolean isRtl = isLayoutRtl(); + int startPadding = 0; + while (view.getParent() instanceof ViewGroup) { + View parent = (View) view.getParent(); + startPadding += isRtl ? parent.getWidth() - view.getRight() : view.getLeft(); + view = parent; + if (view == this) { + return startPadding; + } + } + return startPadding; + } + + /** * Set how much this notification is transformed into the shelf. * * @param contentTransformationAmount A value from 0 to 1 indicating how much we are transformed @@ -665,6 +716,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { if (mIsLastChild) { translationY *= 0.4f; } + mContentTranslation = translationY; applyContentTransformation(contentAlpha, translationY); } @@ -709,6 +761,13 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { } /** + * return the amount that the content is translated + */ + public float getContentTranslation() { + return mContentTranslation; + } + + /** * A listener notifying when {@link #getActualHeight} changes. */ public interface OnHeightChangedListener { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 8b8a9012cbdc..9b9225e0bde0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -159,7 +159,7 @@ public class NotificationContentView extends FrameLayout { private int mContentHeightAtAnimationStart = UNDEFINED; private boolean mFocusOnVisibilityChange; private boolean mHeadsUpAnimatingAway; - private boolean mIconsVisible; + private boolean mShelfIconVisible; private int mClipBottomAmount; private boolean mIsLowPriority; private boolean mIsContentExpandable; @@ -1582,29 +1582,20 @@ public class NotificationContentView extends FrameLayout { mFocusOnVisibilityChange = true; } - public void setIconsVisible(boolean iconsVisible) { - mIconsVisible = iconsVisible; + public void setShelfIconVisible(boolean iconsVisible) { + mShelfIconVisible = iconsVisible; updateIconVisibilities(); } private void updateIconVisibilities() { if (mContractedWrapper != null) { - NotificationHeaderView header = mContractedWrapper.getNotificationHeader(); - if (header != null) { - header.getIcon().setForceHidden(!mIconsVisible); - } + mContractedWrapper.setShelfIconVisible(mShelfIconVisible); } if (mHeadsUpWrapper != null) { - NotificationHeaderView header = mHeadsUpWrapper.getNotificationHeader(); - if (header != null) { - header.getIcon().setForceHidden(!mIconsVisible); - } + mHeadsUpWrapper.setShelfIconVisible(mShelfIconVisible); } if (mExpandedWrapper != null) { - NotificationHeaderView header = mExpandedWrapper.getNotificationHeader(); - if (header != null) { - header.getIcon().setForceHidden(!mIconsVisible); - } + mExpandedWrapper.setShelfIconVisible(mShelfIconVisible); } } @@ -1835,4 +1826,23 @@ public class NotificationContentView extends FrameLayout { public RemoteInputView getExpandedRemoteInput() { return mExpandedRemoteInput; } + + /** + * @return get the transformation target of the shelf, which usually is the icon + */ + public View getShelfTransformationTarget() { + NotificationViewWrapper visibleWrapper = getVisibleWrapper(mVisibleType); + if (visibleWrapper != null) { + return visibleWrapper.getShelfTransformationTarget(); + } + return null; + } + + public int getOriginalIconColor() { + NotificationViewWrapper visibleWrapper = getVisibleWrapper(mVisibleType); + if (visibleWrapper != null) { + return visibleWrapper.getOriginalIconColor(); + } + return Notification.COLOR_INVALID; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt index 593de23c58de..13e7fe5cd6d8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt @@ -18,7 +18,9 @@ package com.android.systemui.statusbar.notification.row.wrapper import android.content.Context import android.view.View +import android.view.View.GONE import android.view.ViewGroup +import com.android.internal.widget.CachingIconView import com.android.internal.widget.ConversationLayout import com.android.internal.widget.MessagingLinearLayout import com.android.systemui.R @@ -44,7 +46,7 @@ class NotificationConversationTemplateViewWrapper constructor( ) private val conversationLayout: ConversationLayout = view as ConversationLayout - private lateinit var conversationIcon: View + private lateinit var conversationIcon: CachingIconView private lateinit var conversationBadgeBg: View private lateinit var expandButton: View private lateinit var expandButtonContainer: View @@ -132,6 +134,32 @@ class NotificationConversationTemplateViewWrapper constructor( ) } + override fun setShelfIconVisible(visible: Boolean) { + if (conversationLayout.isImportantConversation) { + if (conversationIcon.visibility != GONE) { + conversationIcon.setForceHidden(visible); + // We don't want the small icon to be hidden by the extended wrapper, as force + // hiding the conversationIcon will already do that via its listener. + return; + } + } + super.setShelfIconVisible(visible) + } + + override fun getShelfTransformationTarget(): View? { + if (conversationLayout.isImportantConversation) { + if (conversationIcon.visibility != GONE) { + return conversationIcon + } else { + // A notification with a fallback icon was set to important. Currently + // the transformation doesn't work for these and needs to be fixed. In the meantime + // those are using the icon. + return super.getShelfTransformationTarget(); + } + } + return super.getShelfTransformationTarget() + } + override fun setRemoteInputVisible(visible: Boolean) = conversationLayout.showHistoricMessages(visible) 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 a44ad3d7f5c1..7808a4b2dc74 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 @@ -29,6 +29,7 @@ import android.view.animation.PathInterpolator; import android.widget.ImageView; import android.widget.TextView; +import com.android.internal.widget.CachingIconView; import com.android.internal.widget.NotificationExpandButton; import com.android.systemui.Interpolators; import com.android.systemui.R; @@ -54,7 +55,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { protected int mColor; - private ImageView mIcon; + private CachingIconView mIcon; private NotificationExpandButton mExpandButton; protected NotificationHeaderView mNotificationHeader; private TextView mHeaderText; @@ -199,6 +200,22 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { } @Override + public int getOriginalIconColor() { + return mIcon.getOriginalIconColor(); + } + + @Override + public View getShelfTransformationTarget() { + return mIcon; + } + + @Override + public void setShelfIconVisible(boolean visible) { + super.setShelfIconVisible(visible); + mIcon.setForceHidden(visible); + } + + @Override public TransformState getCurrentState(int fadingView) { return mTransformationHelper.getCurrentState(fadingView); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java index 46d7d9374290..e4fb2f7c42d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.row.wrapper; import android.annotation.ColorInt; +import android.annotation.Nullable; import android.app.Notification; import android.content.Context; import android.content.res.Configuration; @@ -230,6 +231,22 @@ public abstract class NotificationViewWrapper implements TransformableView { return null; } + public int getOriginalIconColor() { + return Notification.COLOR_INVALID; + } + + /** + * @return get the transformation target of the shelf, which usually is the icon + */ + public @Nullable View getShelfTransformationTarget() { + return null; + } + + /** + * Set the shelf icon to be visible and hide our own icons. + */ + public void setShelfIconVisible(boolean shelfIconVisible) {} + public int getHeaderTranslation(boolean forceNoHeader) { return 0; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java index 87a3cc9d2db8..112d48c115c2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java @@ -16,12 +16,15 @@ package com.android.systemui.statusbar.notification.stack; +import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.util.ArrayMap; import android.util.Property; import android.view.View; import android.view.animation.Interpolator; +import java.util.function.Consumer; + /** * Properties for a View animation */ @@ -29,7 +32,7 @@ public class AnimationProperties { public long duration; public long delay; private ArrayMap<Property, Interpolator> mInterpolatorMap; - private AnimatorListenerAdapter mAnimatorListenerAdapter; + private Consumer<Property> mAnimationEndAction; /** * @return an animation filter for this animation. @@ -44,14 +47,32 @@ public class AnimationProperties { } /** - * @return a listener that should be run whenever any property finished its animation + * @return a listener that will be added for a given property during its animation. */ - public AnimatorListenerAdapter getAnimationFinishListener() { - return mAnimatorListenerAdapter; + public AnimatorListenerAdapter getAnimationFinishListener(Property property) { + if (mAnimationEndAction == null) { + return null; + } + Consumer<Property> endAction = mAnimationEndAction; + return new AnimatorListenerAdapter() { + private boolean mCancelled; + + @Override + public void onAnimationCancel(Animator animation) { + mCancelled = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + if (!mCancelled) { + endAction.accept(property); + } + } + }; } - public AnimationProperties setAnimationFinishListener(AnimatorListenerAdapter listener) { - mAnimatorListenerAdapter = listener; + public AnimationProperties setAnimationEndAction(Consumer<Property> listener) { + mAnimationEndAction = listener; return this; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java index 72ef7f9572a4..628c4e258e50 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java @@ -263,7 +263,8 @@ public class ExpandableViewState extends ViewState { || previousAnimator.getAnimatedFraction() == 0)) { animator.setStartDelay(properties.delay); } - AnimatorListenerAdapter listener = properties.getAnimationFinishListener(); + AnimatorListenerAdapter listener = properties.getAnimationFinishListener( + null /* no property for this height */); if (listener != null) { animator.addListener(listener); } @@ -343,7 +344,8 @@ public class ExpandableViewState extends ViewState { || previousAnimator.getAnimatedFraction() == 0)) { animator.setStartDelay(properties.delay); } - AnimatorListenerAdapter listener = properties.getAnimationFinishListener(); + AnimatorListenerAdapter listener = properties.getAnimationFinishListener( + null /* no property for top inset */); if (listener != null) { animator.addListener(listener); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java index 2c17764799a5..3d0bf3f4c1c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java @@ -1187,18 +1187,18 @@ public class NotificationChildrenContainer extends ViewGroup { return 0; } - public void setIconsVisible(boolean iconsVisible) { + public void setShelfIconVisible(boolean iconVisible) { if (mNotificationHeaderWrapper != null) { NotificationHeaderView header = mNotificationHeaderWrapper.getNotificationHeader(); if (header != null) { - header.getIcon().setForceHidden(!iconsVisible); + header.getIcon().setForceHidden(iconVisible); } } if (mNotificationHeaderWrapperLowPriority != null) { NotificationHeaderView header = mNotificationHeaderWrapperLowPriority.getNotificationHeader(); if (header != null) { - header.getIcon().setForceHidden(!iconsVisible); + header.getIcon().setForceHidden(iconVisible); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java index 14442e346db4..77850826a5e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java @@ -104,7 +104,7 @@ public class StackStateAnimator { } @Override - public AnimatorListenerAdapter getAnimationFinishListener() { + public AnimatorListenerAdapter getAnimationFinishListener(Property property) { return getGlobalAnimationFinishedListener(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java index b00068cd2445..3da4e321c54d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java @@ -393,7 +393,7 @@ public class ViewState implements Dumpable { || previousAnimator.getAnimatedFraction() == 0)) { animator.setStartDelay(properties.delay); } - AnimatorListenerAdapter listener = properties.getAnimationFinishListener(); + AnimatorListenerAdapter listener = properties.getAnimationFinishListener(View.ALPHA); if (listener != null) { animator.addListener(listener); } @@ -450,7 +450,8 @@ public class ViewState implements Dumpable { || previousAnimator.getAnimatedFraction() == 0)) { animator.setStartDelay(properties.delay); } - AnimatorListenerAdapter listener = properties.getAnimationFinishListener(); + AnimatorListenerAdapter listener = properties.getAnimationFinishListener( + View.TRANSLATION_Z); if (listener != null) { animator.addListener(listener); } @@ -515,7 +516,8 @@ public class ViewState implements Dumpable { || previousAnimator.getAnimatedFraction() == 0)) { animator.setStartDelay(properties.delay); } - AnimatorListenerAdapter listener = properties.getAnimationFinishListener(); + AnimatorListenerAdapter listener = properties.getAnimationFinishListener( + View.TRANSLATION_X); if (listener != null) { animator.addListener(listener); } @@ -580,7 +582,8 @@ public class ViewState implements Dumpable { || previousAnimator.getAnimatedFraction() == 0)) { animator.setStartDelay(properties.delay); } - AnimatorListenerAdapter listener = properties.getAnimationFinishListener(); + AnimatorListenerAdapter listener = properties.getAnimationFinishListener( + View.TRANSLATION_Y); if (listener != null) { animator.addListener(listener); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index a53ce9bb2014..07eaaa187fbe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -19,6 +19,8 @@ package com.android.systemui.statusbar.phone; import static com.android.systemui.statusbar.phone.HeadsUpAppearanceController.CONTENT_FADE_DELAY; import static com.android.systemui.statusbar.phone.HeadsUpAppearanceController.CONTENT_FADE_DURATION; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.content.Context; import android.content.res.Configuration; import android.graphics.Canvas; @@ -27,7 +29,9 @@ import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.Icon; import android.util.AttributeSet; +import android.util.Property; import android.view.View; +import android.view.animation.Interpolator; import androidx.collection.ArrayMap; @@ -42,6 +46,7 @@ import com.android.systemui.statusbar.notification.stack.ViewState; import java.util.ArrayList; import java.util.HashMap; +import java.util.function.Consumer; /** * A container for notification icons. It handles overflowing icons properly and positions them @@ -69,7 +74,10 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { }.setDuration(200); private static final AnimationProperties ICON_ANIMATION_PROPERTIES = new AnimationProperties() { - private AnimationFilter mAnimationFilter = new AnimationFilter().animateY().animateAlpha() + private AnimationFilter mAnimationFilter = new AnimationFilter() + .animateX() + .animateY() + .animateAlpha() .animateScale(); @Override @@ -77,8 +85,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { return mAnimationFilter; } - }.setDuration(CANNED_ANIMATION_DURATION) - .setCustomInterpolator(View.TRANSLATION_Y, Interpolators.ICON_OVERSHOT); + }.setDuration(CANNED_ANIMATION_DURATION); /** * Temporary AnimationProperties to avoid unnecessary allocations. @@ -272,7 +279,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { super.onViewAdded(child); boolean isReplacingIcon = isReplacingIcon(child); if (!mChangingViewPositions) { - IconState v = new IconState(); + IconState v = new IconState(child); if (isReplacingIcon) { v.justAdded = false; v.justReplaced = true; @@ -388,7 +395,12 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { for (int i = 0; i < childCount; i++) { View view = getChildAt(i); IconState iconState = mIconStates.get(view); - iconState.xTranslation = translationX; + if (iconState.iconAppearAmount == 1.0f) { + // We only modify the xTranslation if it's fully inside of the container + // since during the transition to the shelf, the translations are controlled + // from the outside + iconState.xTranslation = translationX; + } if (mFirstVisibleIconState == null) { mFirstVisibleIconState = iconState; } @@ -499,7 +511,10 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { return mActualPaddingEnd; } - private float getActualPaddingStart() { + /** + * @return the actual startPadding of this view + */ + public float getActualPaddingStart() { if (mActualPaddingStart == NO_VALUE) { return getPaddingStart(); } @@ -692,6 +707,20 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { public boolean noAnimations; public boolean isLastExpandIcon; public int customTransformHeight = NO_VALUE; + private final View mView; + + private final Consumer<Property> mCannedAnimationEndListener; + + public IconState(View child) { + mView = child; + mCannedAnimationEndListener = (property) -> { + // If we finished animating out of the shelf + if (property == View.TRANSLATION_Y && iconAppearAmount == 0.0f + && mView.getVisibility() == VISIBLE) { + mView.setVisibility(INVISIBLE); + } + }; + } @Override public void applyToView(View view) { @@ -729,6 +758,14 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { ICON_ANIMATION_PROPERTIES.getAnimationFilter()); sTempProperties.resetCustomInterpolators(); sTempProperties.combineCustomInterpolators(ICON_ANIMATION_PROPERTIES); + Interpolator interpolator; + if (icon.showsConversation()) { + interpolator = Interpolators.ICON_OVERSHOT_LESS; + } else { + interpolator = Interpolators.ICON_OVERSHOT; + } + sTempProperties.setCustomInterpolator(View.TRANSLATION_Y, interpolator); + sTempProperties.setAnimationEndAction(mCannedAnimationEndListener); if (animationProperties != null) { animationFilter.combineFilter(animationProperties.getAnimationFilter()); sTempProperties.combineCustomInterpolators(animationProperties); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 5588c24f2fd6..98ba6e5b88a0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -379,14 +379,6 @@ public class NotificationPanelViewController extends PanelViewController { private Runnable mPanelAlphaEndAction; private float mBottomAreaShadeAlpha; private final ValueAnimator mBottomAreaShadeAlphaAnimator; - private AnimatorListenerAdapter mAnimatorListenerAdapter = new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (mPanelAlphaEndAction != null) { - mPanelAlphaEndAction.run(); - } - } - }; private final AnimatableProperty mPanelAlphaAnimator = AnimatableProperty.from("panelAlpha", NotificationPanelView::setPanelAlphaInternal, NotificationPanelView::getCurrentPanelAlpha, @@ -396,8 +388,11 @@ public class NotificationPanelViewController extends PanelViewController { new AnimationProperties().setDuration(150).setCustomInterpolator( mPanelAlphaAnimator.getProperty(), Interpolators.ALPHA_OUT); private final AnimationProperties mPanelAlphaInPropertiesAnimator = - new AnimationProperties().setDuration(200).setAnimationFinishListener( - mAnimatorListenerAdapter).setCustomInterpolator( + new AnimationProperties().setDuration(200).setAnimationEndAction((property) -> { + if (mPanelAlphaEndAction != null) { + mPanelAlphaEndAction.run(); + } + }).setCustomInterpolator( mPanelAlphaAnimator.getProperty(), Interpolators.ALPHA_IN); private final NotificationEntryManager mEntryManager; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java index 6359234fa6ba..57278e32d553 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java @@ -93,7 +93,7 @@ public class PropertyAnimatorTest extends SysuiTestCase { } @Override - public AnimatorListenerAdapter getAnimationFinishListener() { + public AnimatorListenerAdapter getAnimationFinishListener(Property property) { return mFinishListener; } }.setDuration(200); |