Merge "Add importance ring animation on convo priority change" into rvc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
80d5c61da5
@@ -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() {
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
android:color="#ffffff"/>
|
||||
|
||||
<size
|
||||
android:width="26dp"
|
||||
android:height="26dp"/>
|
||||
android:width="20dp"
|
||||
android:height="20dp"/>
|
||||
</shape>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user