Merge changes from topic "b215583339-AnimBigPic" into tm-qpr-dev

* changes:
  Animates drawables in Notifications
  Adds flag for Animated Big Picture notifications
This commit is contained in:
Alexander Roederer
2023-01-18 13:14:43 +00:00
committed by Android (Google) Code Review
15 changed files with 727 additions and 20 deletions

View File

@@ -102,6 +102,10 @@ object Flags {
val FILTER_UNSEEN_NOTIFS_ON_KEYGUARD = val FILTER_UNSEEN_NOTIFS_ON_KEYGUARD =
unreleasedFlag(254647461, "filter_unseen_notifs_on_keyguard", teamfood = true) unreleasedFlag(254647461, "filter_unseen_notifs_on_keyguard", teamfood = true)
// TODO(b/263414400): Tracking Bug
@JvmField
val NOTIFICATION_ANIMATE_BIG_PICTURE = unreleasedFlag(120, "notification_animate_big_picture")
// 200 - keyguard/lockscreen // 200 - keyguard/lockscreen
// ** Flag retired ** // ** Flag retired **
// public static final BooleanFlag KEYGUARD_LAYOUT = // public static final BooleanFlag KEYGUARD_LAYOUT =

View File

@@ -79,6 +79,8 @@ import com.android.internal.widget.CallLayout;
import com.android.systemui.R; import com.android.systemui.R;
import com.android.systemui.animation.Interpolators; import com.android.systemui.animation.Interpolators;
import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -177,6 +179,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private PeopleNotificationIdentifier mPeopleNotificationIdentifier; private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
private Optional<BubblesManager> mBubblesManagerOptional; private Optional<BubblesManager> mBubblesManagerOptional;
private MetricsLogger mMetricsLogger; private MetricsLogger mMetricsLogger;
private FeatureFlags mFeatureFlags;
private int mIconTransformContentShift; private int mIconTransformContentShift;
private int mMaxHeadsUpHeightBeforeN; private int mMaxHeadsUpHeightBeforeN;
private int mMaxHeadsUpHeightBeforeP; private int mMaxHeadsUpHeightBeforeP;
@@ -277,7 +280,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private boolean mChildIsExpanding; private boolean mChildIsExpanding;
private boolean mJustClicked; private boolean mJustClicked;
private boolean mIconAnimationRunning; private boolean mAnimationRunning;
private boolean mShowNoBackground; private boolean mShowNoBackground;
private ExpandableNotificationRow mNotificationParent; private ExpandableNotificationRow mNotificationParent;
private OnExpandClickListener mOnExpandClickListener; private OnExpandClickListener mOnExpandClickListener;
@@ -451,10 +454,26 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return mPublicLayout; return mPublicLayout;
} }
public void setIconAnimationRunning(boolean running) { /**
for (NotificationContentView l : mLayouts) { * Sets animations running in the layouts of this row, including public, private, and children.
setIconAnimationRunning(running, l); * @param running whether the animations should be started running or stopped.
*/
public void setAnimationRunning(boolean running) {
// Sets animations running in the private/public layouts.
if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE)) {
for (NotificationContentView l : mLayouts) {
if (l != null) {
l.setContentAnimationRunning(running);
setIconAnimationRunning(running, l);
}
}
} else {
for (NotificationContentView l : mLayouts) {
setIconAnimationRunning(running, l);
}
} }
// For groups summaries with children, we want to set the children containers
// animating as well.
if (mIsSummaryWithChildren) { if (mIsSummaryWithChildren) {
NotificationViewWrapper viewWrapper = mChildrenContainer.getNotificationViewWrapper(); NotificationViewWrapper viewWrapper = mChildrenContainer.getNotificationViewWrapper();
if (viewWrapper != null) { if (viewWrapper != null) {
@@ -468,12 +487,18 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mChildrenContainer.getAttachedChildren(); mChildrenContainer.getAttachedChildren();
for (int i = 0; i < notificationChildren.size(); i++) { for (int i = 0; i < notificationChildren.size(); i++) {
ExpandableNotificationRow child = notificationChildren.get(i); ExpandableNotificationRow child = notificationChildren.get(i);
child.setIconAnimationRunning(running); child.setAnimationRunning(running);
} }
} }
mIconAnimationRunning = running; mAnimationRunning = running;
} }
/**
* Starts or stops animations of the icons in all potential content views (regardless of
* whether they're contracted, expanded, etc).
*
* @param running whether to start or stop the icon's animation.
*/
private void setIconAnimationRunning(boolean running, NotificationContentView layout) { private void setIconAnimationRunning(boolean running, NotificationContentView layout) {
if (layout != null) { if (layout != null) {
View contractedChild = layout.getContractedChild(); View contractedChild = layout.getContractedChild();
@@ -485,16 +510,29 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
} }
} }
/**
* Starts or stops animations of the icon in the provided view's icon and right icon.
*
* @param running whether to start or stop the icon's animation.
* @param child the view with the icon to start or stop.
*/
private void setIconAnimationRunningForChild(boolean running, View child) { private void setIconAnimationRunningForChild(boolean running, View child) {
if (child != null) { if (child != null) {
ImageView icon = child.findViewById(com.android.internal.R.id.icon); ImageView icon = child.findViewById(com.android.internal.R.id.icon);
setIconRunning(icon, running); setImageViewAnimationRunning(icon, running);
ImageView rightIcon = child.findViewById(com.android.internal.R.id.right_icon); ImageView rightIcon = child.findViewById(com.android.internal.R.id.right_icon);
setIconRunning(rightIcon, running); setImageViewAnimationRunning(rightIcon, running);
} }
} }
private void setIconRunning(ImageView imageView, boolean running) { /**
* Starts or stops the animation of a provided image view if it's an AnimationDrawable or an
* AnimatedVectorDrawable.
*
* @param imageView the image view on which to start/stop animation.
* @param running whether to start or stop the view's animation.
*/
private void setImageViewAnimationRunning(ImageView imageView, boolean running) {
if (imageView != null) { if (imageView != null) {
Drawable drawable = imageView.getDrawable(); Drawable drawable = imageView.getDrawable();
if (drawable instanceof AnimationDrawable) { if (drawable instanceof AnimationDrawable) {
@@ -561,8 +599,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mChildrenContainer.recreateNotificationHeader(mExpandClickListener, isConversation()); mChildrenContainer.recreateNotificationHeader(mExpandClickListener, isConversation());
mChildrenContainer.onNotificationUpdated(); mChildrenContainer.onNotificationUpdated();
} }
if (mIconAnimationRunning) { if (mAnimationRunning) {
setIconAnimationRunning(true); setAnimationRunning(true);
} }
if (mLastChronometerRunning) { if (mLastChronometerRunning) {
setChronometerRunning(true); setChronometerRunning(true);
@@ -1038,7 +1076,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
notifyHeightChanged(false /* needsAnimation */); notifyHeightChanged(false /* needsAnimation */);
} }
if (pinned) { if (pinned) {
setIconAnimationRunning(true); setAnimationRunning(true);
mExpandedWhenPinned = false; mExpandedWhenPinned = false;
} else if (mExpandedWhenPinned) { } else if (mExpandedWhenPinned) {
setUserExpanded(true); setUserExpanded(true);
@@ -1627,6 +1665,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
); );
} }
/**
* Constructs an ExpandableNotificationRow.
* @param context context passed to image resolver
* @param attrs attributes used to initialize parent view
*/
public ExpandableNotificationRow(Context context, AttributeSet attrs) { public ExpandableNotificationRow(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
mImageResolver = new NotificationInlineImageResolver(context, mImageResolver = new NotificationInlineImageResolver(context,
@@ -1662,7 +1705,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
NotificationGutsManager gutsManager, NotificationGutsManager gutsManager,
MetricsLogger metricsLogger, MetricsLogger metricsLogger,
SmartReplyConstants smartReplyConstants, SmartReplyConstants smartReplyConstants,
SmartReplyController smartReplyController) { SmartReplyController smartReplyController,
FeatureFlags featureFlags) {
mEntry = entry; mEntry = entry;
mAppName = appName; mAppName = appName;
if (mMenuRow == null) { if (mMenuRow == null) {
@@ -1697,6 +1741,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mBubblesManagerOptional = bubblesManagerOptional; mBubblesManagerOptional = bubblesManagerOptional;
mNotificationGutsManager = gutsManager; mNotificationGutsManager = gutsManager;
mMetricsLogger = metricsLogger; mMetricsLogger = metricsLogger;
mFeatureFlags = featureFlags;
} }
private void initDimens() { private void initDimens() {
@@ -3588,11 +3633,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@VisibleForTesting @VisibleForTesting
protected void setPrivateLayout(NotificationContentView privateLayout) { protected void setPrivateLayout(NotificationContentView privateLayout) {
mPrivateLayout = privateLayout; mPrivateLayout = privateLayout;
mLayouts = new NotificationContentView[]{mPrivateLayout, mPublicLayout};
} }
@VisibleForTesting @VisibleForTesting
protected void setPublicLayout(NotificationContentView publicLayout) { protected void setPublicLayout(NotificationContentView publicLayout) {
mPublicLayout = publicLayout; mPublicLayout = publicLayout;
mLayouts = new NotificationContentView[]{mPrivateLayout, mPublicLayout};
} }
/** /**

View File

@@ -219,7 +219,8 @@ public class ExpandableNotificationRowController implements NotifViewController
mNotificationGutsManager, mNotificationGutsManager,
mMetricsLogger, mMetricsLogger,
mSmartReplyConstants, mSmartReplyConstants,
mSmartReplyController mSmartReplyController,
mFeatureFlags
); );
mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
if (mAllowLongPress) { if (mAllowLongPress) {

View File

@@ -184,6 +184,8 @@ public class NotificationContentView extends FrameLayout implements Notification
private boolean mRemoteInputVisible; private boolean mRemoteInputVisible;
private int mUnrestrictedContentHeight; private int mUnrestrictedContentHeight;
private boolean mContentAnimating;
public NotificationContentView(Context context, AttributeSet attrs) { public NotificationContentView(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
mHybridGroupManager = new HybridGroupManager(getContext()); mHybridGroupManager = new HybridGroupManager(getContext());
@@ -2129,8 +2131,49 @@ public class NotificationContentView extends FrameLayout implements Notification
return false; return false;
} }
/**
* Starts and stops animations in the underlying views.
* Avoids restarting the animations by checking whether they're already running first.
* Return value is used for testing.
*
* @param running whether to start animations running, or stop them.
* @return true if the state of animations changed.
*/
public boolean setContentAnimationRunning(boolean running) {
boolean stateChangeRequired = (running != mContentAnimating);
if (stateChangeRequired) {
// Starts or stops the animations in the potential views.
if (mContractedWrapper != null) {
mContractedWrapper.setAnimationsRunning(running);
}
if (mExpandedWrapper != null) {
mExpandedWrapper.setAnimationsRunning(running);
}
if (mHeadsUpWrapper != null) {
mHeadsUpWrapper.setAnimationsRunning(running);
}
// Updates the state tracker.
mContentAnimating = running;
return true;
}
return false;
}
private static class RemoteInputViewData { private static class RemoteInputViewData {
@Nullable RemoteInputView mView; @Nullable RemoteInputView mView;
@Nullable RemoteInputViewController mController; @Nullable RemoteInputViewController mController;
} }
@VisibleForTesting
protected void setContractedWrapper(NotificationViewWrapper contractedWrapper) {
mContractedWrapper = contractedWrapper;
}
@VisibleForTesting
protected void setExpandedWrapper(NotificationViewWrapper expandedWrapper) {
mExpandedWrapper = expandedWrapper;
}
@VisibleForTesting
protected void setHeadsUpWrapper(NotificationViewWrapper headsUpWrapper) {
mHeadsUpWrapper = headsUpWrapper;
}
} }

View File

@@ -18,11 +18,15 @@ package com.android.systemui.statusbar.notification.row.wrapper;
import android.app.Notification; import android.app.Notification;
import android.content.Context; import android.content.Context;
import android.graphics.drawable.AnimatedImageDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon; import android.graphics.drawable.Icon;
import android.os.Bundle; import android.os.Bundle;
import android.service.notification.StatusBarNotification; import android.service.notification.StatusBarNotification;
import android.view.View; import android.view.View;
import com.android.internal.R;
import com.android.internal.widget.BigPictureNotificationImageView;
import com.android.systemui.statusbar.notification.ImageTransformState; import com.android.systemui.statusbar.notification.ImageTransformState;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -31,6 +35,8 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
*/ */
public class NotificationBigPictureTemplateViewWrapper extends NotificationTemplateViewWrapper { public class NotificationBigPictureTemplateViewWrapper extends NotificationTemplateViewWrapper {
private BigPictureNotificationImageView mImageView;
protected NotificationBigPictureTemplateViewWrapper(Context ctx, View view, protected NotificationBigPictureTemplateViewWrapper(Context ctx, View view,
ExpandableNotificationRow row) { ExpandableNotificationRow row) {
super(ctx, view, row); super(ctx, view, row);
@@ -39,9 +45,14 @@ public class NotificationBigPictureTemplateViewWrapper extends NotificationTempl
@Override @Override
public void onContentUpdated(ExpandableNotificationRow row) { public void onContentUpdated(ExpandableNotificationRow row) {
super.onContentUpdated(row); super.onContentUpdated(row);
resolveViews();
updateImageTag(row.getEntry().getSbn()); updateImageTag(row.getEntry().getSbn());
} }
private void resolveViews() {
mImageView = mView.findViewById(R.id.big_picture);
}
private void updateImageTag(StatusBarNotification sbn) { private void updateImageTag(StatusBarNotification sbn) {
final Bundle extras = sbn.getNotification().extras; final Bundle extras = sbn.getNotification().extras;
Icon bigLargeIcon = extras.getParcelable(Notification.EXTRA_LARGE_ICON_BIG, Icon.class); Icon bigLargeIcon = extras.getParcelable(Notification.EXTRA_LARGE_ICON_BIG, Icon.class);
@@ -54,4 +65,25 @@ public class NotificationBigPictureTemplateViewWrapper extends NotificationTempl
mRightIcon.setTag(ImageTransformState.ICON_TAG, getLargeIcon(sbn.getNotification())); mRightIcon.setTag(ImageTransformState.ICON_TAG, getLargeIcon(sbn.getNotification()));
} }
} }
/**
* Starts or stops the animations in any drawables contained in this BigPicture Notification.
*
* @param running Whether the animations should be set to run.
*/
@Override
public void setAnimationsRunning(boolean running) {
if (mImageView == null) {
return;
}
Drawable d = mImageView.getDrawable();
if (d instanceof AnimatedImageDrawable) {
AnimatedImageDrawable animatedImageDrawable = (AnimatedImageDrawable) d;
if (running) {
animatedImageDrawable.start();
} else {
animatedImageDrawable.stop();
}
}
}
} }

View File

@@ -17,16 +17,20 @@
package com.android.systemui.statusbar.notification.row.wrapper package com.android.systemui.statusbar.notification.row.wrapper
import android.content.Context import android.content.Context
import android.graphics.drawable.AnimatedImageDrawable
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import com.android.internal.widget.CachingIconView import com.android.internal.widget.CachingIconView
import com.android.internal.widget.ConversationLayout import com.android.internal.widget.ConversationLayout
import com.android.internal.widget.MessagingGroup
import com.android.internal.widget.MessagingImageMessage
import com.android.internal.widget.MessagingLinearLayout import com.android.internal.widget.MessagingLinearLayout
import com.android.systemui.R import com.android.systemui.R
import com.android.systemui.statusbar.notification.NotificationFadeAware import com.android.systemui.statusbar.notification.NotificationFadeAware
import com.android.systemui.statusbar.notification.NotificationUtils import com.android.systemui.statusbar.notification.NotificationUtils
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.wrapper.NotificationMessagingTemplateViewWrapper.setCustomImageMessageTransform import com.android.systemui.statusbar.notification.row.wrapper.NotificationMessagingTemplateViewWrapper.setCustomImageMessageTransform
import com.android.systemui.util.children
/** /**
* Wraps a notification containing a conversation template * Wraps a notification containing a conversation template
@@ -49,6 +53,7 @@ class NotificationConversationTemplateViewWrapper constructor(
private lateinit var expandBtn: View private lateinit var expandBtn: View
private lateinit var expandBtnContainer: View private lateinit var expandBtnContainer: View
private lateinit var imageMessageContainer: ViewGroup private lateinit var imageMessageContainer: ViewGroup
private lateinit var messageContainers: ArrayList<MessagingGroup>
private lateinit var messagingLinearLayout: MessagingLinearLayout private lateinit var messagingLinearLayout: MessagingLinearLayout
private lateinit var conversationTitleView: View private lateinit var conversationTitleView: View
private lateinit var importanceRing: View private lateinit var importanceRing: View
@@ -60,6 +65,7 @@ class NotificationConversationTemplateViewWrapper constructor(
private fun resolveViews() { private fun resolveViews() {
messagingLinearLayout = conversationLayout.messagingLinearLayout messagingLinearLayout = conversationLayout.messagingLinearLayout
imageMessageContainer = conversationLayout.imageMessageContainer imageMessageContainer = conversationLayout.imageMessageContainer
messageContainers = conversationLayout.messagingGroups
with(conversationLayout) { with(conversationLayout) {
conversationIconContainer = conversationIconContainer =
requireViewById(com.android.internal.R.id.conversation_icon_container) requireViewById(com.android.internal.R.id.conversation_icon_container)
@@ -146,4 +152,26 @@ class NotificationConversationTemplateViewWrapper constructor(
NotificationFadeAware.setLayerTypeForFaded(expandBtn, faded) NotificationFadeAware.setLayerTypeForFaded(expandBtn, faded)
NotificationFadeAware.setLayerTypeForFaded(conversationIconContainer, faded) NotificationFadeAware.setLayerTypeForFaded(conversationIconContainer, faded)
} }
// Starts or stops the animations in any drawables contained in this Conversation Notification.
override fun setAnimationsRunning(running: Boolean) {
// We apply to both the child message containers in a conversation group,
// and the top level image message container.
val containers = messageContainers.asSequence().map { it.messageContainer } +
sequenceOf(imageMessageContainer)
val drawables =
containers
.flatMap { it.children }
.mapNotNull { child ->
(child as? MessagingImageMessage)?.let { imageMessage ->
imageMessage.drawable as? AnimatedImageDrawable
}
}
drawables.toSet().forEach {
when {
running -> it.start()
!running -> it.stop()
}
}
}
} }

View File

@@ -17,9 +17,13 @@
package com.android.systemui.statusbar.notification.row.wrapper; package com.android.systemui.statusbar.notification.row.wrapper;
import android.content.Context; import android.content.Context;
import android.graphics.drawable.AnimatedImageDrawable;
import android.graphics.drawable.Drawable;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.android.internal.widget.MessagingGroup;
import com.android.internal.widget.MessagingImageMessage;
import com.android.internal.widget.MessagingLayout; import com.android.internal.widget.MessagingLayout;
import com.android.internal.widget.MessagingLinearLayout; import com.android.internal.widget.MessagingLinearLayout;
import com.android.systemui.R; import com.android.systemui.R;
@@ -127,4 +131,40 @@ public class NotificationMessagingTemplateViewWrapper extends NotificationTempla
} }
return super.getMinLayoutHeight(); return super.getMinLayoutHeight();
} }
/**
* Starts or stops the animations in any drawables contained in this Messaging Notification.
*
* @param running Whether the animations should be set to run.
*/
@Override
public void setAnimationsRunning(boolean running) {
if (mMessagingLayout == null) {
return;
}
for (MessagingGroup group : mMessagingLayout.getMessagingGroups()) {
for (int i = 0; i < group.getMessageContainer().getChildCount(); i++) {
View view = group.getMessageContainer().getChildAt(i);
// We only need to set animations in MessagingImageMessages.
if (!(view instanceof MessagingImageMessage)) {
continue;
}
MessagingImageMessage imageMessage =
(com.android.internal.widget.MessagingImageMessage) view;
// If the drawable isn't an AnimatedImageDrawable, we can't set it to animate.
Drawable d = imageMessage.getDrawable();
if (!(d instanceof AnimatedImageDrawable)) {
continue;
}
AnimatedImageDrawable animatedImageDrawable = (AnimatedImageDrawable) d;
if (running) {
animatedImageDrawable.start();
} else {
animatedImageDrawable.stop();
}
}
}
}
} }

View File

@@ -403,4 +403,12 @@ public abstract class NotificationViewWrapper implements TransformableView {
NotificationFadeAware.setLayerTypeForFaded(getIcon(), faded); NotificationFadeAware.setLayerTypeForFaded(getIcon(), faded);
NotificationFadeAware.setLayerTypeForFaded(getExpandButton(), faded); NotificationFadeAware.setLayerTypeForFaded(getExpandButton(), faded);
} }
/**
* Starts or stops the animations in any drawables contained in this Notification.
*
* @param running Whether the animations should be set to run.
*/
public void setAnimationsRunning(boolean running) {
}
} }

View File

@@ -3132,7 +3132,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private void updateAnimationState(boolean running, View child) { private void updateAnimationState(boolean running, View child) {
if (child instanceof ExpandableNotificationRow) { if (child instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) child; ExpandableNotificationRow row = (ExpandableNotificationRow) child;
row.setIconAnimationRunning(running); row.setAnimationRunning(running);
} }
} }

View File

@@ -44,16 +44,23 @@ import static org.mockito.Mockito.when;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationChannel; import android.app.NotificationChannel;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
import android.testing.AndroidTestingRunner; import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper; import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper; import android.testing.TestableLooper.RunWithLooper;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.view.View; import android.view.View;
import android.widget.ImageView;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import com.android.internal.R; import com.android.internal.R;
import com.android.internal.widget.CachingIconView;
import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestCase;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener; import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
@@ -61,6 +68,7 @@ import com.android.systemui.statusbar.notification.FeedbackIcon;
import com.android.systemui.statusbar.notification.SourceType; import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener; import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer; import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
import org.junit.Assert; import org.junit.Assert;
@@ -72,6 +80,7 @@ import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule; import org.mockito.junit.MockitoRule;
import java.util.Arrays;
import java.util.List; import java.util.List;
@SmallTest @SmallTest
@@ -96,6 +105,9 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
mDependency, mDependency,
TestableLooper.get(this)); TestableLooper.get(this));
mNotificationTestHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL); mNotificationTestHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL);
FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags();
fakeFeatureFlags.set(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE, true);
mNotificationTestHelper.setFeatureFlags(fakeFeatureFlags);
// create a standard private notification row // create a standard private notification row
Notification normalNotif = mNotificationTestHelper.createNotification(); Notification normalNotif = mNotificationTestHelper.createNotification();
normalNotif.publicVersion = null; normalNotif.publicVersion = null;
@@ -559,4 +571,123 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
Assert.assertEquals(1f, mGroupRow.getBottomRoundness(), 0.001f); Assert.assertEquals(1f, mGroupRow.getBottomRoundness(), 0.001f);
Assert.assertEquals(1f, mGroupRow.getChildrenContainer().getBottomRoundness(), 0.001f); Assert.assertEquals(1f, mGroupRow.getChildrenContainer().getBottomRoundness(), 0.001f);
} }
@Test
public void testSetContentAnimationRunning_Run() throws Exception {
// Create views for the notification row.
NotificationContentView publicLayout = mock(NotificationContentView.class);
mNotifRow.setPublicLayout(publicLayout);
NotificationContentView privateLayout = mock(NotificationContentView.class);
mNotifRow.setPrivateLayout(privateLayout);
mNotifRow.setAnimationRunning(true);
verify(publicLayout, times(1)).setContentAnimationRunning(true);
verify(privateLayout, times(1)).setContentAnimationRunning(true);
}
@Test
public void testSetContentAnimationRunning_Stop() {
// Create views for the notification row.
NotificationContentView publicLayout = mock(NotificationContentView.class);
mNotifRow.setPublicLayout(publicLayout);
NotificationContentView privateLayout = mock(NotificationContentView.class);
mNotifRow.setPrivateLayout(privateLayout);
mNotifRow.setAnimationRunning(false);
verify(publicLayout, times(1)).setContentAnimationRunning(false);
verify(privateLayout, times(1)).setContentAnimationRunning(false);
}
@Test
public void testSetContentAnimationRunningInGroupChild_Run() {
// Creates parent views on mGroupRow.
NotificationContentView publicParentLayout = mock(NotificationContentView.class);
mGroupRow.setPublicLayout(publicParentLayout);
NotificationContentView privateParentLayout = mock(NotificationContentView.class);
mGroupRow.setPrivateLayout(privateParentLayout);
// Create child views on mNotifRow.
NotificationContentView publicChildLayout = mock(NotificationContentView.class);
mNotifRow.setPublicLayout(publicChildLayout);
NotificationContentView privateChildLayout = mock(NotificationContentView.class);
mNotifRow.setPrivateLayout(privateChildLayout);
when(mNotifRow.isGroupExpanded()).thenReturn(true);
setMockChildrenContainer(mGroupRow, mNotifRow);
mGroupRow.setAnimationRunning(true);
verify(publicParentLayout, times(1)).setContentAnimationRunning(true);
verify(privateParentLayout, times(1)).setContentAnimationRunning(true);
// The child layouts should be started too.
verify(publicChildLayout, times(1)).setContentAnimationRunning(true);
verify(privateChildLayout, times(1)).setContentAnimationRunning(true);
}
@Test
public void testSetIconAnimationRunningGroup_Run() {
// Create views for a group row.
NotificationContentView publicParentLayout = mock(NotificationContentView.class);
mGroupRow.setPublicLayout(publicParentLayout);
NotificationContentView privateParentLayout = mock(NotificationContentView.class);
mGroupRow.setPrivateLayout(privateParentLayout);
when(mGroupRow.isGroupExpanded()).thenReturn(true);
// Sets up mNotifRow as a child ExpandableNotificationRow.
NotificationContentView publicChildLayout = mock(NotificationContentView.class);
mNotifRow.setPublicLayout(publicChildLayout);
NotificationContentView privateChildLayout = mock(NotificationContentView.class);
mNotifRow.setPrivateLayout(privateChildLayout);
when(mNotifRow.isGroupExpanded()).thenReturn(true);
NotificationChildrenContainer mockContainer =
setMockChildrenContainer(mGroupRow, mNotifRow);
// Mock the children view wrappers, and give them each an icon.
NotificationViewWrapper mockViewWrapper = mock(NotificationViewWrapper.class);
when(mockContainer.getNotificationViewWrapper()).thenReturn(mockViewWrapper);
CachingIconView mockIcon = mock(CachingIconView.class);
when(mockViewWrapper.getIcon()).thenReturn(mockIcon);
NotificationViewWrapper mockLowPriorityViewWrapper = mock(NotificationViewWrapper.class);
when(mockContainer.getLowPriorityViewWrapper()).thenReturn(mockLowPriorityViewWrapper);
CachingIconView mockLowPriorityIcon = mock(CachingIconView.class);
when(mockLowPriorityViewWrapper.getIcon()).thenReturn(mockLowPriorityIcon);
// Give the icon image views drawables, so we can make sure they animate.
// We use both AnimationDrawables and AnimatedVectorDrawables to ensure both work.
AnimationDrawable drawable = mock(AnimationDrawable.class);
AnimatedVectorDrawable vectorDrawable = mock(AnimatedVectorDrawable.class);
setDrawableIconsInImageView(mockIcon, drawable, vectorDrawable);
AnimationDrawable lowPriDrawable = mock(AnimationDrawable.class);
AnimatedVectorDrawable lowPriVectorDrawable = mock(AnimatedVectorDrawable.class);
setDrawableIconsInImageView(mockLowPriorityIcon, lowPriDrawable, lowPriVectorDrawable);
mGroupRow.setAnimationRunning(true);
verify(drawable, times(1)).start();
verify(vectorDrawable, times(1)).start();
verify(lowPriDrawable, times(1)).start();
verify(lowPriVectorDrawable, times(1)).start();
}
private void setDrawableIconsInImageView(CachingIconView icon, Drawable iconDrawable,
Drawable rightIconDrawable) {
ImageView iconView = mock(ImageView.class);
when(icon.findViewById(com.android.internal.R.id.icon)).thenReturn(iconView);
when(iconView.getDrawable()).thenReturn(iconDrawable);
ImageView rightIconView = mock(ImageView.class);
when(icon.findViewById(com.android.internal.R.id.right_icon)).thenReturn(rightIconView);
when(rightIconView.getDrawable()).thenReturn(rightIconDrawable);
}
private NotificationChildrenContainer setMockChildrenContainer(
ExpandableNotificationRow parentRow, ExpandableNotificationRow childRow) {
List<ExpandableNotificationRow> rowList = Arrays.asList(childRow);
NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class);
when(mockContainer.getNotificationChildCount()).thenReturn(1);
when(mockContainer.getAttachedChildren()).thenReturn(rowList);
parentRow.setChildrenContainer(mockContainer);
return mockContainer;
}
} }

View File

@@ -34,9 +34,12 @@ import com.android.systemui.media.dialog.MediaOutputDialogFactory
import com.android.systemui.statusbar.notification.FeedbackIcon import com.android.systemui.statusbar.notification.FeedbackIcon
import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper
import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.whenever
import junit.framework.Assert.assertEquals import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@@ -44,6 +47,7 @@ import org.mockito.Mock
import org.mockito.Mockito.doReturn import org.mockito.Mockito.doReturn
import org.mockito.Mockito.never import org.mockito.Mockito.never
import org.mockito.Mockito.spy import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations.initMocks import org.mockito.MockitoAnnotations.initMocks
@@ -305,6 +309,86 @@ class NotificationContentViewTest : SysuiTestCase() {
assertEquals(0, getMarginBottom(actionListMarginTarget)) assertEquals(0, getMarginBottom(actionListMarginTarget))
} }
@Test
fun onSetAnimationRunning() {
// Given: contractedWrapper, enpandedWrapper, and headsUpWrapper being set
val mockContracted = mock<NotificationViewWrapper>()
val mockExpanded = mock<NotificationViewWrapper>()
val mockHeadsUp = mock<NotificationViewWrapper>()
view.setContractedWrapper(mockContracted)
view.setExpandedWrapper(mockExpanded)
view.setHeadsUpWrapper(mockHeadsUp)
// When: we set content animation running.
assertTrue(view.setContentAnimationRunning(true))
// Then: contractedChild, expandedChild, and headsUpChild should have setAnimationsRunning
// called on them.
verify(mockContracted, times(1)).setAnimationsRunning(true)
verify(mockExpanded, times(1)).setAnimationsRunning(true)
verify(mockHeadsUp, times(1)).setAnimationsRunning(true)
// When: we set content animation running true _again_.
assertFalse(view.setContentAnimationRunning(true))
// Then: the children should not have setAnimationRunning called on them again.
// Verify counts number of calls so far on the object, so these still register as 1.
verify(mockContracted, times(1)).setAnimationsRunning(true)
verify(mockExpanded, times(1)).setAnimationsRunning(true)
verify(mockHeadsUp, times(1)).setAnimationsRunning(true)
}
@Test
fun onSetAnimationStopped() {
// Given: contractedWrapper, expandedWrapper, and headsUpWrapper being set
val mockContracted = mock<NotificationViewWrapper>()
val mockExpanded = mock<NotificationViewWrapper>()
val mockHeadsUp = mock<NotificationViewWrapper>()
view.setContractedWrapper(mockContracted)
view.setExpandedWrapper(mockExpanded)
view.setHeadsUpWrapper(mockHeadsUp)
// When: we set content animation running.
assertTrue(view.setContentAnimationRunning(true))
// Then: contractedChild, expandedChild, and headsUpChild should have setAnimationsRunning
// called on them.
verify(mockContracted).setAnimationsRunning(true)
verify(mockExpanded).setAnimationsRunning(true)
verify(mockHeadsUp).setAnimationsRunning(true)
// When: we set content animation running false, the state changes, so the function
// returns true.
assertTrue(view.setContentAnimationRunning(false))
// Then: the children have their animations stopped.
verify(mockContracted).setAnimationsRunning(false)
verify(mockExpanded).setAnimationsRunning(false)
verify(mockHeadsUp).setAnimationsRunning(false)
}
@Test
fun onSetAnimationInitStopped() {
// Given: contractedWrapper, expandedWrapper, and headsUpWrapper being set
val mockContracted = mock<NotificationViewWrapper>()
val mockExpanded = mock<NotificationViewWrapper>()
val mockHeadsUp = mock<NotificationViewWrapper>()
view.setContractedWrapper(mockContracted)
view.setExpandedWrapper(mockExpanded)
view.setHeadsUpWrapper(mockHeadsUp)
// When: we try to stop the animations before they've been started.
assertFalse(view.setContentAnimationRunning(false))
// Then: the children should not have setAnimationRunning called on them again.
verify(mockContracted, never()).setAnimationsRunning(false)
verify(mockExpanded, never()).setAnimationsRunning(false)
verify(mockHeadsUp, never()).setAnimationsRunning(false)
}
private fun createMockContainingNotification(notificationEntry: NotificationEntry) = private fun createMockContainingNotification(notificationEntry: NotificationEntry) =
mock<ExpandableNotificationRow>().apply { mock<ExpandableNotificationRow>().apply {
whenever(this.entry).thenReturn(notificationEntry) whenever(this.entry).thenReturn(notificationEntry)

View File

@@ -53,6 +53,7 @@ import com.android.internal.logging.UiEventLogger;
import com.android.systemui.TestableDependency; import com.android.systemui.TestableDependency;
import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.controls.util.MediaFeatureFlag; import com.android.systemui.media.controls.util.MediaFeatureFlag;
import com.android.systemui.media.dialog.MediaOutputDialogFactory; import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -130,6 +131,7 @@ public class NotificationTestHelper {
public final OnUserInteractionCallback mOnUserInteractionCallback; public final OnUserInteractionCallback mOnUserInteractionCallback;
public final Runnable mFutureDismissalRunnable; public final Runnable mFutureDismissalRunnable;
private @InflationFlag int mDefaultInflationFlags; private @InflationFlag int mDefaultInflationFlags;
private FeatureFlags mFeatureFlags;
public NotificationTestHelper( public NotificationTestHelper(
Context context, Context context,
@@ -193,12 +195,17 @@ public class NotificationTestHelper {
mFutureDismissalRunnable = mock(Runnable.class); mFutureDismissalRunnable = mock(Runnable.class);
when(mOnUserInteractionCallback.registerFutureDismissal(any(), anyInt())) when(mOnUserInteractionCallback.registerFutureDismissal(any(), anyInt()))
.thenReturn(mFutureDismissalRunnable); .thenReturn(mFutureDismissalRunnable);
mFeatureFlags = mock(FeatureFlags.class);
} }
public void setDefaultInflationFlags(@InflationFlag int defaultInflationFlags) { public void setDefaultInflationFlags(@InflationFlag int defaultInflationFlags) {
mDefaultInflationFlags = defaultInflationFlags; mDefaultInflationFlags = defaultInflationFlags;
} }
public void setFeatureFlags(FeatureFlags featureFlags) {
mFeatureFlags = featureFlags;
}
public ExpandableNotificationRowLogger getMockLogger() { public ExpandableNotificationRowLogger getMockLogger() {
return mMockLogger; return mMockLogger;
} }
@@ -561,7 +568,8 @@ public class NotificationTestHelper {
mock(NotificationGutsManager.class), mock(NotificationGutsManager.class),
mock(MetricsLogger.class), mock(MetricsLogger.class),
mock(SmartReplyConstants.class), mock(SmartReplyConstants.class),
mock(SmartReplyController.class)); mock(SmartReplyController.class),
mFeatureFlags);
row.setAboveShelfChangedListener(aboveShelf -> { }); row.setAboveShelfChangedListener(aboveShelf -> { });
mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags); mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags);

View File

@@ -16,11 +16,12 @@
package com.android.systemui.statusbar.notification.row.wrapper; package com.android.systemui.statusbar.notification.row.wrapper;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.verify;
import android.app.Notification; import android.app.Notification;
import android.content.Context; import android.graphics.drawable.AnimatedImageDrawable;
import android.graphics.drawable.Icon; import android.graphics.drawable.Icon;
import android.os.Bundle; import android.os.Bundle;
import android.testing.AndroidTestingRunner; import android.testing.AndroidTestingRunner;
@@ -28,11 +29,11 @@ import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper; import android.testing.TestableLooper.RunWithLooper;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import com.android.internal.R;
import com.android.internal.widget.BigPictureNotificationImageView;
import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -73,4 +74,38 @@ public class NotificationBigPictureTemplateViewWrapperTest extends SysuiTestCase
Notification.EXTRA_LARGE_ICON_BIG, new Bundle()); Notification.EXTRA_LARGE_ICON_BIG, new Bundle());
wrapper.onContentUpdated(mRow); wrapper.onContentUpdated(mRow);
} }
@Test
public void setAnimationsRunning_Run() {
BigPictureNotificationImageView imageView = mView.findViewById(R.id.big_picture);
AnimatedImageDrawable mockDrawable = mock(AnimatedImageDrawable.class);
assertNotNull(imageView);
imageView.setImageDrawable(mockDrawable);
NotificationViewWrapper wrapper = new NotificationBigPictureTemplateViewWrapper(mContext,
mView, mRow);
// Required to re-initialize the imageView to the imageView created above.
wrapper.onContentUpdated(mRow);
wrapper.setAnimationsRunning(true);
verify(mockDrawable).start();
}
@Test
public void setAnimationsRunning_Stop() {
BigPictureNotificationImageView imageView = mView.findViewById(R.id.big_picture);
AnimatedImageDrawable mockDrawable = mock(AnimatedImageDrawable.class);
assertNotNull(imageView);
imageView.setImageDrawable(mockDrawable);
NotificationViewWrapper wrapper = new NotificationBigPictureTemplateViewWrapper(mContext,
mView, mRow);
// Required to re-initialize the imageView to the imageView created above.
wrapper.onContentUpdated(mRow);
wrapper.setAnimationsRunning(false);
verify(mockDrawable).stop();
}
} }

View File

@@ -0,0 +1,143 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar.notification.row.wrapper
import android.graphics.drawable.AnimatedImageDrawable
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import android.view.View
import androidx.test.filters.SmallTest
import com.android.internal.R
import com.android.internal.widget.CachingIconView
import com.android.internal.widget.ConversationLayout
import com.android.internal.widget.MessagingGroup
import com.android.internal.widget.MessagingImageMessage
import com.android.internal.widget.MessagingLinearLayout
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.NotificationTestHelper
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper
class NotificationConversationTemplateViewWrapperTest : SysuiTestCase() {
private lateinit var mRow: ExpandableNotificationRow
private lateinit var helper: NotificationTestHelper
@Before
fun setUp() {
allowTestableLooperAsMainThread()
helper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
mRow = helper.createRow()
}
@Test
fun setAnimationsRunning_Run() {
// Creates a mocked out NotificationEntry of ConversationLayout type,
// with a mock imageMessage.drawable embedded in its MessagingImageMessages
// (both top level, and in a group).
val mockDrawable = mock<AnimatedImageDrawable>()
val mockDrawable2 = mock<AnimatedImageDrawable>()
val mockLayoutView: View = fakeConversationLayout(mockDrawable, mockDrawable2)
val wrapper: NotificationViewWrapper =
NotificationConversationTemplateViewWrapper(mContext, mockLayoutView, mRow)
wrapper.onContentUpdated(mRow)
wrapper.setAnimationsRunning(true)
// Verifies that each AnimatedImageDrawable is started animating.
verify(mockDrawable).start()
verify(mockDrawable2).start()
}
@Test
fun setAnimationsRunning_Stop() {
// Creates a mocked out NotificationEntry of ConversationLayout type,
// with a mock imageMessage.drawable embedded in its MessagingImageMessages
// (both top level, and in a group).
val mockDrawable = mock<AnimatedImageDrawable>()
val mockDrawable2 = mock<AnimatedImageDrawable>()
val mockLayoutView: View = fakeConversationLayout(mockDrawable, mockDrawable2)
val wrapper: NotificationViewWrapper =
NotificationConversationTemplateViewWrapper(mContext, mockLayoutView, mRow)
wrapper.onContentUpdated(mRow)
wrapper.setAnimationsRunning(false)
// Verifies that each AnimatedImageDrawable is started animating.
verify(mockDrawable).stop()
verify(mockDrawable2).stop()
}
private fun fakeConversationLayout(
mockDrawableGroupMessage: AnimatedImageDrawable,
mockDrawableImageMessage: AnimatedImageDrawable
): View {
val mockMessagingImageMessage: MessagingImageMessage =
mock<MessagingImageMessage>().apply {
whenever(drawable).thenReturn(mockDrawableImageMessage)
}
val mockImageMessageContainer: MessagingLinearLayout =
mock<MessagingLinearLayout>().apply {
whenever(childCount).thenReturn(1)
whenever(getChildAt(any())).thenReturn(mockMessagingImageMessage)
}
val mockMessagingImageMessageForGroup: MessagingImageMessage =
mock<MessagingImageMessage>().apply {
whenever(drawable).thenReturn(mockDrawableGroupMessage)
}
val mockMessageContainer: MessagingLinearLayout =
mock<MessagingLinearLayout>().apply {
whenever(childCount).thenReturn(1)
whenever(getChildAt(any())).thenReturn(mockMessagingImageMessageForGroup)
}
val mockGroup: MessagingGroup =
mock<MessagingGroup>().apply {
whenever(messageContainer).thenReturn(mockMessageContainer)
}
val mockView: View =
mock<ConversationLayout>().apply {
whenever(messagingGroups).thenReturn(ArrayList<MessagingGroup>(listOf(mockGroup)))
whenever(imageMessageContainer).thenReturn(mockImageMessageContainer)
whenever(messagingLinearLayout).thenReturn(mockMessageContainer)
// These must be mocked as they're required to be nonnull.
whenever(requireViewById<View>(R.id.conversation_icon_container)).thenReturn(mock())
whenever(requireViewById<CachingIconView>(R.id.conversation_icon))
.thenReturn(mock())
whenever(findViewById<CachingIconView>(R.id.icon)).thenReturn(mock())
whenever(requireViewById<View>(R.id.conversation_icon_badge_bg)).thenReturn(mock())
whenever(requireViewById<View>(R.id.expand_button)).thenReturn(mock())
whenever(requireViewById<View>(R.id.expand_button_container)).thenReturn(mock())
whenever(requireViewById<View>(R.id.conversation_icon_badge_ring))
.thenReturn(mock())
whenever(requireViewById<View>(R.id.app_name_text)).thenReturn(mock())
whenever(requireViewById<View>(R.id.conversation_text)).thenReturn(mock())
}
return mockView
}
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar.notification.row.wrapper
import android.graphics.drawable.AnimatedImageDrawable
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import android.view.View
import androidx.test.filters.SmallTest
import com.android.internal.widget.MessagingGroup
import com.android.internal.widget.MessagingImageMessage
import com.android.internal.widget.MessagingLayout
import com.android.internal.widget.MessagingLinearLayout
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.NotificationTestHelper
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper
class NotificationMessagingTemplateViewWrapperTest : SysuiTestCase() {
private lateinit var mRow: ExpandableNotificationRow
private lateinit var helper: NotificationTestHelper
@Before
fun setUp() {
allowTestableLooperAsMainThread()
helper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
mRow = helper.createRow()
}
@Test
fun setAnimationsRunning_Run() {
// Creates a mocked out NotificationEntry of MessagingLayout/MessagingStyle type,
// with a mock imageMessage.drawable embedded in its MessagingImageMessage.
val mockDrawable = mock<AnimatedImageDrawable>()
val mockLayoutView: View = fakeMessagingLayout(mockDrawable)
val wrapper: NotificationViewWrapper =
NotificationMessagingTemplateViewWrapper(mContext, mockLayoutView, mRow)
wrapper.setAnimationsRunning(true)
// Verifies that each AnimatedImageDrawable is started animating.
verify(mockDrawable).start()
}
@Test
fun setAnimationsRunning_Stop() {
// Creates a mocked out NotificationEntry of MessagingLayout/MessagingStyle type,
// with a mock imageMessage.drawable embedded in its MessagingImageMessage.
val mockDrawable = mock<AnimatedImageDrawable>()
val mockLayoutView: View = fakeMessagingLayout(mockDrawable)
val wrapper: NotificationViewWrapper =
NotificationMessagingTemplateViewWrapper(mContext, mockLayoutView, mRow)
wrapper.setAnimationsRunning(false)
// Verifies that each AnimatedImageDrawable is started animating.
verify(mockDrawable).stop()
}
private fun fakeMessagingLayout(mockDrawable: AnimatedImageDrawable): View {
val mockMessagingImageMessage: MessagingImageMessage =
mock<MessagingImageMessage>().apply { whenever(drawable).thenReturn(mockDrawable) }
val mockMessageContainer: MessagingLinearLayout =
mock<MessagingLinearLayout>().apply {
whenever(childCount).thenReturn(1)
whenever(getChildAt(any())).thenReturn(mockMessagingImageMessage)
}
val mockGroup: MessagingGroup =
mock<MessagingGroup>().apply {
whenever(messageContainer).thenReturn(mockMessageContainer)
}
val mockView: View =
mock<MessagingLayout>().apply {
whenever(messagingGroups).thenReturn(ArrayList<MessagingGroup>(listOf(mockGroup)))
}
return mockView
}
}