diff options
14 files changed, 723 insertions, 20 deletions
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 9f50aef6de11..9275e2b603c3 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 @@ -79,6 +79,8 @@ import com.android.internal.widget.CallLayout; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; 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.PluginListener; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; @@ -177,6 +179,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private PeopleNotificationIdentifier mPeopleNotificationIdentifier; private Optional<BubblesManager> mBubblesManagerOptional; private MetricsLogger mMetricsLogger; + private FeatureFlags mFeatureFlags; private int mIconTransformContentShift; private int mMaxHeadsUpHeightBeforeN; private int mMaxHeadsUpHeightBeforeP; @@ -277,7 +280,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private boolean mChildIsExpanding; private boolean mJustClicked; - private boolean mIconAnimationRunning; + private boolean mAnimationRunning; private boolean mShowNoBackground; private ExpandableNotificationRow mNotificationParent; private OnExpandClickListener mOnExpandClickListener; @@ -451,10 +454,26 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mPublicLayout; } - public void setIconAnimationRunning(boolean running) { - for (NotificationContentView l : mLayouts) { - setIconAnimationRunning(running, l); + /** + * Sets animations running in the layouts of this row, including public, private, and children. + * @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) { NotificationViewWrapper viewWrapper = mChildrenContainer.getNotificationViewWrapper(); if (viewWrapper != null) { @@ -468,12 +487,18 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mChildrenContainer.getAttachedChildren(); for (int i = 0; i < notificationChildren.size(); 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) { if (layout != null) { 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) { if (child != null) { 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); - 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) { Drawable drawable = imageView.getDrawable(); if (drawable instanceof AnimationDrawable) { @@ -561,8 +599,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mChildrenContainer.recreateNotificationHeader(mExpandClickListener, isConversation()); mChildrenContainer.onNotificationUpdated(); } - if (mIconAnimationRunning) { - setIconAnimationRunning(true); + if (mAnimationRunning) { + setAnimationRunning(true); } if (mLastChronometerRunning) { setChronometerRunning(true); @@ -1038,7 +1076,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView notifyHeightChanged(false /* needsAnimation */); } if (pinned) { - setIconAnimationRunning(true); + setAnimationRunning(true); mExpandedWhenPinned = false; } else if (mExpandedWhenPinned) { 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) { super(context, attrs); mImageResolver = new NotificationInlineImageResolver(context, @@ -1662,7 +1705,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView NotificationGutsManager gutsManager, MetricsLogger metricsLogger, SmartReplyConstants smartReplyConstants, - SmartReplyController smartReplyController) { + SmartReplyController smartReplyController, + FeatureFlags featureFlags) { mEntry = entry; mAppName = appName; if (mMenuRow == null) { @@ -1697,6 +1741,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mBubblesManagerOptional = bubblesManagerOptional; mNotificationGutsManager = gutsManager; mMetricsLogger = metricsLogger; + mFeatureFlags = featureFlags; } private void initDimens() { @@ -3588,11 +3633,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @VisibleForTesting protected void setPrivateLayout(NotificationContentView privateLayout) { mPrivateLayout = privateLayout; + mLayouts = new NotificationContentView[]{mPrivateLayout, mPublicLayout}; } @VisibleForTesting protected void setPublicLayout(NotificationContentView publicLayout) { mPublicLayout = publicLayout; + mLayouts = new NotificationContentView[]{mPrivateLayout, mPublicLayout}; } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java index d1138608805b..bb92dfcdfcb5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -219,7 +219,8 @@ public class ExpandableNotificationRowController implements NotifViewController mNotificationGutsManager, mMetricsLogger, mSmartReplyConstants, - mSmartReplyController + mSmartReplyController, + mFeatureFlags ); mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); if (mAllowLongPress) { 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 e46bf522acff..4a023c41388e 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 @@ -184,6 +184,8 @@ public class NotificationContentView extends FrameLayout implements Notification private boolean mRemoteInputVisible; private int mUnrestrictedContentHeight; + private boolean mContentAnimating; + public NotificationContentView(Context context, AttributeSet attrs) { super(context, attrs); mHybridGroupManager = new HybridGroupManager(getContext()); @@ -2129,8 +2131,49 @@ public class NotificationContentView extends FrameLayout implements Notification 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 { @Nullable RemoteInputView mView; @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; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java index 8732696dc7a1..175ba15eebae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java @@ -18,11 +18,15 @@ package com.android.systemui.statusbar.notification.row.wrapper; import android.app.Notification; import android.content.Context; +import android.graphics.drawable.AnimatedImageDrawable; +import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.Bundle; import android.service.notification.StatusBarNotification; 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.row.ExpandableNotificationRow; @@ -31,6 +35,8 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow */ public class NotificationBigPictureTemplateViewWrapper extends NotificationTemplateViewWrapper { + private BigPictureNotificationImageView mImageView; + protected NotificationBigPictureTemplateViewWrapper(Context ctx, View view, ExpandableNotificationRow row) { super(ctx, view, row); @@ -39,9 +45,14 @@ public class NotificationBigPictureTemplateViewWrapper extends NotificationTempl @Override public void onContentUpdated(ExpandableNotificationRow row) { super.onContentUpdated(row); + resolveViews(); updateImageTag(row.getEntry().getSbn()); } + private void resolveViews() { + mImageView = mView.findViewById(R.id.big_picture); + } + private void updateImageTag(StatusBarNotification sbn) { final Bundle extras = sbn.getNotification().extras; 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())); } } + + /** + * 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(); + } + } + } } 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 e136055b80b3..10753f215103 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 @@ -17,16 +17,20 @@ package com.android.systemui.statusbar.notification.row.wrapper import android.content.Context +import android.graphics.drawable.AnimatedImageDrawable import android.view.View import android.view.ViewGroup 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.R import com.android.systemui.statusbar.notification.NotificationFadeAware import com.android.systemui.statusbar.notification.NotificationUtils import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.wrapper.NotificationMessagingTemplateViewWrapper.setCustomImageMessageTransform +import com.android.systemui.util.children /** * Wraps a notification containing a conversation template @@ -49,6 +53,7 @@ class NotificationConversationTemplateViewWrapper constructor( private lateinit var expandBtn: View private lateinit var expandBtnContainer: View private lateinit var imageMessageContainer: ViewGroup + private lateinit var messageContainers: ArrayList<MessagingGroup> private lateinit var messagingLinearLayout: MessagingLinearLayout private lateinit var conversationTitleView: View private lateinit var importanceRing: View @@ -60,6 +65,7 @@ class NotificationConversationTemplateViewWrapper constructor( private fun resolveViews() { messagingLinearLayout = conversationLayout.messagingLinearLayout imageMessageContainer = conversationLayout.imageMessageContainer + messageContainers = conversationLayout.messagingGroups with(conversationLayout) { conversationIconContainer = requireViewById(com.android.internal.R.id.conversation_icon_container) @@ -146,4 +152,26 @@ class NotificationConversationTemplateViewWrapper constructor( NotificationFadeAware.setLayerTypeForFaded(expandBtn, 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() + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapper.java index c587ce05b13f..4592fde69a93 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapper.java @@ -17,9 +17,13 @@ package com.android.systemui.statusbar.notification.row.wrapper; import android.content.Context; +import android.graphics.drawable.AnimatedImageDrawable; +import android.graphics.drawable.Drawable; import android.view.View; 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.MessagingLinearLayout; import com.android.systemui.R; @@ -127,4 +131,40 @@ public class NotificationMessagingTemplateViewWrapper extends NotificationTempla } 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(); + } + } + } + } } 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 1c22f0933236..ff5b9cbf3c23 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 @@ -403,4 +403,12 @@ public abstract class NotificationViewWrapper implements TransformableView { NotificationFadeAware.setLayerTypeForFaded(getIcon(), 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) { + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 356ddfa2d482..4837075680ae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -3132,7 +3132,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private void updateAnimationState(boolean running, View child) { if (child instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) child; - row.setIconAnimationRunning(running); + row.setAnimationRunning(running); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index 9d531a165f1f..4559a23a4f5c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -44,16 +44,23 @@ import static org.mockito.Mockito.when; import android.app.Notification; import android.app.NotificationChannel; 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.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.util.DisplayMetrics; import android.view.View; +import android.widget.ImageView; import androidx.test.filters.SmallTest; import com.android.internal.R; +import com.android.internal.widget.CachingIconView; 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.StatusBarStateController; 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.collection.NotificationEntry; 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 org.junit.Assert; @@ -72,6 +80,7 @@ import org.mockito.Mockito; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.util.Arrays; import java.util.List; @SmallTest @@ -96,6 +105,9 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { mDependency, TestableLooper.get(this)); 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 Notification normalNotif = mNotificationTestHelper.createNotification(); normalNotif.publicVersion = null; @@ -559,4 +571,123 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { Assert.assertEquals(1f, mGroupRow.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; + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt index 562b4dfb35ef..7b2051da4d15 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt @@ -34,9 +34,12 @@ import com.android.systemui.media.dialog.MediaOutputDialogFactory import com.android.systemui.statusbar.notification.FeedbackIcon import com.android.systemui.statusbar.notification.collection.NotificationEntry 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.whenever import junit.framework.Assert.assertEquals +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -44,6 +47,7 @@ import org.mockito.Mock import org.mockito.Mockito.doReturn import org.mockito.Mockito.never import org.mockito.Mockito.spy +import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations.initMocks @@ -305,6 +309,86 @@ class NotificationContentViewTest : SysuiTestCase() { 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) = mock<ExpandableNotificationRow>().apply { whenever(this.entry).thenReturn(notificationEntry) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index 728e0265c729..e4fc4d5d54ba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -53,6 +53,7 @@ import com.android.internal.logging.UiEventLogger; import com.android.systemui.TestableDependency; import com.android.systemui.classifier.FalsingCollectorFake; 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.dialog.MediaOutputDialogFactory; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -130,6 +131,7 @@ public class NotificationTestHelper { public final OnUserInteractionCallback mOnUserInteractionCallback; public final Runnable mFutureDismissalRunnable; private @InflationFlag int mDefaultInflationFlags; + private FeatureFlags mFeatureFlags; public NotificationTestHelper( Context context, @@ -193,12 +195,17 @@ public class NotificationTestHelper { mFutureDismissalRunnable = mock(Runnable.class); when(mOnUserInteractionCallback.registerFutureDismissal(any(), anyInt())) .thenReturn(mFutureDismissalRunnable); + mFeatureFlags = mock(FeatureFlags.class); } public void setDefaultInflationFlags(@InflationFlag int defaultInflationFlags) { mDefaultInflationFlags = defaultInflationFlags; } + public void setFeatureFlags(FeatureFlags featureFlags) { + mFeatureFlags = featureFlags; + } + public ExpandableNotificationRowLogger getMockLogger() { return mMockLogger; } @@ -561,7 +568,8 @@ public class NotificationTestHelper { mock(NotificationGutsManager.class), mock(MetricsLogger.class), mock(SmartReplyConstants.class), - mock(SmartReplyController.class)); + mock(SmartReplyController.class), + mFeatureFlags); row.setAboveShelfChangedListener(aboveShelf -> { }); mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java index 509ba4194e5e..8f88501a38f7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java @@ -16,11 +16,12 @@ 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.when; +import static org.mockito.Mockito.verify; import android.app.Notification; -import android.content.Context; +import android.graphics.drawable.AnimatedImageDrawable; import android.graphics.drawable.Icon; import android.os.Bundle; import android.testing.AndroidTestingRunner; @@ -28,11 +29,11 @@ import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.view.LayoutInflater; import android.view.View; -import android.widget.LinearLayout; -import android.widget.TextView; 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.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationTestHelper; @@ -73,4 +74,38 @@ public class NotificationBigPictureTemplateViewWrapperTest extends SysuiTestCase Notification.EXTRA_LARGE_ICON_BIG, new Bundle()); 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(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt new file mode 100644 index 000000000000..3fa68bb69da2 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt @@ -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 + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt new file mode 100644 index 000000000000..c0444b563a2c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt @@ -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 + } +} |