Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +41 −10 Original line number Diff line number Diff line Loading @@ -2734,6 +2734,23 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return overlayContainers; } @GuardedBy("mLock") @NonNull private List<IBinder> getAllNonFinishingContainerTokens() { final List<IBinder> tokens = new ArrayList<>(); for (int i = 0; i < mTaskContainers.size(); i++) { final TaskContainer taskContainer = mTaskContainers.valueAt(i); final List<IBinder> tokensPerTask = taskContainer .getTaskFragmentContainers() .stream() .filter(c -> !c.isFinished()) .map(TaskFragmentContainer::getTaskFragmentToken) .toList(); tokens.addAll(tokensPerTask); } return tokens; } @GuardedBy("mLock") private boolean isAssociatedWithOverlay(@NonNull Activity activity) { final TaskContainer taskContainer = getTaskContainer(getTaskId(activity)); Loading Loading @@ -3086,9 +3103,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen */ @VisibleForTesting class ActivityStartMonitor extends Instrumentation.ActivityMonitor { /** * Keeps track of the current starting activity Intent if it is also used to create a new * TaskFragment as {@link TaskFragmentContainer#getPendingAppearedIntent()}. */ @VisibleForTesting @Nullable @GuardedBy("mLock") Intent mCurrentIntent; Intent mCurrentPendingAppearedIntent; @Override public Instrumentation.ActivityResult onStartActivity(@NonNull Context who, Loading @@ -3109,9 +3132,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // TODO(b/295993745): Use KEY_LAUNCH_TASK_FRAGMENT_TOKEN after WM Jetpack migrates to // bundle. This is still needed to support #setLaunchingActivityStack. if (options.getBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN) != null) { synchronized (mLock) { mCurrentIntent = intent; } return super.onStartActivity(who, intent, options); } Loading Loading @@ -3142,6 +3162,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen .startNewTransaction(); transactionRecord.setOriginType(TASK_FRAGMENT_TRANSIT_OPEN); final WindowContainerTransaction wct = transactionRecord.getTransaction(); // Track the existing TFs before the operation. final List<IBinder> allExistingTFTokens = getAllNonFinishingContainerTokens(); final TaskFragmentContainer launchedInTaskFragment; if (launchingActivity != null) { final String overlayTag = options.getString(KEY_OVERLAY_TAG); Loading Loading @@ -3171,12 +3195,18 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Amend the request to let the WM know that the activity should be placed in // the dedicated container. // TODO(b/229680885): skip override launching TaskFragment token by split-rule options.putBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN, launchedInTaskFragment.getTaskFragmentToken()); final IBinder tfToken = launchedInTaskFragment.getTaskFragmentToken(); options.putBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN, tfToken); if (activityEmbeddingDelayTaskFragmentFinishForActivityLaunch()) { launchedInTaskFragment.setActivityLaunchHint(); } mCurrentIntent = intent; if (!allExistingTFTokens.contains(tfToken)) { // When we are creating a new TaskFragment for this Intent, keep track of it // in case the startActivity fails, so that we can cleanup early. // If the TF is an existing one, this Intent will not be kept as a pending // appeared Intent. mCurrentPendingAppearedIntent = intent; } } else { transactionRecord.abort(); } Loading @@ -3189,18 +3219,19 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen public void onStartActivityResult(int result, @NonNull Bundle bOptions) { super.onStartActivityResult(result, bOptions); synchronized (mLock) { if (mCurrentIntent != null && result != START_SUCCESS) { if (mCurrentPendingAppearedIntent != null && result != START_SUCCESS) { // Clear the pending appeared intent if the activity was not started // successfully. final IBinder token = bOptions.getBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN); if (token != null) { final TaskFragmentContainer container = getContainer(token); if (container != null) { container.clearPendingAppearedIntentIfNeeded(mCurrentIntent); container.clearPendingAppearedIntentIfNeeded( mCurrentPendingAppearedIntent); } } } mCurrentIntent = null; mCurrentPendingAppearedIntent = null; } } } Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +6 −0 Original line number Diff line number Diff line Loading @@ -864,6 +864,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { // bounds. Return failure to create a new SplitContainer which fills task bounds. final TaskFragmentContainer primaryContainer = splitContainer.getPrimaryContainer(); final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer(); if (primaryContainer.areLastRequestedBoundsEqual(null) && secondaryContainer.areLastRequestedBoundsEqual(null) && secondaryContainer.isLastAdjacentTaskFragmentEqual(null, null)) { // No need to update since it is already expanded. return RESULT_EXPANDED; } if (primaryContainer.getInfo() == null || secondaryContainer.getInfo() == null) { return RESULT_EXPAND_FAILED_NO_TF_INFO; } Loading libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +40 −1 Original line number Diff line number Diff line Loading @@ -17,6 +17,9 @@ package androidx.window.extensions.embedding; import static android.app.ActivityManager.START_CANCELED; import static android.app.ActivityManager.START_DELIVERED_TO_TOP; import static android.app.ActivityManager.START_SUCCESS; import static android.app.ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; Loading Loading @@ -384,13 +387,49 @@ public class SplitControllerTest { final Bundle bundle = new Bundle(); bundle.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN, container.getTaskFragmentToken()); monitor.mCurrentIntent = intent; monitor.mCurrentPendingAppearedIntent = intent; doReturn(container).when(mSplitController).getContainer(any(IBinder.class)); monitor.onStartActivityResult(START_CANCELED, bundle); assertNull(container.getPendingAppearedIntent()); } @Test public void testOnStartActivityResultError_notClearPendingForExistingContainer() { final SplitController.ActivityStartMonitor monitor = mSplitController.getActivityStartMonitor(); final Intent intent = new Intent(); final Bundle options0 = new Bundle(); setupSplitRule(mActivity, intent, false /* clearTop */); // On creating a new split pair, keep track of the pending Intent. monitor.onStartActivity(mActivity, intent, options0); final IBinder tfToken = options0.getBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN); final TaskFragmentContainer container = mSplitController.getContainer(tfToken); assertEquals(intent, monitor.mCurrentPendingAppearedIntent); assertNotNull(container); assertEquals(intent, container.getPendingAppearedIntent()); // On success, do not clear the pending Intent. monitor.onStartActivityResult(START_SUCCESS, new Bundle()); assertNull(monitor.mCurrentPendingAppearedIntent); assertEquals(intent, container.getPendingAppearedIntent()); // On reusing an existing split pair, do not track of the pending Intent. final Bundle options1 = new Bundle(); monitor.onStartActivity(mActivity, intent, options1); assertEquals(tfToken, options1.getBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN)); assertNull(monitor.mCurrentPendingAppearedIntent); // On failure of a non-tracking Intent, do not clear the pending Intent. monitor.onStartActivityResult(START_DELIVERED_TO_TOP, new Bundle()); assertEquals(intent, container.getPendingAppearedIntent()); } @Test public void testOnActivityCreated() { mSplitController.onActivityCreated(mTransaction, mActivity); Loading libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java +21 −0 Original line number Diff line number Diff line Loading @@ -55,6 +55,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; Loading Loading @@ -666,6 +667,7 @@ public class SplitPresenterTest { @Test public void testExpandSplitContainerIfNeeded() { doNothing().when(mPresenter).expandTaskFragment(any(), any()); Activity secondaryActivity = createMockActivity(); SplitRule splitRule = createSplitRule(mActivity, secondaryActivity); TaskFragmentContainer primaryTf = createTfContainer(mController, mActivity); Loading @@ -681,11 +683,30 @@ public class SplitPresenterTest { splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */)); verify(mPresenter, never()).expandTaskFragment(any(), any()); // Not fail when the split pair is already expanded, even if it's info is not yet received. splitContainer.updateCurrentSplitAttributes(SPLIT_ATTRIBUTES); doReturn(createActivityInfoWithMinDimensions()).when(secondaryActivity).getActivityInfo(); primaryTf.setLastRequestedBounds(null); secondaryTf.setLastRequestedBounds(null); assertEquals(RESULT_EXPANDED, mPresenter.expandSplitContainerIfNeeded( mTransaction, splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */)); verify(mPresenter, never()).expandTaskFragment(any(), any()); // Failed when the split pair is not expanded, and it's info is not yet received. splitContainer.updateCurrentSplitAttributes(SPLIT_ATTRIBUTES); primaryTf.setLastRequestedBounds(new Rect(0, 0, 500, 1000)); secondaryTf.setLastRequestedBounds(new Rect(500, 0, 1000, 1000)); final WindowContainerTransaction.TaskFragmentAdjacentParams params = new WindowContainerTransaction.TaskFragmentAdjacentParams(); secondaryTf.setLastAdjacentTaskFragment(primaryTf.getTaskFragmentToken(), params); primaryTf.setLastAdjacentTaskFragment(secondaryTf.getTaskFragmentToken(), params); assertEquals(RESULT_EXPAND_FAILED_NO_TF_INFO, mPresenter.expandSplitContainerIfNeeded( mTransaction, splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */)); verify(mPresenter, never()).expandTaskFragment(any(), any()); splitContainer.updateCurrentSplitAttributes(SPLIT_ATTRIBUTES); primaryTf.setInfo(mTransaction, createMockTaskFragmentInfo(primaryTf, mActivity)); Loading Loading
libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +41 −10 Original line number Diff line number Diff line Loading @@ -2734,6 +2734,23 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return overlayContainers; } @GuardedBy("mLock") @NonNull private List<IBinder> getAllNonFinishingContainerTokens() { final List<IBinder> tokens = new ArrayList<>(); for (int i = 0; i < mTaskContainers.size(); i++) { final TaskContainer taskContainer = mTaskContainers.valueAt(i); final List<IBinder> tokensPerTask = taskContainer .getTaskFragmentContainers() .stream() .filter(c -> !c.isFinished()) .map(TaskFragmentContainer::getTaskFragmentToken) .toList(); tokens.addAll(tokensPerTask); } return tokens; } @GuardedBy("mLock") private boolean isAssociatedWithOverlay(@NonNull Activity activity) { final TaskContainer taskContainer = getTaskContainer(getTaskId(activity)); Loading Loading @@ -3086,9 +3103,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen */ @VisibleForTesting class ActivityStartMonitor extends Instrumentation.ActivityMonitor { /** * Keeps track of the current starting activity Intent if it is also used to create a new * TaskFragment as {@link TaskFragmentContainer#getPendingAppearedIntent()}. */ @VisibleForTesting @Nullable @GuardedBy("mLock") Intent mCurrentIntent; Intent mCurrentPendingAppearedIntent; @Override public Instrumentation.ActivityResult onStartActivity(@NonNull Context who, Loading @@ -3109,9 +3132,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // TODO(b/295993745): Use KEY_LAUNCH_TASK_FRAGMENT_TOKEN after WM Jetpack migrates to // bundle. This is still needed to support #setLaunchingActivityStack. if (options.getBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN) != null) { synchronized (mLock) { mCurrentIntent = intent; } return super.onStartActivity(who, intent, options); } Loading Loading @@ -3142,6 +3162,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen .startNewTransaction(); transactionRecord.setOriginType(TASK_FRAGMENT_TRANSIT_OPEN); final WindowContainerTransaction wct = transactionRecord.getTransaction(); // Track the existing TFs before the operation. final List<IBinder> allExistingTFTokens = getAllNonFinishingContainerTokens(); final TaskFragmentContainer launchedInTaskFragment; if (launchingActivity != null) { final String overlayTag = options.getString(KEY_OVERLAY_TAG); Loading Loading @@ -3171,12 +3195,18 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Amend the request to let the WM know that the activity should be placed in // the dedicated container. // TODO(b/229680885): skip override launching TaskFragment token by split-rule options.putBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN, launchedInTaskFragment.getTaskFragmentToken()); final IBinder tfToken = launchedInTaskFragment.getTaskFragmentToken(); options.putBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN, tfToken); if (activityEmbeddingDelayTaskFragmentFinishForActivityLaunch()) { launchedInTaskFragment.setActivityLaunchHint(); } mCurrentIntent = intent; if (!allExistingTFTokens.contains(tfToken)) { // When we are creating a new TaskFragment for this Intent, keep track of it // in case the startActivity fails, so that we can cleanup early. // If the TF is an existing one, this Intent will not be kept as a pending // appeared Intent. mCurrentPendingAppearedIntent = intent; } } else { transactionRecord.abort(); } Loading @@ -3189,18 +3219,19 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen public void onStartActivityResult(int result, @NonNull Bundle bOptions) { super.onStartActivityResult(result, bOptions); synchronized (mLock) { if (mCurrentIntent != null && result != START_SUCCESS) { if (mCurrentPendingAppearedIntent != null && result != START_SUCCESS) { // Clear the pending appeared intent if the activity was not started // successfully. final IBinder token = bOptions.getBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN); if (token != null) { final TaskFragmentContainer container = getContainer(token); if (container != null) { container.clearPendingAppearedIntentIfNeeded(mCurrentIntent); container.clearPendingAppearedIntentIfNeeded( mCurrentPendingAppearedIntent); } } } mCurrentIntent = null; mCurrentPendingAppearedIntent = null; } } } Loading
libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +6 −0 Original line number Diff line number Diff line Loading @@ -864,6 +864,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { // bounds. Return failure to create a new SplitContainer which fills task bounds. final TaskFragmentContainer primaryContainer = splitContainer.getPrimaryContainer(); final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer(); if (primaryContainer.areLastRequestedBoundsEqual(null) && secondaryContainer.areLastRequestedBoundsEqual(null) && secondaryContainer.isLastAdjacentTaskFragmentEqual(null, null)) { // No need to update since it is already expanded. return RESULT_EXPANDED; } if (primaryContainer.getInfo() == null || secondaryContainer.getInfo() == null) { return RESULT_EXPAND_FAILED_NO_TF_INFO; } Loading
libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +40 −1 Original line number Diff line number Diff line Loading @@ -17,6 +17,9 @@ package androidx.window.extensions.embedding; import static android.app.ActivityManager.START_CANCELED; import static android.app.ActivityManager.START_DELIVERED_TO_TOP; import static android.app.ActivityManager.START_SUCCESS; import static android.app.ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; Loading Loading @@ -384,13 +387,49 @@ public class SplitControllerTest { final Bundle bundle = new Bundle(); bundle.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN, container.getTaskFragmentToken()); monitor.mCurrentIntent = intent; monitor.mCurrentPendingAppearedIntent = intent; doReturn(container).when(mSplitController).getContainer(any(IBinder.class)); monitor.onStartActivityResult(START_CANCELED, bundle); assertNull(container.getPendingAppearedIntent()); } @Test public void testOnStartActivityResultError_notClearPendingForExistingContainer() { final SplitController.ActivityStartMonitor monitor = mSplitController.getActivityStartMonitor(); final Intent intent = new Intent(); final Bundle options0 = new Bundle(); setupSplitRule(mActivity, intent, false /* clearTop */); // On creating a new split pair, keep track of the pending Intent. monitor.onStartActivity(mActivity, intent, options0); final IBinder tfToken = options0.getBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN); final TaskFragmentContainer container = mSplitController.getContainer(tfToken); assertEquals(intent, monitor.mCurrentPendingAppearedIntent); assertNotNull(container); assertEquals(intent, container.getPendingAppearedIntent()); // On success, do not clear the pending Intent. monitor.onStartActivityResult(START_SUCCESS, new Bundle()); assertNull(monitor.mCurrentPendingAppearedIntent); assertEquals(intent, container.getPendingAppearedIntent()); // On reusing an existing split pair, do not track of the pending Intent. final Bundle options1 = new Bundle(); monitor.onStartActivity(mActivity, intent, options1); assertEquals(tfToken, options1.getBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN)); assertNull(monitor.mCurrentPendingAppearedIntent); // On failure of a non-tracking Intent, do not clear the pending Intent. monitor.onStartActivityResult(START_DELIVERED_TO_TOP, new Bundle()); assertEquals(intent, container.getPendingAppearedIntent()); } @Test public void testOnActivityCreated() { mSplitController.onActivityCreated(mTransaction, mActivity); Loading
libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java +21 −0 Original line number Diff line number Diff line Loading @@ -55,6 +55,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; Loading Loading @@ -666,6 +667,7 @@ public class SplitPresenterTest { @Test public void testExpandSplitContainerIfNeeded() { doNothing().when(mPresenter).expandTaskFragment(any(), any()); Activity secondaryActivity = createMockActivity(); SplitRule splitRule = createSplitRule(mActivity, secondaryActivity); TaskFragmentContainer primaryTf = createTfContainer(mController, mActivity); Loading @@ -681,11 +683,30 @@ public class SplitPresenterTest { splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */)); verify(mPresenter, never()).expandTaskFragment(any(), any()); // Not fail when the split pair is already expanded, even if it's info is not yet received. splitContainer.updateCurrentSplitAttributes(SPLIT_ATTRIBUTES); doReturn(createActivityInfoWithMinDimensions()).when(secondaryActivity).getActivityInfo(); primaryTf.setLastRequestedBounds(null); secondaryTf.setLastRequestedBounds(null); assertEquals(RESULT_EXPANDED, mPresenter.expandSplitContainerIfNeeded( mTransaction, splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */)); verify(mPresenter, never()).expandTaskFragment(any(), any()); // Failed when the split pair is not expanded, and it's info is not yet received. splitContainer.updateCurrentSplitAttributes(SPLIT_ATTRIBUTES); primaryTf.setLastRequestedBounds(new Rect(0, 0, 500, 1000)); secondaryTf.setLastRequestedBounds(new Rect(500, 0, 1000, 1000)); final WindowContainerTransaction.TaskFragmentAdjacentParams params = new WindowContainerTransaction.TaskFragmentAdjacentParams(); secondaryTf.setLastAdjacentTaskFragment(primaryTf.getTaskFragmentToken(), params); primaryTf.setLastAdjacentTaskFragment(secondaryTf.getTaskFragmentToken(), params); assertEquals(RESULT_EXPAND_FAILED_NO_TF_INFO, mPresenter.expandSplitContainerIfNeeded( mTransaction, splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */)); verify(mPresenter, never()).expandTaskFragment(any(), any()); splitContainer.updateCurrentSplitAttributes(SPLIT_ATTRIBUTES); primaryTf.setInfo(mTransaction, createMockTaskFragmentInfo(primaryTf, mActivity)); Loading