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

Commit fd46900b authored by Vinit Nayak's avatar Vinit Nayak
Browse files

Support splitting from workspace with Widgets

* Need to insert widget's icon in animation
* Launching w/ same package app + widget is
broken

Test: Launched apps from predicted apps + widget,
hotseat apps + widget
Flag: ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE
Bug: 276361926

Change-Id: I3e30189e56536371ebd0acfbdd2c073a882cc731
parent fc7fa8f3
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -56,6 +56,10 @@ class QuickstepInteractionHandler implements RemoteViews.InteractionHandler {
            return RemoteViews.startPendingIntent(hostView, pendingIntent,
                    remoteResponse.getLaunchOptions(view));
        }
        if (mLauncher.getSplitToWorkspaceController().handleSecondWidgetSelectionForSplit(view,
                pendingIntent)) {
            return true;
        }
        Pair<Intent, ActivityOptions> options = remoteResponse.getLaunchOptions(view);
        ActivityOptionsWrapper activityOptions = mLauncher.getAppTransitionManager()
                .getActivityLaunchOptions(hostView);
+2 −2
Original line number Diff line number Diff line
@@ -970,8 +970,8 @@ public class QuickstepLauncher extends Launcher {
        return mTaskbarUIController;
    }

    public SplitSelectStateController getSplitSelectStateController() {
        return mSplitSelectStateController;
    public SplitToWorkspaceController getSplitToWorkspaceController() {
        return mSplitToWorkspaceController;
    }

    public <T extends OverviewActionsView> T getActionsView() {
+47 −8
Original line number Diff line number Diff line
@@ -97,8 +97,15 @@ public class SplitSelectStateController {
    private UserHandle mInitialUser;
    private int mInitialTaskId = INVALID_TASK_ID;
    /** {@link #mSecondTaskIntent} and {@link #mSecondUser} (the user of the Intent) are set
     * together when split is confirmed with an Intent. */
     * together when split is confirmed with an Intent. Either this or {@link #mSecondPendingIntent}
     * will be set, but not both
     */
    private Intent mSecondTaskIntent;
    /**
     * Set when split is confirmed via a widget. Either this or {@link #mSecondTaskIntent} will be
     * set, but not both
     */
    private PendingIntent mSecondPendingIntent;
    private UserHandle mSecondUser;
    private int mSecondTaskId = INVALID_TASK_ID;
    private boolean mRecentsAnimationRunning;
@@ -246,6 +253,16 @@ public class SplitSelectStateController {
        mSecondUser = user;
    }

    /**
     * To be called as soon as user selects the second app (even if animations aren't complete)
     * Sets {@link #mSecondUser} from that of the pendingIntent
     * @param pendingIntent The second PendingIntent that will be launched.
     */
    public void setSecondTask(PendingIntent pendingIntent) {
        mSecondPendingIntent = pendingIntent;
        mSecondUser = pendingIntent.getCreatorUserHandle();
    }

    /**
     * To be called when we want to launch split pairs from an existing GroupedTaskView.
     */
@@ -290,17 +307,18 @@ public class SplitSelectStateController {
        if (freezeTaskList) {
            options1.setFreezeRecentTasksReordering();
        }
        boolean hasSecondaryPendingIntent = mSecondPendingIntent != null;
        if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
            final RemoteSplitLaunchTransitionRunner animationRunner =
                    new RemoteSplitLaunchTransitionRunner(taskId1, taskId2, callback);
            final RemoteTransition remoteTransition = new RemoteTransition(animationRunner,
                    ActivityThread.currentActivityThread().getApplicationThread(),
                    "LaunchSplitPair");
            if (intent1 == null && intent2 == null) {
            if (intent1 == null && (intent2 == null && !hasSecondaryPendingIntent)) {
                mSystemUiProxy.startTasks(taskId1, options1.toBundle(), taskId2,
                        null /* options2 */, stagePosition, splitRatio, remoteTransition,
                        shellInstanceId);
            } else if (intent2 == null) {
            } else if (intent2 == null && !hasSecondaryPendingIntent) {
                launchIntentOrShortcut(intent1, mInitialUser, options1, taskId2, stagePosition,
                        splitRatio, remoteTransition, shellInstanceId);
            } else if (intent1 == null) {
@@ -310,7 +328,9 @@ public class SplitSelectStateController {
            } else {
                mSystemUiProxy.startIntents(getPendingIntent(intent1, mInitialUser),
                        getShortcutInfo(intent1, mInitialUser), options1.toBundle(),
                        getPendingIntent(intent2, mSecondUser),
                        hasSecondaryPendingIntent
                                ? mSecondPendingIntent
                                : getPendingIntent(intent2, mSecondUser),
                        getShortcutInfo(intent2, mSecondUser), null /* options2 */,
                        stagePosition, splitRatio, remoteTransition, shellInstanceId);
            }
@@ -321,11 +341,11 @@ public class SplitSelectStateController {
                    animationRunner, 300, 150,
                    ActivityThread.currentActivityThread().getApplicationThread());

            if (intent1 == null && intent2 == null) {
            if (intent1 == null && (intent2 == null && !hasSecondaryPendingIntent)) {
                mSystemUiProxy.startTasksWithLegacyTransition(taskId1, options1.toBundle(),
                        taskId2, null /* options2 */, stagePosition, splitRatio, adapter,
                        shellInstanceId);
            } else if (intent2 == null) {
            } else if (intent2 == null && !hasSecondaryPendingIntent) {
                launchIntentOrShortcutLegacy(intent1, mInitialUser, options1, taskId2,
                        stagePosition, splitRatio, adapter, shellInstanceId);
            } else if (intent1 == null) {
@@ -336,7 +356,9 @@ public class SplitSelectStateController {
                mSystemUiProxy.startIntentsWithLegacyTransition(
                        getPendingIntent(intent1, mInitialUser),
                        getShortcutInfo(intent1, mInitialUser), options1.toBundle(),
                        getPendingIntent(intent2, mSecondUser),
                        hasSecondaryPendingIntent
                                ? mSecondPendingIntent
                                : getPendingIntent(intent2, mSecondUser),
                        getShortcutInfo(intent2, mSecondUser), null /* options2 */, stagePosition,
                        splitRatio, adapter, shellInstanceId);
            }
@@ -374,7 +396,22 @@ public class SplitSelectStateController {
        }
    }

    /**
     * We treat launching by intents as grouped in two ways,
     * If {@param intent} represents the first app, we always convert the intent to pending intent
     * It it represents second app, either the second intent OR mSecondPendingIntent will be used
     *    convert second intent to a pendingIntent OR return mSecondPendingIntent as is
     */
    private PendingIntent getPendingIntent(Intent intent, UserHandle user) {
        boolean isParamFirstIntent = intent != null && intent == mInitialTaskIntent;
        if (!isParamFirstIntent && mSecondPendingIntent != null) {
            // Because mSecondPendingIntent and mSecondTaskIntent can't both be set, we know we need
            // to be using mSecondPendingIntent
            return mSecondPendingIntent;
        }

        // intent param must either be mInitialTaskIntent or mSecondTaskIntent, convert either to
        // a new PendingIntent
        return intent == null ? null : (user != null
                ? PendingIntent.getActivityAsUser(mContext, 0, intent,
                FLAG_MUTABLE | FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT, null /* options */, user)
@@ -546,6 +583,7 @@ public class SplitSelectStateController {
        mSplitEvent = null;
        mAnimateCurrentTaskDismissal = false;
        mDismissingFromSplitPair = false;
        mSecondPendingIntent = null;
    }

    /**
@@ -577,7 +615,8 @@ public class SplitSelectStateController {
    }

    private boolean isSecondTaskIntentSet() {
        return (mSecondTaskId != INVALID_TASK_ID || mSecondTaskIntent != null);
        return (mSecondTaskId != INVALID_TASK_ID || mSecondTaskIntent != null
                || mSecondPendingIntent != null);
    }

    public void setFirstFloatingTaskView(FloatingTaskView floatingTaskView) {
+46 −6
Original line number Diff line number Diff line
@@ -21,9 +21,15 @@ import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSP

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.view.View;

@@ -55,14 +61,38 @@ public class SplitToWorkspaceController {
                R.dimen.multi_window_task_divider_size) / 2;
    }

    /**
     * Handles widget selection from staged split.
     * @param view Original widget view
     * @param pendingIntent Provided by widget via InteractionHandler
     * @return {@code true} if we can attempt launch the widget into split, {@code false} otherwise
     *         to allow launcher to handle the click
     */
    public boolean handleSecondWidgetSelectionForSplit(View view, PendingIntent pendingIntent) {
        if (shouldIgnoreSecondSplitLaunch()) {
            return false;
        }

        // Convert original widgetView into bitmap to use for animation
        // TODO(b/276361926) get the icon for this widget via PackageManager?
        int width = view.getWidth();
        int height = view.getHeight();
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        view.draw(canvas);

        mController.setSecondTask(pendingIntent);

        startWorkspaceAnimation(view, bitmap, null /*icon*/);
        return true;
    }

    /**
     * Handles second app selection from stage split. If the item can't be opened in split or
     * it's not in stage split state, we pass it onto Launcher's default item click handler.
     */
    public boolean handleSecondAppSelectionForSplit(View view) {
        if ((!ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS.get()
                && !ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get())
                || !mController.isSplitSelectActive()) {
        if (shouldIgnoreSecondSplitLaunch()) {
            return false;
        }
        Object tag = view.getTag();
@@ -86,6 +116,12 @@ public class SplitToWorkspaceController {

        mController.setSecondTask(intent, user);

        startWorkspaceAnimation(view, null /*bitmap*/, bitmapInfo.newIcon(mLauncher));
        return true;
    }

    private void startWorkspaceAnimation(@NonNull View view, @Nullable Bitmap bitmap,
            @Nullable Drawable icon) {
        boolean isTablet = mLauncher.getDeviceProfile().isTablet;
        SplitAnimationTimings timings = AnimUtils.getDeviceSplitToConfirmTimings(isTablet);
        PendingAnimation pendingAnimation = new PendingAnimation(timings.getDuration());
@@ -107,8 +143,7 @@ public class SplitToWorkspaceController {
                false /* fadeWithThumbnail */, true /* isStagedTask */);

        FloatingTaskView secondFloatingTaskView = FloatingTaskView.getFloatingTaskView(mLauncher,
                view, null /* thumbnail */, bitmapInfo.newIcon(mLauncher),
                secondTaskStartingBounds);
                view, bitmap, icon, secondTaskStartingBounds);
        secondFloatingTaskView.setAlpha(1);
        secondFloatingTaskView.addConfirmAnimation(pendingAnimation, secondTaskStartingBounds,
                secondTaskEndingBounds, true /* fadeWithThumbnail */, false /* isStagedTask */);
@@ -138,6 +173,11 @@ public class SplitToWorkspaceController {
            }
        });
        pendingAnimation.buildAnim().start();
        return true;
    }

    private boolean shouldIgnoreSecondSplitLaunch() {
        return (!ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS.get()
                && !ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get())
                || !mController.isSplitSelectActive();
    }
}
+12 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@
package com.android.quickstep.util

import android.app.ActivityManager
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.Intent
@@ -32,6 +33,8 @@ import com.android.launcher3.statehandlers.DepthController
import com.android.launcher3.statemanager.StateManager
import com.android.launcher3.util.ComponentKey
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition
import com.android.launcher3.util.mock
import com.android.launcher3.util.withArgCaptor
import com.android.quickstep.RecentsModel
import com.android.quickstep.SystemUiProxy
@@ -59,6 +62,7 @@ class SplitSelectStateControllerTest {
    @Mock lateinit var handler: Handler
    @Mock lateinit var context: Context
    @Mock lateinit var recentsModel: RecentsModel
    @Mock lateinit var pendingIntent: PendingIntent

    lateinit var splitSelectStateController: SplitSelectStateController

@@ -348,6 +352,14 @@ class SplitSelectStateControllerTest {
        assertFalse(splitSelectStateController.isSplitSelectActive)
    }

    @Test
    fun secondPendingIntentSet() {
        val itemInfo = ItemInfo()
        splitSelectStateController.setInitialTaskSelect(null, 0, itemInfo, null, 1)
        splitSelectStateController.setSecondTask(pendingIntent)
        assertTrue(splitSelectStateController.isBothSplitAppsConfirmed)
    }

    // Generate GroupTask with default userId.
    private fun generateGroupTask(
        task1ComponentName: ComponentName,