diff options
| author | 2020-03-31 20:02:01 +0000 | |
|---|---|---|
| committer | 2020-03-31 20:02:01 +0000 | |
| commit | 9bad85af2b646fa3c6dcacf85f5f02b901e8cc29 (patch) | |
| tree | 4321ad67cb0eda0ff483a26662ed0fc5973a956e | |
| parent | b259d151ce0fbd8f075d557e1d2d1c95d910d74b (diff) | |
| parent | e027da23c040f393aa9d5089afa33a9b0dcd0449 (diff) | |
Merge changes I0d79642d,If8756ac8,I29e73897,Ia8c5121a,I533b8f06 into rvc-dev
* changes:
Indented the conversation action list
Fixed some issues where conversation badges would not be visible
Improved the animations of the conversation badges
Important conversations now also transform into the shelf
Adapted Shelf algorithm to also use conversation icons
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); |