Loading libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java +1 −43 Original line number Diff line number Diff line Loading @@ -45,12 +45,10 @@ import android.app.WindowConfiguration; import android.content.ActivityNotFoundException; import android.content.ClipData; import android.content.ClipDescription; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.LauncherApps; import android.content.pm.ResolveInfo; import android.graphics.Insets; import android.graphics.Rect; import android.os.Bundle; Loading @@ -64,19 +62,15 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.internal.logging.InstanceId; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.split.SplitLayout; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.SplitScreenController; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.Objects; /** * The policy for handling drag and drop operations to shell. Loading Loading @@ -269,46 +263,10 @@ public class DragAndDropPolicy { mStarter.startShortcut(packageName, id, position, opts, user); } else { final PendingIntent launchIntent = intent.getParcelableExtra(EXTRA_PENDING_INTENT); mStarter.startIntent(launchIntent, getStartIntentFillInIntent(launchIntent, position), position, opts); mStarter.startIntent(launchIntent, null /* fillIntent */, position, opts); } } /** * Returns the fill-in intent to use when starting an app from a drop. */ @VisibleForTesting Intent getStartIntentFillInIntent(PendingIntent launchIntent, @SplitPosition int position) { // Get the drag app final List<ResolveInfo> infos = launchIntent.queryIntentComponents(0 /* flags */); final ComponentName dragIntentActivity = !infos.isEmpty() ? infos.get(0).activityInfo.getComponentName() : null; // Get the current app (either fullscreen or the remaining app post-drop if in splitscreen) final boolean inSplitScreen = mSplitScreen != null && mSplitScreen.isSplitScreenVisible(); final ComponentName currentActivity; if (!inSplitScreen) { currentActivity = mSession.runningTaskInfo != null ? mSession.runningTaskInfo.baseIntent.getComponent() : null; } else { final ActivityManager.RunningTaskInfo nonReplacedTaskInfo = mSplitScreen.getTaskInfo(SplitLayout.reversePosition(position)); currentActivity = nonReplacedTaskInfo.baseIntent.getComponent(); } if (Objects.equals(currentActivity, dragIntentActivity)) { // Only apply MULTIPLE_TASK if we are dragging the same activity final Intent fillInIntent = new Intent(); fillInIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Adding MULTIPLE_TASK"); return fillInIntent; } return null; } /** * Per-drag session data. */ Loading libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +64 −12 Original line number Diff line number Diff line Loading @@ -18,6 +18,9 @@ package com.android.wm.shell.splitscreen; import static android.app.ActivityManager.START_SUCCESS; import static android.app.ActivityManager.START_TASK_TO_FRONT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.RemoteAnimationTarget.MODE_OPENING; Loading Loading @@ -58,7 +61,9 @@ import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.InstanceId; import com.android.internal.protolog.common.ProtoLog; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; Loading @@ -74,6 +79,7 @@ import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.common.split.SplitLayout; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.draganddrop.DragAndDropPolicy; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreen.StageType; import com.android.wm.shell.transition.LegacyTransitions; Loading @@ -95,7 +101,7 @@ import java.util.concurrent.Executor; */ // TODO(b/198577848): Implement split screen flicker test to consolidate CUJ of split screen. public class SplitScreenController implements DragAndDropPolicy.Starter, RemoteCallable<SplitScreenController> { RemoteCallable<SplitScreenController>, ShellTaskOrganizer.FocusListener { private static final String TAG = SplitScreenController.class.getSimpleName(); static final int EXIT_REASON_UNKNOWN = 0; Loading Loading @@ -143,6 +149,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, // outside the bounds of the roots by being reparented into a higher level fullscreen container private SurfaceControl mSplitTasksContainerLayer; private ActivityManager.RunningTaskInfo mFocusingTaskInfo; public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, Context context, RootTaskDisplayAreaOrganizer rootTDAOrganizer, Loading @@ -164,6 +172,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mLogger = new SplitscreenEventLogger(); mIconProvider = iconProvider; mRecentTasksOptional = recentTasks; mTaskOrganizer.addFocusListener(this); } public SplitScreen asSplitScreen() { Loading @@ -180,6 +189,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return mMainExecutor; } @Override public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) { mFocusingTaskInfo = taskInfo; } public void onOrganizerRegistered() { if (mStageCoordinator == null) { // TODO: Multi-display Loading Loading @@ -341,14 +355,21 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */); // Flag this as a no-user-action launch to prevent sending user leaving event to the // current top activity since it's going to be put into another side of the split. This // prevents the current top activity from going into pip mode due to user leaving event. if (fillInIntent == null) { fillInIntent = new Intent(); } // Flag this as a no-user-action launch to prevent sending user leaving event to the // current top activity since it's going to be put into another side of the split. This // prevents the current top activity from going into pip mode due to user leaving event. fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION); // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the // split. if (isLaunchingAdjacently(intent.getIntent(), position)) { fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } intent.send(mContext, 0, fillInIntent, null /* onFinished */, null /* handler */, null /* requiredPermission */, options); } catch (PendingIntent.CanceledException e) { Loading @@ -358,6 +379,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, private void startIntentLegacy(PendingIntent intent, @Nullable Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options) { boolean startSameActivityAdjacently = isLaunchingAdjacently(intent.getIntent(), position); final WindowContainerTransaction evictWct = new WindowContainerTransaction(); mStageCoordinator.prepareEvictChildTasks(position, evictWct); Loading @@ -368,14 +391,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, IRemoteAnimationFinishedCallback finishedCallback, SurfaceControl.Transaction t) { if (apps == null || apps.length == 0) { final ActivityManager.RunningTaskInfo pairedTaskInfo = getTaskInfo(SplitLayout.reversePosition(position)); final ComponentName pairedActivity = pairedTaskInfo != null ? pairedTaskInfo.baseIntent.getComponent() : null; final ComponentName intentActivity = intent.getIntent() != null ? intent.getIntent().getComponent() : null; if (Objects.equals(pairedActivity, intentActivity)) { if (startSameActivityAdjacently) { // Switch split position if dragging the same activity to another side. setSideStagePosition(SplitLayout.reversePosition( mStageCoordinator.getSideStagePosition())); Loading Loading @@ -417,11 +433,47 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, fillInIntent = new Intent(); } fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION); if (startSameActivityAdjacently) { fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } wct.sendPendingIntent(intent, fillInIntent, options); mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct); } /** Returns {@code true} if it's launching the same component on both sides of the split. */ @VisibleForTesting boolean isLaunchingAdjacently(@Nullable Intent startIntent, @SplitPosition int position) { if (startIntent == null) { return false; } final ComponentName launchingActivity = startIntent.getComponent(); if (launchingActivity == null) { return false; } if (isSplitScreenVisible()) { final ActivityManager.RunningTaskInfo pairedTaskInfo = getTaskInfo(SplitLayout.reversePosition(position)); final ComponentName pairedActivity = pairedTaskInfo != null ? pairedTaskInfo.baseIntent.getComponent() : null; return Objects.equals(launchingActivity, pairedActivity); } if (mFocusingTaskInfo != null // TODO (b/238032411): have an API to determine whether an activity is valid for // split screen or not. && mFocusingTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN && mFocusingTaskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) { return Objects.equals(mFocusingTaskInfo.baseIntent.getComponent(), launchingActivity); } return false; } RemoteAnimationTarget[] onGoingToRecentsLegacy(RemoteAnimationTarget[] apps) { if (isSplitScreenVisible()) { // Evict child tasks except the top visible one under split root to ensure it could be Loading libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +6 −5 Original line number Diff line number Diff line Loading @@ -200,11 +200,6 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { @Override @CallSuper public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { if (!taskInfo.supportsMultiWindow) { // Leave split screen if the task no longer supports multi window. mCallbacks.onNoLongerSupportMultiWindow(); return; } if (mRootTaskInfo.taskId == taskInfo.taskId) { // Inflates split decor view only when the root task is visible. if (mRootTaskInfo.isVisible != taskInfo.isVisible) { Loading @@ -217,6 +212,12 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { } mRootTaskInfo = taskInfo; } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) { if (!taskInfo.supportsMultiWindow) { // Leave split screen if the task no longer supports multi window. mCallbacks.onNoLongerSupportMultiWindow(); return; } mChildrenTaskInfo.put(taskInfo.taskId, taskInfo); mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */, taskInfo.isVisible); Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java +0 −58 Original line number Diff line number Diff line Loading @@ -34,7 +34,6 @@ import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPL import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; Loading @@ -57,7 +56,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Insets; Loading Loading @@ -265,62 +263,6 @@ public class DragAndDropPolicyTest { } } @Test public void testLaunchMultipleTask_differentActivity() { setRunningTask(mFullscreenAppTask); mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId); Intent fillInIntent = mPolicy.getStartIntentFillInIntent(mock(PendingIntent.class), 0); assertNull(fillInIntent); } @Test public void testLaunchMultipleTask_differentActivity_inSplitscreen() { setRunningTask(mFullscreenAppTask); doReturn(true).when(mSplitScreenStarter).isSplitScreenVisible(); doReturn(mFullscreenAppTask).when(mSplitScreenStarter).getTaskInfo(anyInt()); mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId); Intent fillInIntent = mPolicy.getStartIntentFillInIntent(mock(PendingIntent.class), 0); assertNull(fillInIntent); } @Test public void testLaunchMultipleTask_sameActivity() { setRunningTask(mFullscreenAppTask); // Replace the mocked drag pending intent and ensure it resolves to the same activity PendingIntent launchIntent = mock(PendingIntent.class); ResolveInfo launchInfo = new ResolveInfo(); launchInfo.activityInfo = mFullscreenAppTask.topActivityInfo; doReturn(Collections.singletonList(launchInfo)) .when(launchIntent).queryIntentComponents(anyInt()); mActivityClipData.getItemAt(0).getIntent().putExtra(ClipDescription.EXTRA_PENDING_INTENT, launchIntent); mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId); Intent fillInIntent = mPolicy.getStartIntentFillInIntent(launchIntent, 0); assertTrue((fillInIntent.getFlags() & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0); } @Test public void testLaunchMultipleTask_sameActivity_inSplitScreen() { setRunningTask(mFullscreenAppTask); // Replace the mocked drag pending intent and ensure it resolves to the same activity PendingIntent launchIntent = mock(PendingIntent.class); ResolveInfo launchInfo = new ResolveInfo(); launchInfo.activityInfo = mFullscreenAppTask.topActivityInfo; doReturn(Collections.singletonList(launchInfo)) .when(launchIntent).queryIntentComponents(anyInt()); mActivityClipData.getItemAt(0).getIntent().putExtra(ClipDescription.EXTRA_PENDING_INTENT, launchIntent); doReturn(true).when(mSplitScreenStarter).isSplitScreenVisible(); doReturn(mFullscreenAppTask).when(mSplitScreenStarter).getTaskInfo(anyInt()); mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId); Intent fillInIntent = mPolicy.getStartIntentFillInIntent(launchIntent, 0); assertTrue((fillInIntent.getFlags() & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0); } private Target filterTargetByType(ArrayList<Target> targets, int type) { for (Target t : targets) { if (type == t.type) { Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java 0 → 100644 +152 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.wm.shell.splitscreen; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import android.app.ActivityManager; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.transition.Transitions; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.Optional; /** * Tests for {@link SplitScreenController} */ @SmallTest @RunWith(AndroidJUnit4.class) public class SplitScreenControllerTests extends ShellTestCase { @Mock ShellTaskOrganizer mTaskOrganizer; @Mock SyncTransactionQueue mSyncQueue; @Mock RootTaskDisplayAreaOrganizer mRootTDAOrganizer; @Mock ShellExecutor mMainExecutor; @Mock DisplayController mDisplayController; @Mock DisplayImeController mDisplayImeController; @Mock DisplayInsetsController mDisplayInsetsController; @Mock Transitions mTransitions; @Mock TransactionPool mTransactionPool; @Mock IconProvider mIconProvider; @Mock Optional<RecentTasksController> mRecentTasks; private SplitScreenController mSplitScreenController; @Before public void setup() { MockitoAnnotations.initMocks(this); mSplitScreenController = spy(new SplitScreenController(mTaskOrganizer, mSyncQueue, mContext, mRootTDAOrganizer, mMainExecutor, mDisplayController, mDisplayImeController, mDisplayInsetsController, mTransitions, mTransactionPool, mIconProvider, mRecentTasks)); } @Test public void testIsLaunchingAdjacently_notInSplitScreen() { doReturn(false).when(mSplitScreenController).isSplitScreenVisible(); // Verify launching the same activity returns true. Intent startIntent = createStartIntent("startActivity"); ActivityManager.RunningTaskInfo focusTaskInfo = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent); mSplitScreenController.onFocusTaskChanged(focusTaskInfo); assertTrue(mSplitScreenController.isLaunchingAdjacently( startIntent, SPLIT_POSITION_TOP_OR_LEFT)); // Verify launching different activity returns false. Intent diffIntent = createStartIntent("diffActivity"); focusTaskInfo = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, diffIntent); mSplitScreenController.onFocusTaskChanged(focusTaskInfo); assertFalse(mSplitScreenController.isLaunchingAdjacently( startIntent, SPLIT_POSITION_TOP_OR_LEFT)); } @Test public void testIsLaunchingAdjacently_inSplitScreen() { doReturn(true).when(mSplitScreenController).isSplitScreenVisible(); // Verify launching the same activity returns true. Intent startIntent = createStartIntent("startActivity"); ActivityManager.RunningTaskInfo pairingTaskInfo = createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, startIntent); doReturn(pairingTaskInfo).when(mSplitScreenController).getTaskInfo(anyInt()); assertTrue(mSplitScreenController.isLaunchingAdjacently( startIntent, SPLIT_POSITION_TOP_OR_LEFT)); // Verify launching different activity returns false. Intent diffIntent = createStartIntent("diffActivity"); pairingTaskInfo = createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, diffIntent); doReturn(pairingTaskInfo).when(mSplitScreenController).getTaskInfo(anyInt()); assertFalse(mSplitScreenController.isLaunchingAdjacently( startIntent, SPLIT_POSITION_TOP_OR_LEFT)); } private Intent createStartIntent(String activityName) { Intent intent = new Intent(); intent.setComponent(new ComponentName(mContext, activityName)); return intent; } private ActivityManager.RunningTaskInfo createTaskInfo(int winMode, int actType, Intent strIntent) { ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo(); info.configuration.windowConfiguration.setActivityType(actType); info.configuration.windowConfiguration.setWindowingMode(winMode); info.supportsMultiWindow = true; info.baseIntent = strIntent; info.baseActivity = strIntent.getComponent(); ActivityInfo activityInfo = new ActivityInfo(); activityInfo.packageName = info.baseActivity.getPackageName(); activityInfo.name = info.baseActivity.getClassName(); info.topActivityInfo = activityInfo; return info; } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java +1 −43 Original line number Diff line number Diff line Loading @@ -45,12 +45,10 @@ import android.app.WindowConfiguration; import android.content.ActivityNotFoundException; import android.content.ClipData; import android.content.ClipDescription; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.LauncherApps; import android.content.pm.ResolveInfo; import android.graphics.Insets; import android.graphics.Rect; import android.os.Bundle; Loading @@ -64,19 +62,15 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.internal.logging.InstanceId; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.split.SplitLayout; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.SplitScreenController; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.Objects; /** * The policy for handling drag and drop operations to shell. Loading Loading @@ -269,46 +263,10 @@ public class DragAndDropPolicy { mStarter.startShortcut(packageName, id, position, opts, user); } else { final PendingIntent launchIntent = intent.getParcelableExtra(EXTRA_PENDING_INTENT); mStarter.startIntent(launchIntent, getStartIntentFillInIntent(launchIntent, position), position, opts); mStarter.startIntent(launchIntent, null /* fillIntent */, position, opts); } } /** * Returns the fill-in intent to use when starting an app from a drop. */ @VisibleForTesting Intent getStartIntentFillInIntent(PendingIntent launchIntent, @SplitPosition int position) { // Get the drag app final List<ResolveInfo> infos = launchIntent.queryIntentComponents(0 /* flags */); final ComponentName dragIntentActivity = !infos.isEmpty() ? infos.get(0).activityInfo.getComponentName() : null; // Get the current app (either fullscreen or the remaining app post-drop if in splitscreen) final boolean inSplitScreen = mSplitScreen != null && mSplitScreen.isSplitScreenVisible(); final ComponentName currentActivity; if (!inSplitScreen) { currentActivity = mSession.runningTaskInfo != null ? mSession.runningTaskInfo.baseIntent.getComponent() : null; } else { final ActivityManager.RunningTaskInfo nonReplacedTaskInfo = mSplitScreen.getTaskInfo(SplitLayout.reversePosition(position)); currentActivity = nonReplacedTaskInfo.baseIntent.getComponent(); } if (Objects.equals(currentActivity, dragIntentActivity)) { // Only apply MULTIPLE_TASK if we are dragging the same activity final Intent fillInIntent = new Intent(); fillInIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Adding MULTIPLE_TASK"); return fillInIntent; } return null; } /** * Per-drag session data. */ Loading
libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +64 −12 Original line number Diff line number Diff line Loading @@ -18,6 +18,9 @@ package com.android.wm.shell.splitscreen; import static android.app.ActivityManager.START_SUCCESS; import static android.app.ActivityManager.START_TASK_TO_FRONT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.RemoteAnimationTarget.MODE_OPENING; Loading Loading @@ -58,7 +61,9 @@ import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.InstanceId; import com.android.internal.protolog.common.ProtoLog; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; Loading @@ -74,6 +79,7 @@ import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.common.split.SplitLayout; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.draganddrop.DragAndDropPolicy; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreen.StageType; import com.android.wm.shell.transition.LegacyTransitions; Loading @@ -95,7 +101,7 @@ import java.util.concurrent.Executor; */ // TODO(b/198577848): Implement split screen flicker test to consolidate CUJ of split screen. public class SplitScreenController implements DragAndDropPolicy.Starter, RemoteCallable<SplitScreenController> { RemoteCallable<SplitScreenController>, ShellTaskOrganizer.FocusListener { private static final String TAG = SplitScreenController.class.getSimpleName(); static final int EXIT_REASON_UNKNOWN = 0; Loading Loading @@ -143,6 +149,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, // outside the bounds of the roots by being reparented into a higher level fullscreen container private SurfaceControl mSplitTasksContainerLayer; private ActivityManager.RunningTaskInfo mFocusingTaskInfo; public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, Context context, RootTaskDisplayAreaOrganizer rootTDAOrganizer, Loading @@ -164,6 +172,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mLogger = new SplitscreenEventLogger(); mIconProvider = iconProvider; mRecentTasksOptional = recentTasks; mTaskOrganizer.addFocusListener(this); } public SplitScreen asSplitScreen() { Loading @@ -180,6 +189,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return mMainExecutor; } @Override public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) { mFocusingTaskInfo = taskInfo; } public void onOrganizerRegistered() { if (mStageCoordinator == null) { // TODO: Multi-display Loading Loading @@ -341,14 +355,21 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */); // Flag this as a no-user-action launch to prevent sending user leaving event to the // current top activity since it's going to be put into another side of the split. This // prevents the current top activity from going into pip mode due to user leaving event. if (fillInIntent == null) { fillInIntent = new Intent(); } // Flag this as a no-user-action launch to prevent sending user leaving event to the // current top activity since it's going to be put into another side of the split. This // prevents the current top activity from going into pip mode due to user leaving event. fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION); // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the // split. if (isLaunchingAdjacently(intent.getIntent(), position)) { fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } intent.send(mContext, 0, fillInIntent, null /* onFinished */, null /* handler */, null /* requiredPermission */, options); } catch (PendingIntent.CanceledException e) { Loading @@ -358,6 +379,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, private void startIntentLegacy(PendingIntent intent, @Nullable Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options) { boolean startSameActivityAdjacently = isLaunchingAdjacently(intent.getIntent(), position); final WindowContainerTransaction evictWct = new WindowContainerTransaction(); mStageCoordinator.prepareEvictChildTasks(position, evictWct); Loading @@ -368,14 +391,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, IRemoteAnimationFinishedCallback finishedCallback, SurfaceControl.Transaction t) { if (apps == null || apps.length == 0) { final ActivityManager.RunningTaskInfo pairedTaskInfo = getTaskInfo(SplitLayout.reversePosition(position)); final ComponentName pairedActivity = pairedTaskInfo != null ? pairedTaskInfo.baseIntent.getComponent() : null; final ComponentName intentActivity = intent.getIntent() != null ? intent.getIntent().getComponent() : null; if (Objects.equals(pairedActivity, intentActivity)) { if (startSameActivityAdjacently) { // Switch split position if dragging the same activity to another side. setSideStagePosition(SplitLayout.reversePosition( mStageCoordinator.getSideStagePosition())); Loading Loading @@ -417,11 +433,47 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, fillInIntent = new Intent(); } fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION); if (startSameActivityAdjacently) { fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } wct.sendPendingIntent(intent, fillInIntent, options); mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct); } /** Returns {@code true} if it's launching the same component on both sides of the split. */ @VisibleForTesting boolean isLaunchingAdjacently(@Nullable Intent startIntent, @SplitPosition int position) { if (startIntent == null) { return false; } final ComponentName launchingActivity = startIntent.getComponent(); if (launchingActivity == null) { return false; } if (isSplitScreenVisible()) { final ActivityManager.RunningTaskInfo pairedTaskInfo = getTaskInfo(SplitLayout.reversePosition(position)); final ComponentName pairedActivity = pairedTaskInfo != null ? pairedTaskInfo.baseIntent.getComponent() : null; return Objects.equals(launchingActivity, pairedActivity); } if (mFocusingTaskInfo != null // TODO (b/238032411): have an API to determine whether an activity is valid for // split screen or not. && mFocusingTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN && mFocusingTaskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) { return Objects.equals(mFocusingTaskInfo.baseIntent.getComponent(), launchingActivity); } return false; } RemoteAnimationTarget[] onGoingToRecentsLegacy(RemoteAnimationTarget[] apps) { if (isSplitScreenVisible()) { // Evict child tasks except the top visible one under split root to ensure it could be Loading
libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +6 −5 Original line number Diff line number Diff line Loading @@ -200,11 +200,6 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { @Override @CallSuper public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { if (!taskInfo.supportsMultiWindow) { // Leave split screen if the task no longer supports multi window. mCallbacks.onNoLongerSupportMultiWindow(); return; } if (mRootTaskInfo.taskId == taskInfo.taskId) { // Inflates split decor view only when the root task is visible. if (mRootTaskInfo.isVisible != taskInfo.isVisible) { Loading @@ -217,6 +212,12 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { } mRootTaskInfo = taskInfo; } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) { if (!taskInfo.supportsMultiWindow) { // Leave split screen if the task no longer supports multi window. mCallbacks.onNoLongerSupportMultiWindow(); return; } mChildrenTaskInfo.put(taskInfo.taskId, taskInfo); mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */, taskInfo.isVisible); Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java +0 −58 Original line number Diff line number Diff line Loading @@ -34,7 +34,6 @@ import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPL import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; Loading @@ -57,7 +56,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Insets; Loading Loading @@ -265,62 +263,6 @@ public class DragAndDropPolicyTest { } } @Test public void testLaunchMultipleTask_differentActivity() { setRunningTask(mFullscreenAppTask); mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId); Intent fillInIntent = mPolicy.getStartIntentFillInIntent(mock(PendingIntent.class), 0); assertNull(fillInIntent); } @Test public void testLaunchMultipleTask_differentActivity_inSplitscreen() { setRunningTask(mFullscreenAppTask); doReturn(true).when(mSplitScreenStarter).isSplitScreenVisible(); doReturn(mFullscreenAppTask).when(mSplitScreenStarter).getTaskInfo(anyInt()); mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId); Intent fillInIntent = mPolicy.getStartIntentFillInIntent(mock(PendingIntent.class), 0); assertNull(fillInIntent); } @Test public void testLaunchMultipleTask_sameActivity() { setRunningTask(mFullscreenAppTask); // Replace the mocked drag pending intent and ensure it resolves to the same activity PendingIntent launchIntent = mock(PendingIntent.class); ResolveInfo launchInfo = new ResolveInfo(); launchInfo.activityInfo = mFullscreenAppTask.topActivityInfo; doReturn(Collections.singletonList(launchInfo)) .when(launchIntent).queryIntentComponents(anyInt()); mActivityClipData.getItemAt(0).getIntent().putExtra(ClipDescription.EXTRA_PENDING_INTENT, launchIntent); mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId); Intent fillInIntent = mPolicy.getStartIntentFillInIntent(launchIntent, 0); assertTrue((fillInIntent.getFlags() & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0); } @Test public void testLaunchMultipleTask_sameActivity_inSplitScreen() { setRunningTask(mFullscreenAppTask); // Replace the mocked drag pending intent and ensure it resolves to the same activity PendingIntent launchIntent = mock(PendingIntent.class); ResolveInfo launchInfo = new ResolveInfo(); launchInfo.activityInfo = mFullscreenAppTask.topActivityInfo; doReturn(Collections.singletonList(launchInfo)) .when(launchIntent).queryIntentComponents(anyInt()); mActivityClipData.getItemAt(0).getIntent().putExtra(ClipDescription.EXTRA_PENDING_INTENT, launchIntent); doReturn(true).when(mSplitScreenStarter).isSplitScreenVisible(); doReturn(mFullscreenAppTask).when(mSplitScreenStarter).getTaskInfo(anyInt()); mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId); Intent fillInIntent = mPolicy.getStartIntentFillInIntent(launchIntent, 0); assertTrue((fillInIntent.getFlags() & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0); } private Target filterTargetByType(ArrayList<Target> targets, int type) { for (Target t : targets) { if (type == t.type) { Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java 0 → 100644 +152 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.wm.shell.splitscreen; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import android.app.ActivityManager; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.transition.Transitions; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.Optional; /** * Tests for {@link SplitScreenController} */ @SmallTest @RunWith(AndroidJUnit4.class) public class SplitScreenControllerTests extends ShellTestCase { @Mock ShellTaskOrganizer mTaskOrganizer; @Mock SyncTransactionQueue mSyncQueue; @Mock RootTaskDisplayAreaOrganizer mRootTDAOrganizer; @Mock ShellExecutor mMainExecutor; @Mock DisplayController mDisplayController; @Mock DisplayImeController mDisplayImeController; @Mock DisplayInsetsController mDisplayInsetsController; @Mock Transitions mTransitions; @Mock TransactionPool mTransactionPool; @Mock IconProvider mIconProvider; @Mock Optional<RecentTasksController> mRecentTasks; private SplitScreenController mSplitScreenController; @Before public void setup() { MockitoAnnotations.initMocks(this); mSplitScreenController = spy(new SplitScreenController(mTaskOrganizer, mSyncQueue, mContext, mRootTDAOrganizer, mMainExecutor, mDisplayController, mDisplayImeController, mDisplayInsetsController, mTransitions, mTransactionPool, mIconProvider, mRecentTasks)); } @Test public void testIsLaunchingAdjacently_notInSplitScreen() { doReturn(false).when(mSplitScreenController).isSplitScreenVisible(); // Verify launching the same activity returns true. Intent startIntent = createStartIntent("startActivity"); ActivityManager.RunningTaskInfo focusTaskInfo = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent); mSplitScreenController.onFocusTaskChanged(focusTaskInfo); assertTrue(mSplitScreenController.isLaunchingAdjacently( startIntent, SPLIT_POSITION_TOP_OR_LEFT)); // Verify launching different activity returns false. Intent diffIntent = createStartIntent("diffActivity"); focusTaskInfo = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, diffIntent); mSplitScreenController.onFocusTaskChanged(focusTaskInfo); assertFalse(mSplitScreenController.isLaunchingAdjacently( startIntent, SPLIT_POSITION_TOP_OR_LEFT)); } @Test public void testIsLaunchingAdjacently_inSplitScreen() { doReturn(true).when(mSplitScreenController).isSplitScreenVisible(); // Verify launching the same activity returns true. Intent startIntent = createStartIntent("startActivity"); ActivityManager.RunningTaskInfo pairingTaskInfo = createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, startIntent); doReturn(pairingTaskInfo).when(mSplitScreenController).getTaskInfo(anyInt()); assertTrue(mSplitScreenController.isLaunchingAdjacently( startIntent, SPLIT_POSITION_TOP_OR_LEFT)); // Verify launching different activity returns false. Intent diffIntent = createStartIntent("diffActivity"); pairingTaskInfo = createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, diffIntent); doReturn(pairingTaskInfo).when(mSplitScreenController).getTaskInfo(anyInt()); assertFalse(mSplitScreenController.isLaunchingAdjacently( startIntent, SPLIT_POSITION_TOP_OR_LEFT)); } private Intent createStartIntent(String activityName) { Intent intent = new Intent(); intent.setComponent(new ComponentName(mContext, activityName)); return intent; } private ActivityManager.RunningTaskInfo createTaskInfo(int winMode, int actType, Intent strIntent) { ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo(); info.configuration.windowConfiguration.setActivityType(actType); info.configuration.windowConfiguration.setWindowingMode(winMode); info.supportsMultiWindow = true; info.baseIntent = strIntent; info.baseActivity = strIntent.getComponent(); ActivityInfo activityInfo = new ActivityInfo(); activityInfo.packageName = info.baseActivity.getPackageName(); activityInfo.name = info.baseActivity.getClassName(); info.topActivityInfo = activityInfo; return info; } }