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

Commit 955555a3 authored by Vinit Nayak's avatar Vinit Nayak Committed by Android (Google) Code Review
Browse files

Merge "Support splitting from workspace with Widgets" into udc-dev

parents 6e0f34cc fd46900b
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
@@ -973,8 +973,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
@@ -98,8 +98,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;
@@ -248,6 +255,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.
     */
@@ -292,17 +309,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) {
@@ -312,7 +330,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);
            }
@@ -323,11 +343,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) {
@@ -338,7 +358,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);
            }
@@ -376,7 +398,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)
@@ -548,6 +585,7 @@ public class SplitSelectStateController {
        mSplitEvent = null;
        mAnimateCurrentTaskDismissal = false;
        mDismissingFromSplitPair = false;
        mSecondPendingIntent = null;
    }

    /**
@@ -579,7 +617,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,