summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
author Andrii Kulian <akulian@google.com> 2022-05-16 22:42:12 +0000
committer Andrii Kulian <akulian@google.com> 2022-05-25 03:32:54 +0000
commit40a67faae46d9303edca017fb55d55de8220158c (patch)
treeefe73303864f45a78ddc45abf3fb1258e0f63454 /libs
parent248de47fe188ce491020374061dec05fc1d38e64 (diff)
Synchronize SplitController
Adding synchronization to the public methods in SplitController, as they might be called on different threads and cause a ConcurrentModificationException. Bug: 228201646 Test: atest CtsWindowManagerJetpackTestCases --iterations 100 Change-Id: I2db9ccb5e9829487eae7aa85eff5f6153721cc04
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java355
1 files changed, 196 insertions, 159 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 23cf1d9654c4..3380a23f3c09 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -47,6 +47,7 @@ import android.util.SparseArray;
import android.window.TaskFragmentInfo;
import android.window.WindowContainerTransaction;
+import androidx.annotation.GuardedBy;
import androidx.window.common.EmptyLifecycleCallbacksAdapter;
import com.android.internal.annotations.VisibleForTesting;
@@ -65,9 +66,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
private static final String TAG = "SplitController";
@VisibleForTesting
+ @GuardedBy("mLock")
final SplitPresenter mPresenter;
// Currently applied split configuration.
+ @GuardedBy("mLock")
private final List<EmbeddingRule> mSplitRules = new ArrayList<>();
/**
* Map from Task id to {@link TaskContainer} which contains all TaskFragment and split pair info
@@ -76,6 +79,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* organizer.
*/
@VisibleForTesting
+ @GuardedBy("mLock")
final SparseArray<TaskContainer> mTaskContainers = new SparseArray<>();
// Callback to Jetpack to notify about changes to split states.
@@ -83,6 +87,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
private Consumer<List<SplitInfo>> mEmbeddingCallback;
private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>();
private final Handler mHandler;
+ private final Object mLock = new Object();
public SplitController() {
final MainThreadExecutor executor = new MainThreadExecutor();
@@ -100,180 +105,183 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/** Updates the embedding rules applied to future activity launches. */
@Override
public void setEmbeddingRules(@NonNull Set<EmbeddingRule> rules) {
- mSplitRules.clear();
- mSplitRules.addAll(rules);
- for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- updateAnimationOverride(mTaskContainers.valueAt(i));
+ synchronized (mLock) {
+ mSplitRules.clear();
+ mSplitRules.addAll(rules);
+ for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
+ updateAnimationOverride(mTaskContainers.valueAt(i));
+ }
}
}
@NonNull
- public List<EmbeddingRule> getSplitRules() {
+ List<EmbeddingRule> getSplitRules() {
return mSplitRules;
}
/**
- * Starts an activity to side of the launchingActivity with the provided split config.
- */
- public void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent intent,
- @Nullable Bundle options, @NonNull SplitRule sideRule,
- @Nullable Consumer<Exception> failureCallback, boolean isPlaceholder) {
- try {
- mPresenter.startActivityToSide(launchingActivity, intent, options, sideRule,
- isPlaceholder);
- } catch (Exception e) {
- if (failureCallback != null) {
- failureCallback.accept(e);
- }
- }
- }
-
- /**
* Registers the split organizer callback to notify about changes to active splits.
*/
@Override
public void setSplitInfoCallback(@NonNull Consumer<List<SplitInfo>> callback) {
- mEmbeddingCallback = callback;
- updateCallbackIfNecessary();
+ synchronized (mLock) {
+ mEmbeddingCallback = callback;
+ updateCallbackIfNecessary();
+ }
}
@Override
public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {
- TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
- if (container == null) {
- return;
- }
+ synchronized (mLock) {
+ TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
+ if (container == null) {
+ return;
+ }
- container.setInfo(taskFragmentInfo);
- if (container.isFinished()) {
- mPresenter.cleanupContainer(container, false /* shouldFinishDependent */);
+ container.setInfo(taskFragmentInfo);
+ if (container.isFinished()) {
+ mPresenter.cleanupContainer(container, false /* shouldFinishDependent */);
+ }
+ updateCallbackIfNecessary();
}
- updateCallbackIfNecessary();
}
@Override
public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {
- TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
- if (container == null) {
- return;
- }
+ synchronized (mLock) {
+ TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
+ if (container == null) {
+ return;
+ }
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- final boolean wasInPip = isInPictureInPicture(container);
- container.setInfo(taskFragmentInfo);
- final boolean isInPip = isInPictureInPicture(container);
- // Check if there are no running activities - consider the container empty if there are no
- // non-finishing activities left.
- if (!taskFragmentInfo.hasRunningActivity()) {
- if (taskFragmentInfo.isTaskFragmentClearedForPip()) {
- // Do not finish the dependents if the last activity is reparented to PiP.
- // Instead, the original split should be cleanup, and the dependent may be expanded
- // to fullscreen.
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final boolean wasInPip = isInPictureInPicture(container);
+ container.setInfo(taskFragmentInfo);
+ final boolean isInPip = isInPictureInPicture(container);
+ // Check if there are no running activities - consider the container empty if there are
+ // no non-finishing activities left.
+ if (!taskFragmentInfo.hasRunningActivity()) {
+ if (taskFragmentInfo.isTaskFragmentClearedForPip()) {
+ // Do not finish the dependents if the last activity is reparented to PiP.
+ // Instead, the original split should be cleanup, and the dependent may be
+ // expanded to fullscreen.
+ cleanupForEnterPip(wct, container);
+ mPresenter.cleanupContainer(container, false /* shouldFinishDependent */, wct);
+ } else if (taskFragmentInfo.isTaskClearedForReuse()) {
+ // Do not finish the dependents if this TaskFragment was cleared due to
+ // launching activity in the Task.
+ mPresenter.cleanupContainer(container, false /* shouldFinishDependent */, wct);
+ } else if (!container.isWaitingActivityAppear()) {
+ // Do not finish the container before the expected activity appear until
+ // timeout.
+ mPresenter.cleanupContainer(container, true /* shouldFinishDependent */, wct);
+ }
+ } else if (wasInPip && isInPip) {
+ // No update until exit PIP.
+ return;
+ } else if (isInPip) {
+ // Enter PIP.
+ // All overrides will be cleanup.
+ container.setLastRequestedBounds(null /* bounds */);
+ container.setLastRequestedWindowingMode(WINDOWING_MODE_UNDEFINED);
cleanupForEnterPip(wct, container);
- mPresenter.cleanupContainer(container, false /* shouldFinishDependent */, wct);
- } else if (taskFragmentInfo.isTaskClearedForReuse()) {
- // Do not finish the dependents if this TaskFragment was cleared due to launching
- // activity in the Task.
- mPresenter.cleanupContainer(container, false /* shouldFinishDependent */, wct);
- } else if (!container.isWaitingActivityAppear()) {
- // Do not finish the container before the expected activity appear until timeout.
- mPresenter.cleanupContainer(container, true /* shouldFinishDependent */, wct);
+ } else if (wasInPip) {
+ // Exit PIP.
+ // Updates the presentation of the container. Expand or launch placeholder if
+ // needed.
+ updateContainer(wct, container);
}
- } else if (wasInPip && isInPip) {
- // No update until exit PIP.
- return;
- } else if (isInPip) {
- // Enter PIP.
- // All overrides will be cleanup.
- container.setLastRequestedBounds(null /* bounds */);
- container.setLastRequestedWindowingMode(WINDOWING_MODE_UNDEFINED);
- cleanupForEnterPip(wct, container);
- } else if (wasInPip) {
- // Exit PIP.
- // Updates the presentation of the container. Expand or launch placeholder if needed.
- updateContainer(wct, container);
+ mPresenter.applyTransaction(wct);
+ updateCallbackIfNecessary();
}
- mPresenter.applyTransaction(wct);
- updateCallbackIfNecessary();
}
@Override
public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {
- final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
- if (container != null) {
- // Cleanup if the TaskFragment vanished is not requested by the organizer.
- removeContainer(container);
- // Make sure the top container is updated.
- final TaskFragmentContainer newTopContainer = getTopActiveContainer(
- container.getTaskId());
- if (newTopContainer != null) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- updateContainer(wct, newTopContainer);
- mPresenter.applyTransaction(wct);
+ synchronized (mLock) {
+ final TaskFragmentContainer container = getContainer(
+ taskFragmentInfo.getFragmentToken());
+ if (container != null) {
+ // Cleanup if the TaskFragment vanished is not requested by the organizer.
+ removeContainer(container);
+ // Make sure the top container is updated.
+ final TaskFragmentContainer newTopContainer = getTopActiveContainer(
+ container.getTaskId());
+ if (newTopContainer != null) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ updateContainer(wct, newTopContainer);
+ mPresenter.applyTransaction(wct);
+ }
+ updateCallbackIfNecessary();
}
- updateCallbackIfNecessary();
+ cleanupTaskFragment(taskFragmentInfo.getFragmentToken());
}
- cleanupTaskFragment(taskFragmentInfo.getFragmentToken());
}
@Override
public void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken,
@NonNull Configuration parentConfig) {
- final TaskFragmentContainer container = getContainer(fragmentToken);
- if (container != null) {
- onTaskConfigurationChanged(container.getTaskId(), parentConfig);
- if (isInPictureInPicture(parentConfig)) {
- // No need to update presentation in PIP until the Task exit PIP.
- return;
+ synchronized (mLock) {
+ final TaskFragmentContainer container = getContainer(fragmentToken);
+ if (container != null) {
+ onTaskConfigurationChanged(container.getTaskId(), parentConfig);
+ if (isInPictureInPicture(parentConfig)) {
+ // No need to update presentation in PIP until the Task exit PIP.
+ return;
+ }
+ mPresenter.updateContainer(container);
+ updateCallbackIfNecessary();
}
- mPresenter.updateContainer(container);
- updateCallbackIfNecessary();
}
}
@Override
public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent,
@NonNull IBinder activityToken) {
- // If the activity belongs to the current app process, we treat it as a new activity launch.
- final Activity activity = getActivity(activityToken);
- if (activity != null) {
- // We don't allow split as primary for new launch because we currently only support
- // launching to top. We allow split as primary for activity reparent because the
- // activity may be split as primary before it is reparented out. In that case, we want
- // to show it as primary again when it is reparented back.
- if (!resolveActivityToContainer(activity, true /* isOnReparent */)) {
- // When there is no embedding rule matched, try to place it in the top container
- // like a normal launch.
- placeActivityInTopContainer(activity);
+ synchronized (mLock) {
+ // If the activity belongs to the current app process, we treat it as a new activity
+ // launch.
+ final Activity activity = getActivity(activityToken);
+ if (activity != null) {
+ // We don't allow split as primary for new launch because we currently only support
+ // launching to top. We allow split as primary for activity reparent because the
+ // activity may be split as primary before it is reparented out. In that case, we
+ // want to show it as primary again when it is reparented back.
+ if (!resolveActivityToContainer(activity, true /* isOnReparent */)) {
+ // When there is no embedding rule matched, try to place it in the top container
+ // like a normal launch.
+ placeActivityInTopContainer(activity);
+ }
+ updateCallbackIfNecessary();
+ return;
}
- updateCallbackIfNecessary();
- return;
- }
- final TaskContainer taskContainer = getTaskContainer(taskId);
- if (taskContainer == null || taskContainer.isInPictureInPicture()) {
- // We don't embed activity when it is in PIP.
- return;
- }
+ final TaskContainer taskContainer = getTaskContainer(taskId);
+ if (taskContainer == null || taskContainer.isInPictureInPicture()) {
+ // We don't embed activity when it is in PIP.
+ return;
+ }
- // If the activity belongs to a different app process, we treat it as starting new intent,
- // since both actions might result in a new activity that should appear in an organized
- // TaskFragment.
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- TaskFragmentContainer targetContainer = resolveStartActivityIntent(wct, taskId,
- activityIntent, null /* launchingActivity */);
- if (targetContainer == null) {
- // When there is no embedding rule matched, try to place it in the top container like a
- // normal launch.
- targetContainer = taskContainer.getTopTaskFragmentContainer();
- }
- if (targetContainer == null) {
- return;
+ // If the activity belongs to a different app process, we treat it as starting new
+ // intent, since both actions might result in a new activity that should appear in an
+ // organized TaskFragment.
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ TaskFragmentContainer targetContainer = resolveStartActivityIntent(wct, taskId,
+ activityIntent, null /* launchingActivity */);
+ if (targetContainer == null) {
+ // When there is no embedding rule matched, try to place it in the top container
+ // like a normal launch.
+ targetContainer = taskContainer.getTopTaskFragmentContainer();
+ }
+ if (targetContainer == null) {
+ return;
+ }
+ wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(),
+ activityToken);
+ mPresenter.applyTransaction(wct);
+ // Because the activity does not belong to the organizer process, we wait until
+ // onTaskFragmentAppeared to trigger updateCallbackIfNecessary().
}
- wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(), activityToken);
- mPresenter.applyTransaction(wct);
- // Because the activity does not belong to the organizer process, we wait until
- // onTaskFragmentAppeared to trigger updateCallbackIfNecessary().
}
/** Called on receiving {@link #onTaskFragmentVanished(TaskFragmentInfo)} for cleanup. */
@@ -481,6 +489,22 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
/**
+ * Starts an activity to side of the launchingActivity with the provided split config.
+ */
+ private void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent intent,
+ @Nullable Bundle options, @NonNull SplitRule sideRule,
+ @Nullable Consumer<Exception> failureCallback, boolean isPlaceholder) {
+ try {
+ mPresenter.startActivityToSide(launchingActivity, intent, options, sideRule,
+ isPlaceholder);
+ } catch (Exception e) {
+ if (failureCallback != null) {
+ failureCallback.accept(e);
+ }
+ }
+ }
+
+ /**
* Expands the given activity by either expanding the TaskFragment it is currently in or putting
* it into a new expanded TaskFragment.
*/
@@ -1382,25 +1406,28 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Override
public void onActivityPreCreated(Activity activity, Bundle savedInstanceState) {
- final IBinder activityToken = activity.getActivityToken();
- final IBinder initialTaskFragmentToken = getInitialTaskFragmentToken(activity);
- // If the activity is not embedded, then it will not have an initial task fragment token
- // so no further action is needed.
- if (initialTaskFragmentToken == null) {
- return;
- }
- for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
- .mContainers;
- for (int j = containers.size() - 1; j >= 0; j--) {
- final TaskFragmentContainer container = containers.get(j);
- if (!container.hasActivity(activityToken)
- && container.getTaskFragmentToken().equals(initialTaskFragmentToken)) {
- // The onTaskFragmentInfoChanged callback containing this activity has not
- // reached the client yet, so add the activity to the pending appeared
- // activities.
- container.addPendingAppearedActivity(activity);
- return;
+ synchronized (mLock) {
+ final IBinder activityToken = activity.getActivityToken();
+ final IBinder initialTaskFragmentToken = getInitialTaskFragmentToken(activity);
+ // If the activity is not embedded, then it will not have an initial task fragment
+ // token so no further action is needed.
+ if (initialTaskFragmentToken == null) {
+ return;
+ }
+ for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
+ final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
+ .mContainers;
+ for (int j = containers.size() - 1; j >= 0; j--) {
+ final TaskFragmentContainer container = containers.get(j);
+ if (!container.hasActivity(activityToken)
+ && container.getTaskFragmentToken()
+ .equals(initialTaskFragmentToken)) {
+ // The onTaskFragmentInfoChanged callback containing this activity has
+ // not reached the client yet, so add the activity to the pending
+ // appeared activities.
+ container.addPendingAppearedActivity(activity);
+ return;
+ }
}
}
}
@@ -1412,17 +1439,23 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// first. In case of a configured placeholder activity we want to make sure
// that we don't launch it if an activity itself already requested something to be
// launched to side.
- SplitController.this.onActivityCreated(activity);
+ synchronized (mLock) {
+ SplitController.this.onActivityCreated(activity);
+ }
}
@Override
public void onActivityConfigurationChanged(Activity activity) {
- SplitController.this.onActivityConfigurationChanged(activity);
+ synchronized (mLock) {
+ SplitController.this.onActivityConfigurationChanged(activity);
+ }
}
@Override
public void onActivityPostDestroyed(Activity activity) {
- SplitController.this.onActivityDestroyed(activity);
+ synchronized (mLock) {
+ SplitController.this.onActivityDestroyed(activity);
+ }
}
}
@@ -1457,16 +1490,18 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return super.onStartActivity(who, intent, options);
}
- final int taskId = getTaskId(launchingActivity);
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- final TaskFragmentContainer launchedInTaskFragment = resolveStartActivityIntent(wct,
- taskId, intent, launchingActivity);
- if (launchedInTaskFragment != null) {
- mPresenter.applyTransaction(wct);
- // Amend the request to let the WM know that the activity should be placed in the
- // dedicated container.
- options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
- launchedInTaskFragment.getTaskFragmentToken());
+ synchronized (mLock) {
+ final int taskId = getTaskId(launchingActivity);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final TaskFragmentContainer launchedInTaskFragment = resolveStartActivityIntent(wct,
+ taskId, intent, launchingActivity);
+ if (launchedInTaskFragment != null) {
+ mPresenter.applyTransaction(wct);
+ // Amend the request to let the WM know that the activity should be placed in
+ // the dedicated container.
+ options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
+ launchedInTaskFragment.getTaskFragmentToken());
+ }
}
return super.onStartActivity(who, intent, options);
@@ -1479,7 +1514,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
*/
@Override
public boolean isActivityEmbedded(@NonNull Activity activity) {
- return mPresenter.isActivityEmbedded(activity.getActivityToken());
+ synchronized (mLock) {
+ return mPresenter.isActivityEmbedded(activity.getActivityToken());
+ }
}
/**