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

Commit 7476c8af authored by Vinit Nayak's avatar Vinit Nayak
Browse files

Check if apps are currently in pip before launching split screen

* Which ever app is not pipped will be launched as a single
fullscreen task
* This handles the case where both apps are intent launches (when
nothing is in recents, i.e. app pairs) and task + intent launch (when
non pipped app is in recents). I don't think it's possible to have
task + task launch since the pipped task isn't a selectable split
target
* Add isInPip() check since mTaskInfo in PipOrganizer isn't sufficient
to determine if the app is in pip or not.

Fixes: 323089902
Test: Launched app pair while YT pipped, with second app being in
recents + not in recents. Also launched both apps from overview with
non pipped app being in recents + not recents and also from all apps.
Tested with launching shortcuts (Gmail compose) as well

Change-Id: I930937f331b0078a880fd495a60acb66694d62cb
parent d395057b
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -266,9 +266,9 @@ public abstract class PipTransitionController implements Transitions.TransitionH
    }

    /** Whether a particular package is same as current pip package. */
    public boolean isInPipPackage(String packageName) {
    public boolean isPackageActiveInPip(String packageName) {
        final TaskInfo inPipTask = mPipOrganizer.getTaskInfo();
        return packageName != null && inPipTask != null
        return packageName != null && inPipTask != null && mPipOrganizer.isInPip()
                && packageName.equals(SplitScreenUtils.getPackageName(inPipTask.baseIntent));
    }

+91 −18
Original line number Diff line number Diff line
@@ -199,7 +199,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
    private final DisplayImeController mDisplayImeController;
    private final DisplayInsetsController mDisplayInsetsController;
    private final TransactionPool mTransactionPool;
    private final SplitScreenTransitions mSplitTransitions;
    private SplitScreenTransitions mSplitTransitions;
    private final SplitscreenEventLogger mLogger;
    private final ShellExecutor mMainExecutor;
    // Cache live tile tasks while entering recents, evict them from stages in finish transaction
@@ -397,6 +397,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        return mSplitTransitions;
    }

    @VisibleForTesting
    void setSplitTransitions(SplitScreenTransitions splitScreenTransitions) {
        mSplitTransitions = splitScreenTransitions;
    }

    public boolean isSplitScreenVisible() {
        return mSideStageListener.mVisible && mMainStageListener.mVisible;
    }
@@ -581,7 +586,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
        wct.startTask(taskId, options);
        // If this should be mixed, send the task to avoid split handle transition directly.
        if (mMixedHandler != null && mMixedHandler.shouldSplitEnterMixed(taskId, mTaskOrganizer)) {
        if (mMixedHandler != null && mMixedHandler.isTaskInPip(taskId, mTaskOrganizer)) {
            mTaskOrganizer.applyTransaction(wct);
            return;
        }
@@ -620,7 +625,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        wct.sendPendingIntent(intent, fillInIntent, options);

        // If this should be mixed, just send the intent to avoid split handle transition directly.
        if (mMixedHandler != null && mMixedHandler.shouldSplitEnterMixed(intent)) {
        if (mMixedHandler != null && mMixedHandler.isIntentInPip(intent)) {
            mTaskOrganizer.applyTransaction(wct);
            return;
        }
@@ -709,16 +714,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
                taskId1, taskId2, splitPosition, snapPosition);
        final WindowContainerTransaction wct = new WindowContainerTransaction();
        if (taskId2 == INVALID_TASK_ID) {
            if (mMainStage.containsTask(taskId1) || mSideStage.containsTask(taskId1)) {
                prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
            }
            if (mRecentTasks.isPresent()) {
                mRecentTasks.get().removeSplitPair(taskId1);
            }
            options1 = options1 != null ? options1 : new Bundle();
            addActivityOptions(options1, null);
            wct.startTask(taskId1, options1);
            mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
            startSingleTask(taskId1, options1, wct, remoteTransition);
            return;
        }

@@ -739,11 +735,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
                "startIntentAndTask: intent=%s task1=%d position=%d snapPosition=%d",
                pendingIntent.getIntent(), taskId, splitPosition, snapPosition);
        final WindowContainerTransaction wct = new WindowContainerTransaction();
        if (taskId == INVALID_TASK_ID) {
            options1 = options1 != null ? options1 : new Bundle();
            addActivityOptions(options1, null);
            wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
            mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
        boolean firstIntentPipped = mMixedHandler.isIntentInPip(pendingIntent);
        boolean secondTaskPipped = mMixedHandler.isTaskInPip(taskId, mTaskOrganizer);
        if (taskId == INVALID_TASK_ID || secondTaskPipped) {
            startSingleIntent(pendingIntent, fillInIntent, options1, wct, remoteTransition);
            return;
        }

        if (firstIntentPipped) {
            startSingleTask(taskId, options2, wct, remoteTransition);
            return;
        }

@@ -755,6 +755,24 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        startWithTask(wct, taskId, options2, snapPosition, remoteTransition, instanceId);
    }

    /**
     * @param taskId Starts this task in fullscreen, removing it from existing pairs if it was part
     *               of one.
     */
    private void startSingleTask(int taskId, Bundle options, WindowContainerTransaction wct,
            RemoteTransition remoteTransition) {
        if (mMainStage.containsTask(taskId) || mSideStage.containsTask(taskId)) {
            prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
        }
        if (mRecentTasks.isPresent()) {
            mRecentTasks.get().removeSplitPair(taskId);
        }
        options = options != null ? options : new Bundle();
        addActivityOptions(options, null);
        wct.startTask(taskId, options);
        mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
    }

    /** Starts a shortcut and a task to a split pair in one transition. */
    void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
            int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
@@ -842,6 +860,21 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
            return;
        }

        boolean handledForPipSplitLaunch = handlePippedSplitIntentsLaunch(
                pendingIntent1,
                pendingIntent2,
                options1,
                options2,
                shortcutInfo1,
                shortcutInfo2,
                wct,
                fillInIntent1,
                fillInIntent2,
                remoteTransition);
        if (handledForPipSplitLaunch) {
            return;
        }

        if (!mMainStage.isActive()) {
            // Build a request WCT that will launch both apps such that task 0 is on the main stage
            // while task 1 is on the side stage.
@@ -876,6 +909,46 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        setEnterInstanceId(instanceId);
    }

    /**
     * Checks if either of the apps in the desired split launch is currently in Pip. If so, it will
     * launch the non-pipped app as a fullscreen app, otherwise no-op.
     */
    private boolean handlePippedSplitIntentsLaunch(PendingIntent pendingIntent1,
            PendingIntent pendingIntent2, Bundle options1, Bundle options2,
            ShortcutInfo shortcutInfo1, ShortcutInfo shortcutInfo2, WindowContainerTransaction wct,
            Intent fillInIntent1, Intent fillInIntent2, RemoteTransition remoteTransition) {
        // If one of the split apps to start is in Pip, only launch the non-pip app in fullscreen
        boolean firstIntentPipped = mMixedHandler.isIntentInPip(pendingIntent1);
        boolean secondIntentPipped = mMixedHandler.isIntentInPip(pendingIntent2);
        if (firstIntentPipped || secondIntentPipped) {
            Bundle options = secondIntentPipped ? options1 : options2;
            options = options == null ? new Bundle() : options;
            addActivityOptions(options, null);
            if (shortcutInfo1 != null || shortcutInfo2 != null) {
                ShortcutInfo infoToLaunch = secondIntentPipped ? shortcutInfo1 : shortcutInfo2;
                wct.startShortcut(mContext.getPackageName(), infoToLaunch, options);
                mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
            } else {
                PendingIntent intentToLaunch = secondIntentPipped ? pendingIntent1 : pendingIntent2;
                Intent fillInIntentToLaunch = secondIntentPipped ? fillInIntent1 : fillInIntent2;
                startSingleIntent(intentToLaunch, fillInIntentToLaunch, options, wct,
                        remoteTransition);
            }
            return true;
        }
        return false;
    }

    /** @param pendingIntent Starts this intent in fullscreen */
    private void startSingleIntent(PendingIntent pendingIntent, Intent fillInIntent, Bundle options,
            WindowContainerTransaction wct,
            RemoteTransition remoteTransition) {
        Bundle optionsToLaunch = options != null ? options : new Bundle();
        addActivityOptions(optionsToLaunch, null);
        wct.sendPendingIntent(pendingIntent, fillInIntent, optionsToLaunch);
        mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
    }

    /** Starts a pair of tasks using legacy transition. */
    void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
            int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
+5 −4
Original line number Diff line number Diff line
@@ -562,22 +562,23 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,

    /** Use to when split use intent to enter, check if this enter transition should be mixed or
     * not.*/
    public boolean shouldSplitEnterMixed(PendingIntent intent) {
    public boolean isIntentInPip(PendingIntent intent) {
        // Check if this intent package is same as pip one or not, if true we want let the pip
        // task enter split.
        if (mPipHandler != null) {
            return mPipHandler.isInPipPackage(SplitScreenUtils.getPackageName(intent.getIntent()));
            return mPipHandler
                    .isPackageActiveInPip(SplitScreenUtils.getPackageName(intent.getIntent()));
        }
        return false;
    }

    /** Use to when split use taskId to enter, check if this enter transition should be mixed or
     * not.*/
    public boolean shouldSplitEnterMixed(int taskId, ShellTaskOrganizer shellTaskOrganizer) {
    public boolean isTaskInPip(int taskId, ShellTaskOrganizer shellTaskOrganizer) {
        // Check if this intent package is same as pip one or not, if true we want let the pip
        // task enter split.
        if (mPipHandler != null) {
            return mPipHandler.isInPipPackage(
            return mPipHandler.isPackageActiveInPip(
                    SplitScreenUtils.getPackageName(taskId, shellTaskOrganizer));
        }
        return false;
+96 −0
Original line number Diff line number Diff line
@@ -40,10 +40,12 @@ import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.ActivityManager;
import android.app.PendingIntent;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
@@ -51,6 +53,7 @@ import android.os.Handler;
import android.os.Looper;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.RemoteTransition;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;

@@ -74,6 +77,7 @@ import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.DefaultMixedHandler;
import com.android.wm.shell.transition.HomeTransitionObserver;
import com.android.wm.shell.transition.Transitions;

@@ -111,6 +115,8 @@ public class StageCoordinatorTests extends ShellTestCase {
    private TransactionPool mTransactionPool;
    @Mock
    private LaunchAdjacentController mLaunchAdjacentController;
    @Mock
    private DefaultMixedHandler mDefaultMixedHandler;

    private final Rect mBounds1 = new Rect(10, 20, 30, 40);
    private final Rect mBounds2 = new Rect(5, 10, 15, 20);
@@ -370,6 +376,96 @@ public class StageCoordinatorTests extends ShellTestCase {
        }
    }

    @Test
    public void testSplitIntentAndTaskWithPippedApp_launchFullscreen() {
        int taskId = 9;
        SplitScreenTransitions splitScreenTransitions =
                spy(mStageCoordinator.getSplitTransitions());
        mStageCoordinator.setSplitTransitions(splitScreenTransitions);
        mStageCoordinator.setMixedHandler(mDefaultMixedHandler);
        PendingIntent pendingIntent = mock(PendingIntent.class);
        RemoteTransition remoteTransition = mock(RemoteTransition.class);
        when(remoteTransition.getDebugName()).thenReturn("");
        // Test launching second task full screen
        when(mDefaultMixedHandler.isIntentInPip(pendingIntent)).thenReturn(true);
        mStageCoordinator.startIntentAndTask(
                pendingIntent,
                null /*fillInIntent*/,
                null /*option1*/,
                taskId,
                null /*option2*/,
                0 /*splitPosition*/,
                1 /*snapPosition*/,
                remoteTransition /*remoteTransition*/,
                null /*instanceId*/);
        verify(splitScreenTransitions, times(1))
                .startFullscreenTransition(any(), any());

        // Test launching first intent fullscreen
        when(mDefaultMixedHandler.isIntentInPip(pendingIntent)).thenReturn(false);
        when(mDefaultMixedHandler.isTaskInPip(taskId, mTaskOrganizer)).thenReturn(true);
        mStageCoordinator.startIntentAndTask(
                pendingIntent,
                null /*fillInIntent*/,
                null /*option1*/,
                taskId,
                null /*option2*/,
                0 /*splitPosition*/,
                1 /*snapPosition*/,
                remoteTransition /*remoteTransition*/,
                null /*instanceId*/);
        verify(splitScreenTransitions, times(2))
                .startFullscreenTransition(any(), any());
    }

    @Test
    public void testSplitIntentsWithPippedApp_launchFullscreen() {
        SplitScreenTransitions splitScreenTransitions =
                spy(mStageCoordinator.getSplitTransitions());
        mStageCoordinator.setSplitTransitions(splitScreenTransitions);
        mStageCoordinator.setMixedHandler(mDefaultMixedHandler);
        PendingIntent pendingIntent = mock(PendingIntent.class);
        PendingIntent pendingIntent2 = mock(PendingIntent.class);
        RemoteTransition remoteTransition = mock(RemoteTransition.class);
        when(remoteTransition.getDebugName()).thenReturn("");
        // Test launching second task full screen
        when(mDefaultMixedHandler.isIntentInPip(pendingIntent)).thenReturn(true);
        mStageCoordinator.startIntents(
                pendingIntent,
                null /*fillInIntent*/,
                null /*shortcutInfo1*/,
                new Bundle(),
                pendingIntent2,
                null /*fillInIntent2*/,
                null /*shortcutInfo1*/,
                new Bundle(),
                0 /*splitPosition*/,
                1 /*snapPosition*/,
                remoteTransition /*remoteTransition*/,
                null /*instanceId*/);
        verify(splitScreenTransitions, times(1))
                .startFullscreenTransition(any(), any());

        // Test launching first intent fullscreen
        when(mDefaultMixedHandler.isIntentInPip(pendingIntent)).thenReturn(false);
        when(mDefaultMixedHandler.isIntentInPip(pendingIntent2)).thenReturn(true);
        mStageCoordinator.startIntents(
                pendingIntent,
                null /*fillInIntent*/,
                null /*shortcutInfo1*/,
                new Bundle(),
                pendingIntent2,
                null /*fillInIntent2*/,
                null /*shortcutInfo1*/,
                new Bundle(),
                0 /*splitPosition*/,
                1 /*snapPosition*/,
                remoteTransition /*remoteTransition*/,
                null /*instanceId*/);
        verify(splitScreenTransitions, times(2))
                .startFullscreenTransition(any(), any());
    }

    private Transitions createTestTransitions() {
        ShellInit shellInit = new ShellInit(mMainExecutor);
        final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),