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

Commit 9330619e authored by Jeremy Sim's avatar Jeremy Sim
Browse files

Fixes crash when launching app pairs with intent+intent

This CL adds a new case for app pair launches. When we launch app pairs with intents, we receive initialTaskId and secondTaskId as -1, which was previously unhandled and caused a crash. With this change, we find the left/top app leash a different way, by looking for windowingMode=WINDOWING_MODE_MULTI_WINDOW and endAbsBounds.left/top == 0.

Fixes: 316050315
Test: Clear all running tasks from Overview, launch app pair, no longer crashes
Flag: ACONFIG com.android.wm.shell.enable_app_pairs DEVELOPMENT
Change-Id: Ic356d71ad267f079242213ebc59322bf1fb86b7c
parent ea4c7015
Loading
Loading
Loading
Loading
+53 −43
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.app.ActivityManager.RunningTaskInfo
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.graphics.Bitmap
import android.graphics.Rect
import android.graphics.RectF
@@ -31,9 +32,11 @@ import android.view.RemoteAnimationTarget
import android.view.SurfaceControl
import android.view.SurfaceControl.Transaction
import android.view.View
import android.view.WindowManager
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.TransitionInfo
import android.window.TransitionInfo.Change
import android.window.WindowContainerToken
import androidx.annotation.VisibleForTesting
import com.android.app.animation.Interpolators
import com.android.launcher3.DeviceProfile
@@ -387,14 +390,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC
                "trying to launch an app pair icon, but encountered an unexpected null"
            }

            composeIconSplitLaunchAnimator(
                launchingIconView,
                initialTaskId,
                secondTaskId,
                info,
                t,
                finishCallback
            )
            composeIconSplitLaunchAnimator(launchingIconView, info, t, finishCallback)
        } else {
            // Fallback case: simple fade-in animation
            check(info != null && t != null) {
@@ -461,12 +457,27 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC
    /**
     * When the user taps an app pair icon to launch split, this will play the tasks' launch
     * animation from the position of the icon.
     *
     * To find the root shell leash that we want to fade in, we do the following:
     * The Changes we receive in transitionInfo are structured like this
     *
     *     Root (grandparent)
     *     |
     *     |--> Split Root 1 (left/top side parent) (WINDOWING_MODE_MULTI_WINDOW)
     *     |   |
     *     |    --> App 1 (left/top side child) (WINDOWING_MODE_MULTI_WINDOW)
     *     |--> Divider
     *     |--> Split Root 2 (right/bottom side parent) (WINDOWING_MODE_MULTI_WINDOW)
     *         |
     *          --> App 2 (right/bottom side child) (WINDOWING_MODE_MULTI_WINDOW)
     *
     * We want to animate the Root (grandparent) so that it affects both apps and the divider.
     * To do this, we find one of the nodes with WINDOWING_MODE_MULTI_WINDOW (one of the
     * left-side ones, for simplicity) and traverse the tree until we find the grandparent.
     */
    @VisibleForTesting
    fun composeIconSplitLaunchAnimator(
        launchingIconView: AppPairIcon,
        initialTaskId: Int,
        secondTaskId: Int,
        transitionInfo: TransitionInfo,
        t: Transaction,
        finishCallback: Runnable
@@ -481,46 +492,47 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC
        progressUpdater.setDuration(timings.getDuration().toLong())
        progressUpdater.interpolator = Interpolators.LINEAR

        // Find the root shell leash that we want to fade in (parent of both app windows and
        // the divider). For simplicity, we search using the initialTaskId.
        var rootShellLayer: SurfaceControl? = null
        var dividerPos = 0
        var rootCandidate: Change? = null

        for (change in transitionInfo.changes) {
            val taskInfo: RunningTaskInfo = change.taskInfo ?: continue
            val taskId = taskInfo.taskId
            val mode = change.mode

            if (taskId == initialTaskId || taskId == secondTaskId) {
                check(
                    mode == WindowManager.TRANSIT_OPEN || mode == WindowManager.TRANSIT_TO_FRONT
                ) {
                    "Expected task to be showing, but it is $mode"
            // TODO (b/316490565): Replace this logic when SplitBounds is available to
            //  startAnimation() and we can know the precise taskIds of launching tasks.
            // Find a change that has WINDOWING_MODE_MULTI_WINDOW.
            if (taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW &&
                (change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT)) {
                // Check if it is a left/top app.
                val isLeftTopApp =
                    (dp.isLeftRightSplit && change.endAbsBounds.left == 0) ||
                        (!dp.isLeftRightSplit && change.endAbsBounds.top == 0)
                if (isLeftTopApp) {
                    // Found one!
                    rootCandidate = change
                    break
                }
            }

            if (taskId == initialTaskId) {
                var splitRoot1 = change
                val parentToken = change.parent
                if (parentToken != null) {
                    splitRoot1 = transitionInfo.getChange(parentToken) ?: change
        }

                val topLevelToken = splitRoot1.parent
                if (topLevelToken != null) {
                    rootShellLayer = transitionInfo.getChange(topLevelToken)?.leash
                }
        // If we could not find a proper root candidate, something went wrong.
        check(rootCandidate != null) { "Could not find a split root candidate" }

                dividerPos =
                    if (dp.isLeftRightSplit) change.endAbsBounds.right
                    else change.endAbsBounds.bottom
            }
        }
        // Find the place where our left/top app window meets the divider (used for the
        // launcher side animation)
        val dividerPos =
            if (dp.isLeftRightSplit) rootCandidate.endAbsBounds.right
            else rootCandidate.endAbsBounds.bottom

        check(rootShellLayer != null) {
            "Could not find a TransitionInfo.Change matching the initialTaskId"
        // Recurse up the tree until parent is null, then we've found our root.
        var parentToken: WindowContainerToken? = rootCandidate.parent
        while (parentToken != null) {
            rootCandidate = transitionInfo.getChange(parentToken) ?: break
            parentToken = rootCandidate.parent
        }

        // Make sure nothing weird happened, like getChange() returning null.
        check(rootCandidate != null) { "Failed to find a root leash" }

        // Shell animation: the apps are revealed toward end of the launch animation
        progressUpdater.addUpdateListener { valueAnimator: ValueAnimator ->
            val progress =
@@ -532,7 +544,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC
                )

            // Set the alpha of the shell layer (2 apps + divider)
            t.setAlpha(rootShellLayer, progress)
            t.setAlpha(rootCandidate.leash, progress)
            t.apply()
        }

@@ -651,9 +663,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC
            // Find the target tasks' root tasks since those are the split stages that need to
            // be animated (the tasks themselves are children and thus inherit animation).
            if (taskId == initialTaskId || taskId == secondTaskId) {
                check(
                    mode == WindowManager.TRANSIT_OPEN || mode == WindowManager.TRANSIT_TO_FRONT
                ) {
                check(mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
                    "Expected task to be showing, but it is $mode"
                }
            }
+2 −2
Original line number Diff line number Diff line
@@ -249,7 +249,7 @@ class SplitAnimationControllerTest {
        val spySplitAnimationController = spy(splitAnimationController)
        doNothing()
            .whenever(spySplitAnimationController)
            .composeIconSplitLaunchAnimator(any(), any(), any(), any(), any(), any())
            .composeIconSplitLaunchAnimator(any(), any(), any(), any())

        spySplitAnimationController.playSplitLaunchAnimation(
            null /* launchingTaskView */,
@@ -267,7 +267,7 @@ class SplitAnimationControllerTest {
        )

        verify(spySplitAnimationController)
            .composeIconSplitLaunchAnimator(any(), any(), any(), any(), any(), any())
            .composeIconSplitLaunchAnimator(any(), any(), any(), any())
    }

    @Test