Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 91d39350 authored by Alexander Roederer's avatar Alexander Roederer
Browse files

Animates drawables in Notifications

Starts and stops drawables in BigPicture, Conversation, and
Messaging Template Notifications via the NotificationViewWrapper
by hooking into times when Icons should be animating.

Bug: 263414400
Test: Unit tests added to cover all files.
Change-Id: I91ba75ba881a88cadce47b3a76cf1b5eab29d5ec
parent 0ebb26a2
Loading
Loading
Loading
Loading
+60 −13
Original line number Diff line number Diff line
@@ -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) {
    /**
     * 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};
    }

    /**
+2 −1
Original line number Diff line number Diff line
@@ -219,7 +219,8 @@ public class ExpandableNotificationRowController implements NotifViewController
                mNotificationGutsManager,
                mMetricsLogger,
                mSmartReplyConstants,
                mSmartReplyController
                mSmartReplyController,
                mFeatureFlags
        );
        mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
        if (mAllowLongPress) {
+43 −0
Original line number Diff line number Diff line
@@ -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;
    }
}
+32 −0
Original line number Diff line number Diff line
@@ -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();
            }
        }
    }
}
+28 −0
Original line number Diff line number Diff line
@@ -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()
            }
        }
    }
}
Loading