Merge "Add importance ring animation on convo priority change" into rvc-dev am: 80d5c61da5

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/11739864

Change-Id: I0010f9c8552cdbc8913e995491ed1354d08d9cb1
This commit is contained in:
TreeHugger Robot
2020-06-09 15:34:32 +00:00
committed by Automerger Merge Worker
9 changed files with 156 additions and 43 deletions

View File

@@ -21,6 +21,10 @@ import static com.android.internal.widget.MessagingGroup.IMAGE_DISPLAY_LOCATION_
import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_IN;
import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_OUT;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.annotation.AttrRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -36,6 +40,7 @@ import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.Parcelable;
@@ -93,8 +98,12 @@ public class ConversationLayout extends FrameLayout
public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
public static final Interpolator FAST_OUT_LINEAR_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
public static final Interpolator OVERSHOOT = new PathInterpolator(0.4f, 0f, 0.2f, 1.4f);
public static final OnLayoutChangeListener MESSAGING_PROPERTY_ANIMATOR
= new MessagingPropertyAnimator();
public static final int IMPORTANCE_ANIM_GROW_DURATION = 250;
public static final int IMPORTANCE_ANIM_SHRINK_DURATION = 200;
public static final int IMPORTANCE_ANIM_SHRINK_DELAY = 25;
private List<MessagingMessage> mMessages = new ArrayList<>();
private List<MessagingMessage> mHistoricMessages = new ArrayList<>();
private MessagingLinearLayout mMessagingLinearLayout;
@@ -331,14 +340,74 @@ public class ConversationLayout extends FrameLayout
mNameReplacement = nameReplacement;
}
/**
* Sets this conversation as "important", adding some additional UI treatment.
*/
/** Sets this conversation as "important", adding some additional UI treatment. */
@RemotableViewMethod
public void setIsImportantConversation(boolean isImportantConversation) {
setIsImportantConversation(isImportantConversation, false);
}
/** @hide **/
public void setIsImportantConversation(boolean isImportantConversation, boolean animate) {
mImportantConversation = isImportantConversation;
mImportanceRingView.setVisibility(isImportantConversation
&& mIcon.getVisibility() != GONE ? VISIBLE : GONE);
mImportanceRingView.setVisibility(isImportantConversation && mIcon.getVisibility() != GONE
? VISIBLE : GONE);
if (animate && isImportantConversation) {
GradientDrawable ring = (GradientDrawable) mImportanceRingView.getDrawable();
ring.mutate();
GradientDrawable bg = (GradientDrawable) mConversationIconBadgeBg.getDrawable();
bg.mutate();
int ringColor = getResources()
.getColor(R.color.conversation_important_highlight);
int standardThickness = getResources()
.getDimensionPixelSize(R.dimen.importance_ring_stroke_width);
int largeThickness = getResources()
.getDimensionPixelSize(R.dimen.importance_ring_anim_max_stroke_width);
int standardSize = getResources().getDimensionPixelSize(
R.dimen.importance_ring_size);
int baseSize = standardSize - standardThickness * 2;
int bgSize = getResources()
.getDimensionPixelSize(R.dimen.conversation_icon_size_badged);
ValueAnimator.AnimatorUpdateListener animatorUpdateListener = animation -> {
int strokeWidth = Math.round((float) animation.getAnimatedValue());
ring.setStroke(strokeWidth, ringColor);
int newSize = baseSize + strokeWidth * 2;
ring.setSize(newSize, newSize);
mImportanceRingView.invalidate();
};
ValueAnimator growAnimation = ValueAnimator.ofFloat(0, largeThickness);
growAnimation.setInterpolator(LINEAR_OUT_SLOW_IN);
growAnimation.setDuration(IMPORTANCE_ANIM_GROW_DURATION);
growAnimation.addUpdateListener(animatorUpdateListener);
ValueAnimator shrinkAnimation =
ValueAnimator.ofFloat(largeThickness, standardThickness);
shrinkAnimation.setDuration(IMPORTANCE_ANIM_SHRINK_DURATION);
shrinkAnimation.setStartDelay(IMPORTANCE_ANIM_SHRINK_DELAY);
shrinkAnimation.setInterpolator(OVERSHOOT);
shrinkAnimation.addUpdateListener(animatorUpdateListener);
shrinkAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
// Shrink the badge bg so that it doesn't peek behind the animation
bg.setSize(baseSize, baseSize);
mConversationIconBadgeBg.invalidate();
}
@Override
public void onAnimationEnd(Animator animation) {
// Reset bg back to normal size
bg.setSize(bgSize, bgSize);
mConversationIconBadgeBg.invalidate();
}
});
AnimatorSet anims = new AnimatorSet();
anims.playSequentially(growAnimation, shrinkAnimation);
anims.start();
}
}
public boolean isImportantConversation() {

View File

@@ -22,7 +22,7 @@
android:color="#ffffff"/>
<size
android:width="26dp"
android:height="26dp"/>
android:width="20dp"
android:height="20dp"/>
</shape>

View File

@@ -16,17 +16,18 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid
android:color="@color/transparent"/>
android:shape="oval"
>
<solid android:color="@color/transparent" />
<stroke
android:color="@color/conversation_important_highlight"
android:width="2dp"/>
android:width="@dimen/importance_ring_stroke_width"
/>
<size
android:width="26dp"
android:height="26dp"/>
android:width="@dimen/importance_ring_size"
android:height="@dimen/importance_ring_size"
/>
</shape>

View File

@@ -38,6 +38,8 @@
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:layout_gravity="top|center_horizontal"
>
@@ -63,13 +65,17 @@
android:layout_height="@dimen/conversation_icon_size_badged"
android:layout_marginLeft="@dimen/conversation_badge_side_margin"
android:layout_marginTop="@dimen/conversation_badge_side_margin"
android:clipChildren="false"
android:clipToPadding="false"
>
<com.android.internal.widget.CachingIconView
android:id="@+id/conversation_icon_badge_bg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:src="@drawable/conversation_badge_background"
android:forceHasOverlappingRendering="false"
android:scaleType="center"
/>
<com.android.internal.widget.CachingIconView
android:id="@+id/icon"
@@ -81,11 +87,14 @@
/>
<com.android.internal.widget.CachingIconView
android:id="@+id/conversation_icon_badge_ring"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/conversation_badge_ring"
android:visibility="gone"
android:forceHasOverlappingRendering="false"
android:clipToPadding="false"
android:scaleType="center"
/>
</FrameLayout>
</FrameLayout>

View File

@@ -720,6 +720,12 @@
<dimen name="conversation_face_pile_protection_width_expanded">1dp</dimen>
<!-- The padding of the expanded message container-->
<dimen name="expanded_group_conversation_message_padding">14dp</dimen>
<!-- The stroke width of the ring used to visually mark a conversation as important -->
<dimen name="importance_ring_stroke_width">2dp</dimen>
<!-- The maximum stroke width used for the animation shown when a conversation is marked as important -->
<dimen name="importance_ring_anim_max_stroke_width">10dp</dimen>
<!-- The size of the importance ring -->
<dimen name="importance_ring_size">20dp</dimen>
<!-- The top padding of the conversation icon container in the regular state-->
<dimen name="conversation_icon_container_top_padding">9dp</dimen>

View File

@@ -3903,6 +3903,12 @@
<java-symbol type="array" name="config_defaultImperceptibleKillingExemptionPkgs" />
<java-symbol type="array" name="config_defaultImperceptibleKillingExemptionProcStates" />
<java-symbol type="color" name="conversation_important_highlight" />
<java-symbol type="dimen" name="importance_ring_stroke_width" />
<java-symbol type="dimen" name="importance_ring_anim_max_stroke_width" />
<java-symbol type="dimen" name="importance_ring_size" />
<java-symbol type="dimen" name="conversation_icon_size_badged" />
<java-symbol type="id" name="header_icon_container" />
<java-symbol type="attr" name="notificationHeaderTextAppearance" />
<java-symbol type="string" name="conversation_single_line_name_display" />

View File

@@ -19,13 +19,16 @@ package com.android.systemui.statusbar.notification
import android.app.Notification
import android.content.Context
import android.content.pm.LauncherApps
import android.os.Handler
import android.service.notification.NotificationListenerService.Ranking
import android.service.notification.NotificationListenerService.RankingMap
import com.android.internal.statusbar.NotificationVisibility
import com.android.internal.widget.ConversationLayout
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.NotificationContentView
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
import com.android.systemui.statusbar.phone.NotificationGroupManager
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
@@ -62,7 +65,8 @@ class ConversationNotificationProcessor @Inject constructor(
class ConversationNotificationManager @Inject constructor(
private val notificationEntryManager: NotificationEntryManager,
private val notificationGroupManager: NotificationGroupManager,
private val context: Context
private val context: Context,
@Main private val mainHandler: Handler
) {
// Need this state to be thread safe, since it's accessed from the ui thread
// (NotificationEntryListener) and a bg thread (NotificationContentInflater)
@@ -72,32 +76,41 @@ class ConversationNotificationManager @Inject constructor(
init {
notificationEntryManager.addNotificationEntryListener(object : NotificationEntryListener {
override fun onNotificationRankingUpdated(rankingMap: RankingMap) {
fun getLayouts(view: NotificationContentView) =
sequenceOf(view.contractedChild, view.expandedChild, view.headsUpChild)
val ranking = Ranking()
states.keys.asSequence()
val activeConversationEntries = states.keys.asSequence()
.mapNotNull { notificationEntryManager.getActiveNotificationUnfiltered(it) }
.forEach { entry ->
if (rankingMap.getRanking(entry.sbn.key, ranking) &&
ranking.isConversation) {
val important = ranking.channel.isImportantConversation
var changed = false
entry.row?.layouts?.asSequence()
?.flatMap(::getLayouts)
?.mapNotNull { it as? ConversationLayout }
?.forEach {
if (important != it.isImportantConversation) {
it.setIsImportantConversation(important)
changed = true
}
}
if (changed) {
notificationGroupManager.updateIsolation(entry)
}
for (entry in activeConversationEntries) {
if (rankingMap.getRanking(entry.sbn.key, ranking) && ranking.isConversation) {
val important = ranking.channel.isImportantConversation
val layouts = entry.row?.layouts?.asSequence()
?.flatMap(::getLayouts)
?.mapNotNull { it as? ConversationLayout }
?: emptySequence()
var changed = false
for (layout in layouts) {
if (important == layout.isImportantConversation) {
continue
}
changed = true
if (important && entry.isMarkedForUserTriggeredMovement) {
// delay this so that it doesn't animate in until after
// the notif has been moved in the shade
mainHandler.postDelayed({
layout.setIsImportantConversation(
important, true /* animate */)
}, IMPORTANCE_ANIMATION_DELAY.toLong())
} else {
layout.setIsImportantConversation(important)
}
}
if (changed) {
notificationGroupManager.updateIsolation(entry)
}
}
}
}
override fun onEntryInflated(entry: NotificationEntry) {
@@ -177,9 +190,16 @@ class ConversationNotificationManager @Inject constructor(
private fun resetBadgeUi(row: ExpandableNotificationRow): Unit =
(row.layouts?.asSequence() ?: emptySequence())
.flatMap { layout -> layout.allViews.asSequence()}
.flatMap { layout -> layout.allViews.asSequence() }
.mapNotNull { view -> view as? ConversationLayout }
.forEach { convoLayout -> convoLayout.setUnreadCount(0) }
private data class ConversationState(val unreadCount: Int, val notification: Notification)
companion object {
private const val IMPORTANCE_ANIMATION_DELAY =
StackStateAnimator.ANIMATION_DURATION_STANDARD +
StackStateAnimator.ANIMATION_DURATION_PRIORITY_CHANGE +
100
}
}

View File

@@ -53,7 +53,6 @@ import android.transition.TransitionManager;
import android.transition.TransitionSet;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Slog;
import android.view.LayoutInflater;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
@@ -508,10 +507,10 @@ public class NotificationConversationInfo extends LinearLayout implements
mBgHandler.post(
new UpdateChannelRunnable(mINotificationManager, mPackageName,
mAppUid, mSelectedAction, mNotificationChannel));
mMainHandler.postDelayed(() -> {
mEntry.markForUserTriggeredMovement(true);
mVisualStabilityManager.temporarilyAllowReordering();
}, StackStateAnimator.ANIMATION_DURATION_STANDARD);
mEntry.markForUserTriggeredMovement(true);
mMainHandler.postDelayed(
mVisualStabilityManager::temporarilyAllowReordering,
StackStateAnimator.ANIMATION_DURATION_STANDARD);
}
private boolean shouldShowPriorityOnboarding() {

View File

@@ -262,7 +262,10 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
stack.push(mView);
while (!stack.isEmpty()) {
View child = stack.pop();
if (child instanceof ImageView) {
if (child instanceof ImageView
// Skip the importance ring for conversations, disabled cropping is needed for
// its animation
&& child.getId() != com.android.internal.R.id.conversation_icon_badge_ring) {
((ImageView) child).setCropToPadding(true);
} else if (child instanceof ViewGroup){
ViewGroup group = (ViewGroup) child;