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

Commit 147de5aa authored by Jordan Demeulenaere's avatar Jordan Demeulenaere
Browse files

Fix HUN launch animation.

This CL fixes HUN launch animation by making sure that the HUN is hidden
only when the launch animation is ended, cancelled, aborted or timed
out.

See b/186389197#comment2 for before/after videos.

Bug: 186389197
Test: Click a HUN notification
Test: atest ActivityLaunchAnimatorTest
Change-Id: I3c8e1d8195124946aef5a25c411b6913157179b8
parent 9ccd991d
Loading
Loading
Loading
Loading
+22 −4
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@ import android.app.PendingIntent
import android.content.Context
import android.graphics.Matrix
import android.graphics.Rect
import android.os.Looper
import android.os.RemoteException
import android.util.MathUtils
import android.view.IRemoteAnimationFinishedCallback
@@ -73,16 +74,20 @@ class ActivityLaunchAnimator(context: Context) {
     * in [Controller.onLaunchAnimationProgress]. No animation will start if there is no window
     * opening.
     *
     * If [controller] is null, then the intent will be started and no animation will run.
     * If [controller] is null or [animate] is false, then the intent will be started and no
     * animation will run.
     *
     * This method will throw any exception thrown by [intentStarter].
     */
    @JvmOverloads
    inline fun startIntentWithAnimation(
        controller: Controller?,
        animate: Boolean = true,
        intentStarter: (RemoteAnimationAdapter?) -> Int
    ) {
        if (controller == null) {
        if (controller == null || !animate) {
            intentStarter(null)
            controller?.callOnIntentStartedOnMainThread(willAnimate = false)
            return
        }

@@ -95,7 +100,7 @@ class ActivityLaunchAnimator(context: Context) {
        val launchResult = intentStarter(animationAdapter)
        val willAnimate = launchResult == ActivityManager.START_TASK_TO_FRONT ||
            launchResult == ActivityManager.START_SUCCESS
        runner.context.mainExecutor.execute { controller.onIntentStarted(willAnimate) }
        controller.callOnIntentStartedOnMainThread(willAnimate)

        // If we expect an animation, post a timeout to cancel it in case the remote animation is
        // never started.
@@ -104,17 +109,30 @@ class ActivityLaunchAnimator(context: Context) {
        }
    }

    @PublishedApi
    internal fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) {
        if (Looper.myLooper() != Looper.getMainLooper()) {
            this.getRootView().context.mainExecutor.execute {
                this.onIntentStarted(willAnimate)
            }
        } else {
            this.onIntentStarted(willAnimate)
        }
    }

    /**
     * Same as [startIntentWithAnimation] but allows [intentStarter] to throw a
     * [PendingIntent.CanceledException] which must then be handled by the caller. This is useful
     * for Java caller starting a [PendingIntent].
     */
    @Throws(PendingIntent.CanceledException::class)
    @JvmOverloads
    fun startPendingIntentWithAnimation(
        controller: Controller?,
        animate: Boolean = true,
        intentStarter: PendingIntentStarter
    ) {
        startIntentWithAnimation(controller) { intentStarter.startPendingIntent(it) }
        startIntentWithAnimation(controller, animate) { intentStarter.startPendingIntent(it) }
    }

    /** Create a new animation [Runner] controlled by [controller]. */
+23 −3
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@ import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.statusbar.NotificationShadeDepthController
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController
import kotlin.math.ceil
import kotlin.math.max
@@ -14,7 +15,8 @@ import kotlin.math.max
class NotificationLaunchAnimatorControllerProvider(
    private val notificationShadeWindowViewController: NotificationShadeWindowViewController,
    private val notificationListContainer: NotificationListContainer,
    private val depthController: NotificationShadeDepthController
    private val depthController: NotificationShadeDepthController,
    private val headsUpManager: HeadsUpManagerPhone
) {
    fun getAnimatorController(
        notification: ExpandableNotificationRow
@@ -23,7 +25,8 @@ class NotificationLaunchAnimatorControllerProvider(
            notificationShadeWindowViewController,
            notificationListContainer,
            depthController,
            notification
            notification,
            headsUpManager
        )
    }
}
@@ -37,8 +40,11 @@ class NotificationLaunchAnimatorController(
    private val notificationShadeWindowViewController: NotificationShadeWindowViewController,
    private val notificationListContainer: NotificationListContainer,
    private val depthController: NotificationShadeDepthController,
    private val notification: ExpandableNotificationRow
    private val notification: ExpandableNotificationRow,
    private val headsUpManager: HeadsUpManagerPhone
) : ActivityLaunchAnimator.Controller {
    private val notificationKey = notification.entry.sbn.key

    override fun getRootView(): View = notification.rootView

    override fun createAnimatorState(): ActivityLaunchAnimator.State {
@@ -76,12 +82,25 @@ class NotificationLaunchAnimatorController(

    override fun onIntentStarted(willAnimate: Boolean) {
        notificationShadeWindowViewController.setExpandAnimationRunning(willAnimate)

        if (!willAnimate) {
            removeHun(animate = true)
        }
    }

    private fun removeHun(animate: Boolean) {
        if (!headsUpManager.isAlerting(notificationKey)) {
            return
        }

        headsUpManager.removeNotification(notificationKey, true /* releaseImmediately */, animate)
    }

    override fun onLaunchAnimationCancelled() {
        // TODO(b/184121838): Should we call InteractionJankMonitor.cancel if the animation started
        // here?
        notificationShadeWindowViewController.setExpandAnimationRunning(false)
        removeHun(animate = true)
    }

    override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
@@ -99,6 +118,7 @@ class NotificationLaunchAnimatorController(
        notificationShadeWindowViewController.setExpandAnimationRunning(false)
        notificationListContainer.setExpandingNotification(null)
        applyParams(null)
        removeHun(animate = false)
    }

    private fun applyParams(params: ExpandAnimationParameters?) {
+13 −1
Original line number Diff line number Diff line
@@ -301,7 +301,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////
    //  HeadsUpManager public methods overrides:
    //  HeadsUpManager public methods overrides and overloads:

    @Override
    public boolean isTrackingHeadsUp() {
@@ -318,6 +318,18 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
        mSwipedOutKeys.add(key);
    }

    public boolean removeNotification(@NonNull String key, boolean releaseImmediately,
            boolean animate) {
        if (animate) {
            return removeNotification(key, releaseImmediately);
        } else {
            mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
            boolean removed = removeNotification(key, releaseImmediately);
            mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(true);
            return removed;
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////
    //  Dumpable overrides:

+48 −47
Original line number Diff line number Diff line
@@ -1422,7 +1422,8 @@ public class StatusBar extends SystemUI implements DemoMode,
        mNotificationAnimationProvider = new NotificationLaunchAnimatorControllerProvider(
                mNotificationShadeWindowViewController,
                mStackScrollerController.getNotificationListContainer(),
                mNotificationShadeDepthControllerLazy.get()
                mNotificationShadeDepthControllerLazy.get(),
                mHeadsUpManager
        );

        // TODO: inject this.
@@ -2795,7 +2796,7 @@ public class StatusBar extends SystemUI implements DemoMode,
                intent, mLockscreenUserManager.getCurrentUserId());

        ActivityLaunchAnimator.Controller animController = null;
        if (animationController != null && areLaunchAnimationsEnabled()) {
        if (animationController != null) {
            animController = dismissShade ? new StatusBarLaunchAnimatorController(
                    animationController, this, true /* isLaunchForActivity */)
                    : animationController;
@@ -2813,7 +2814,8 @@ public class StatusBar extends SystemUI implements DemoMode,
            intent.addFlags(flags);
            int[] result = new int[]{ActivityManager.START_CANCELED};

            mActivityLaunchAnimator.startIntentWithAnimation(animCallbackForLambda, (adapter) -> {
            mActivityLaunchAnimator.startIntentWithAnimation(animCallbackForLambda,
                    areLaunchAnimationsEnabled(), (adapter) -> {
                        ActivityOptions options = new ActivityOptions(
                                getActivityOptions(mDisplayId, adapter));
                        options.setDisallowEnterPictureInPictureWhileLaunching(
@@ -2841,7 +2843,8 @@ public class StatusBar extends SystemUI implements DemoMode,

                        try {
                            result[0] = ActivityTaskManager.getService().startActivityAsUser(
                            null, mContext.getBasePackageName(), mContext.getAttributionTag(),
                                    null, mContext.getBasePackageName(),
                                    mContext.getAttributionTag(),
                                    intent,
                                    intent.resolveTypeIfNeeded(mContext.getContentResolver()),
                                    null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null,
@@ -4569,19 +4572,17 @@ public class StatusBar extends SystemUI implements DemoMode,
                && mActivityIntentHelper.wouldLaunchResolverActivity(intent.getIntent(),
                mLockscreenUserManager.getCurrentUserId());

        boolean animate = animationController != null && areLaunchAnimationsEnabled();
        boolean collapse = !animate;
        boolean collapse = animationController == null;
        executeActionDismissingKeyguard(() -> {
            try {
                // We wrap animationCallback with a StatusBarLaunchAnimatorController so that the
                // shade is collapsed after the animation (or when it is cancelled, aborted, etc).
                ActivityLaunchAnimator.Controller controller =
                        animate ? new StatusBarLaunchAnimatorController(animationController, this,
                                intent.isActivity())
                                : null;
                        animationController != null ? new StatusBarLaunchAnimatorController(
                                animationController, this, intent.isActivity()) : null;

                mActivityLaunchAnimator.startPendingIntentWithAnimation(
                        controller,
                        controller, areLaunchAnimationsEnabled(),
                        (animationAdapter) -> intent.sendAndReturnResult(null, 0, null, null, null,
                                null, getActivityOptions(mDisplayId, animationAdapter)));
            } catch (PendingIntent.CanceledException e) {
+24 −54
Original line number Diff line number Diff line
@@ -283,7 +283,13 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
        mLogger.logHandleClickAfterKeyguardDismissed(entry.getKey());

        // TODO: Some of this code may be able to move to NotificationEntryManager.
        removeHUN(row);
        String key = row.getEntry().getSbn().getKey();
        if (mHeadsUpManager != null && mHeadsUpManager.isAlerting(key)) {
            // Release the HUN notification to the shade.
            if (mPresenter.isPresenterFullyCollapsed()) {
                HeadsUpUtil.setIsClickedHeadsUpNotification(row, true);
            }
        }

        final Runnable runnable = () -> handleNotificationClickAfterPanelCollapsed(
                entry, row, controller, intent,
@@ -331,6 +337,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
                // bypass work challenge
                if (mStatusBarRemoteInputCallback.startWorkChallengeIfNecessary(userId,
                        intent.getIntentSender(), notificationKey)) {
                    removeHUN(row);
                    // Show work challenge, do not run PendingIntent and
                    // remove notification
                    collapseOnMainThread();
@@ -350,6 +357,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
        final boolean canBubble = entry.canBubble();
        if (canBubble) {
            mLogger.logExpandingBubble(notificationKey);
            removeHUN(row);
            expandBubbleStackOnMainThread(entry);
        } else {
            startNotificationIntent(
@@ -422,14 +430,13 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
            boolean isActivityIntent) {
        mLogger.logStartNotificationIntent(entry.getKey(), intent);
        try {
            ActivityLaunchAnimator.Controller animationController = null;
            if (!wasOccluded && mStatusBar.areLaunchAnimationsEnabled()) {
                animationController = new StatusBarLaunchAnimatorController(
            ActivityLaunchAnimator.Controller animationController =
                    new StatusBarLaunchAnimatorController(
                            mNotificationAnimationProvider.getAnimatorController(row), mStatusBar,
                            isActivityIntent);
            }

            mActivityLaunchAnimator.startPendingIntentWithAnimation(animationController,
                    !wasOccluded && mStatusBar.areLaunchAnimationsEnabled(),
                    (adapter) -> {
                        long eventTime = row.getAndResetLastActionUpTime();
                        Bundle options = eventTime > 0
@@ -442,13 +449,6 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
                        return intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
                                null, null, options);
                    });

            // Note that other cases when we should still collapse (like activity already on top) is
            // handled by the StatusBarLaunchAnimatorController.
            boolean shouldCollapse = animationController == null;
            if (shouldCollapse) {
                collapseOnMainThread();
            }
        } catch (PendingIntent.CanceledException e) {
            // the stack trace isn't very helpful here.
            // Just log the exception message.
@@ -462,34 +462,19 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
            ExpandableNotificationRow row) {
        mActivityStarter.dismissKeyguardThenExecute(() -> {
            AsyncTask.execute(() -> {
                ActivityLaunchAnimator.Controller animationController = null;
                if (mStatusBar.areLaunchAnimationsEnabled()) {
                    animationController = new StatusBarLaunchAnimatorController(
                            mNotificationAnimationProvider.getAnimatorController(row), mStatusBar,
                            true /* isActivityIntent */);
                }
                ActivityLaunchAnimator.Controller animationController =
                        new StatusBarLaunchAnimatorController(
                                mNotificationAnimationProvider.getAnimatorController(row),
                                mStatusBar, true /* isActivityIntent */);

                mActivityLaunchAnimator.startIntentWithAnimation(
                        animationController,
                        animationController, mStatusBar.areLaunchAnimationsEnabled(),
                        (adapter) -> TaskStackBuilder.create(mContext)
                                .addNextIntentWithParentStack(intent)
                                .startActivities(getActivityOptions(
                                        mStatusBar.getDisplayId(),
                                        adapter),
                                        new UserHandle(UserHandle.getUserId(appUid))));

                // Note that other cases when we should still collapse (like activity already on
                // top) is handled by the StatusBarLaunchAnimatorController.
                boolean shouldCollapse = animationController == null;

                // Putting it back on the main thread, since we're touching views
                mMainThreadHandler.post(() -> {
                    removeHUN(row);
                    if (shouldCollapse) {
                        mCommandQueue.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
                                true /* force */);
                    }
                });
            });
            return true;
        }, null, false /* afterKeyguardGone */);
@@ -508,26 +493,16 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
                    tsb.addNextIntent(intent);
                }

                ActivityLaunchAnimator.Controller animationController = null;
                if (mStatusBar.areLaunchAnimationsEnabled()) {
                    animationController = new StatusBarLaunchAnimatorController(
                ActivityLaunchAnimator.Controller animationController =
                        new StatusBarLaunchAnimatorController(
                                ActivityLaunchAnimator.Controller.fromView(view), mStatusBar,
                                true /* isActivityIntent */);
                }

                mActivityLaunchAnimator.startIntentWithAnimation(animationController,
                        mStatusBar.areLaunchAnimationsEnabled(),
                        (adapter) -> tsb.startActivities(
                                getActivityOptions(mStatusBar.getDisplayId(), adapter),
                                UserHandle.CURRENT));

                // Note that other cases when we should still collapse (like activity already on
                // top) is handled by the StatusBarLaunchAnimatorController.
                boolean shouldCollapse = animationController == null;
                if (shouldCollapse) {
                    // Putting it back on the main thread, since we're touching views
                    mMainThreadHandler.post(() -> mCommandQueue.animateCollapsePanels(
                            CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */));
                }
            });
            return true;
        }, null, false /* afterKeyguardGone */);
@@ -536,11 +511,6 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
    private void removeHUN(ExpandableNotificationRow row) {
        String key = row.getEntry().getSbn().getKey();
        if (mHeadsUpManager != null && mHeadsUpManager.isAlerting(key)) {
            // Release the HUN notification to the shade.
            if (mPresenter.isPresenterFullyCollapsed()) {
                HeadsUpUtil.setIsClickedHeadsUpNotification(row, true);
            }

            // In most cases, when FLAG_AUTO_CANCEL is set, the notification will
            // become canceled shortly by NoMan, but we can't assume that.
            mHeadsUpManager.removeNotification(key, true /* releaseImmediately */);
Loading