Merge "Synchronize SplitController" into tm-dev am: d4d0fee814

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/18418602

Change-Id: Ie8fe385b3d2bfe862ed2ba8ecd228860cbab10f2
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Andrii Kulian
2022-05-25 18:14:30 +00:00
committed by Automerger Merge Worker

View File

@@ -47,6 +47,7 @@ import android.util.SparseArray;
import android.window.TaskFragmentInfo; import android.window.TaskFragmentInfo;
import android.window.WindowContainerTransaction; import android.window.WindowContainerTransaction;
import androidx.annotation.GuardedBy;
import androidx.window.common.EmptyLifecycleCallbacksAdapter; import androidx.window.common.EmptyLifecycleCallbacksAdapter;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
@@ -65,9 +66,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
private static final String TAG = "SplitController"; private static final String TAG = "SplitController";
@VisibleForTesting @VisibleForTesting
@GuardedBy("mLock")
final SplitPresenter mPresenter; final SplitPresenter mPresenter;
// Currently applied split configuration. // Currently applied split configuration.
@GuardedBy("mLock")
private final List<EmbeddingRule> mSplitRules = new ArrayList<>(); private final List<EmbeddingRule> mSplitRules = new ArrayList<>();
/** /**
* Map from Task id to {@link TaskContainer} which contains all TaskFragment and split pair info * 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. * organizer.
*/ */
@VisibleForTesting @VisibleForTesting
@GuardedBy("mLock")
final SparseArray<TaskContainer> mTaskContainers = new SparseArray<>(); final SparseArray<TaskContainer> mTaskContainers = new SparseArray<>();
// Callback to Jetpack to notify about changes to split states. // 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 Consumer<List<SplitInfo>> mEmbeddingCallback;
private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>(); private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>();
private final Handler mHandler; private final Handler mHandler;
private final Object mLock = new Object();
public SplitController() { public SplitController() {
final MainThreadExecutor executor = new MainThreadExecutor(); final MainThreadExecutor executor = new MainThreadExecutor();
@@ -100,180 +105,183 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/** Updates the embedding rules applied to future activity launches. */ /** Updates the embedding rules applied to future activity launches. */
@Override @Override
public void setEmbeddingRules(@NonNull Set<EmbeddingRule> rules) { public void setEmbeddingRules(@NonNull Set<EmbeddingRule> rules) {
mSplitRules.clear(); synchronized (mLock) {
mSplitRules.addAll(rules); mSplitRules.clear();
for (int i = mTaskContainers.size() - 1; i >= 0; i--) { mSplitRules.addAll(rules);
updateAnimationOverride(mTaskContainers.valueAt(i)); for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
updateAnimationOverride(mTaskContainers.valueAt(i));
}
} }
} }
@NonNull @NonNull
public List<EmbeddingRule> getSplitRules() { List<EmbeddingRule> getSplitRules() {
return mSplitRules; 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. * Registers the split organizer callback to notify about changes to active splits.
*/ */
@Override @Override
public void setSplitInfoCallback(@NonNull Consumer<List<SplitInfo>> callback) { public void setSplitInfoCallback(@NonNull Consumer<List<SplitInfo>> callback) {
mEmbeddingCallback = callback; synchronized (mLock) {
updateCallbackIfNecessary(); mEmbeddingCallback = callback;
updateCallbackIfNecessary();
}
} }
@Override @Override
public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) { public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {
TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); synchronized (mLock) {
if (container == null) { TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
return; if (container == null) {
} return;
}
container.setInfo(taskFragmentInfo); container.setInfo(taskFragmentInfo);
if (container.isFinished()) { if (container.isFinished()) {
mPresenter.cleanupContainer(container, false /* shouldFinishDependent */); mPresenter.cleanupContainer(container, false /* shouldFinishDependent */);
}
updateCallbackIfNecessary();
} }
updateCallbackIfNecessary();
} }
@Override @Override
public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) { public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {
TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); synchronized (mLock) {
if (container == null) { TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
return; 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.
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. final WindowContainerTransaction wct = new WindowContainerTransaction();
return; final boolean wasInPip = isInPictureInPicture(container);
} else if (isInPip) { container.setInfo(taskFragmentInfo);
// Enter PIP. final boolean isInPip = isInPictureInPicture(container);
// All overrides will be cleanup. // Check if there are no running activities - consider the container empty if there are
container.setLastRequestedBounds(null /* bounds */); // no non-finishing activities left.
container.setLastRequestedWindowingMode(WINDOWING_MODE_UNDEFINED); if (!taskFragmentInfo.hasRunningActivity()) {
cleanupForEnterPip(wct, container); if (taskFragmentInfo.isTaskFragmentClearedForPip()) {
} else if (wasInPip) { // Do not finish the dependents if the last activity is reparented to PiP.
// Exit PIP. // Instead, the original split should be cleanup, and the dependent may be
// Updates the presentation of the container. Expand or launch placeholder if needed. // expanded to fullscreen.
updateContainer(wct, container); 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);
} 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 @Override
public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) { public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {
final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); synchronized (mLock) {
if (container != null) { final TaskFragmentContainer container = getContainer(
// Cleanup if the TaskFragment vanished is not requested by the organizer. taskFragmentInfo.getFragmentToken());
removeContainer(container); if (container != null) {
// Make sure the top container is updated. // Cleanup if the TaskFragment vanished is not requested by the organizer.
final TaskFragmentContainer newTopContainer = getTopActiveContainer( removeContainer(container);
container.getTaskId()); // Make sure the top container is updated.
if (newTopContainer != null) { final TaskFragmentContainer newTopContainer = getTopActiveContainer(
final WindowContainerTransaction wct = new WindowContainerTransaction(); container.getTaskId());
updateContainer(wct, newTopContainer); if (newTopContainer != null) {
mPresenter.applyTransaction(wct); final WindowContainerTransaction wct = new WindowContainerTransaction();
updateContainer(wct, newTopContainer);
mPresenter.applyTransaction(wct);
}
updateCallbackIfNecessary();
} }
updateCallbackIfNecessary(); cleanupTaskFragment(taskFragmentInfo.getFragmentToken());
} }
cleanupTaskFragment(taskFragmentInfo.getFragmentToken());
} }
@Override @Override
public void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken, public void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken,
@NonNull Configuration parentConfig) { @NonNull Configuration parentConfig) {
final TaskFragmentContainer container = getContainer(fragmentToken); synchronized (mLock) {
if (container != null) { final TaskFragmentContainer container = getContainer(fragmentToken);
onTaskConfigurationChanged(container.getTaskId(), parentConfig); if (container != null) {
if (isInPictureInPicture(parentConfig)) { onTaskConfigurationChanged(container.getTaskId(), parentConfig);
// No need to update presentation in PIP until the Task exit PIP. if (isInPictureInPicture(parentConfig)) {
return; // No need to update presentation in PIP until the Task exit PIP.
return;
}
mPresenter.updateContainer(container);
updateCallbackIfNecessary();
} }
mPresenter.updateContainer(container);
updateCallbackIfNecessary();
} }
} }
@Override @Override
public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent, public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent,
@NonNull IBinder activityToken) { @NonNull IBinder activityToken) {
// If the activity belongs to the current app process, we treat it as a new activity launch. synchronized (mLock) {
final Activity activity = getActivity(activityToken); // If the activity belongs to the current app process, we treat it as a new activity
if (activity != null) { // launch.
// We don't allow split as primary for new launch because we currently only support final Activity activity = getActivity(activityToken);
// launching to top. We allow split as primary for activity reparent because the if (activity != null) {
// activity may be split as primary before it is reparented out. In that case, we want // We don't allow split as primary for new launch because we currently only support
// to show it as primary again when it is reparented back. // launching to top. We allow split as primary for activity reparent because the
if (!resolveActivityToContainer(activity, true /* isOnReparent */)) { // 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;
}
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 // When there is no embedding rule matched, try to place it in the top container
// like a normal launch. // like a normal launch.
placeActivityInTopContainer(activity); targetContainer = taskContainer.getTopTaskFragmentContainer();
} }
updateCallbackIfNecessary(); if (targetContainer == null) {
return; 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().
} }
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;
}
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. */ /** Called on receiving {@link #onTaskFragmentVanished(TaskFragmentInfo)} for cleanup. */
@@ -480,6 +488,22 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
mPresenter.applyTransaction(wct); mPresenter.applyTransaction(wct);
} }
/**
* 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 * Expands the given activity by either expanding the TaskFragment it is currently in or putting
* it into a new expanded TaskFragment. * it into a new expanded TaskFragment.
@@ -1382,25 +1406,28 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Override @Override
public void onActivityPreCreated(Activity activity, Bundle savedInstanceState) { public void onActivityPreCreated(Activity activity, Bundle savedInstanceState) {
final IBinder activityToken = activity.getActivityToken(); synchronized (mLock) {
final IBinder initialTaskFragmentToken = getInitialTaskFragmentToken(activity); final IBinder activityToken = activity.getActivityToken();
// If the activity is not embedded, then it will not have an initial task fragment token final IBinder initialTaskFragmentToken = getInitialTaskFragmentToken(activity);
// so no further action is needed. // If the activity is not embedded, then it will not have an initial task fragment
if (initialTaskFragmentToken == null) { // token so no further action is needed.
return; if (initialTaskFragmentToken == null) {
} return;
for (int i = mTaskContainers.size() - 1; i >= 0; i--) { }
final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i) for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
.mContainers; final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
for (int j = containers.size() - 1; j >= 0; j--) { .mContainers;
final TaskFragmentContainer container = containers.get(j); for (int j = containers.size() - 1; j >= 0; j--) {
if (!container.hasActivity(activityToken) final TaskFragmentContainer container = containers.get(j);
&& container.getTaskFragmentToken().equals(initialTaskFragmentToken)) { if (!container.hasActivity(activityToken)
// The onTaskFragmentInfoChanged callback containing this activity has not && container.getTaskFragmentToken()
// reached the client yet, so add the activity to the pending appeared .equals(initialTaskFragmentToken)) {
// activities. // The onTaskFragmentInfoChanged callback containing this activity has
container.addPendingAppearedActivity(activity); // not reached the client yet, so add the activity to the pending
return; // 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 // 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 // that we don't launch it if an activity itself already requested something to be
// launched to side. // launched to side.
SplitController.this.onActivityCreated(activity); synchronized (mLock) {
SplitController.this.onActivityCreated(activity);
}
} }
@Override @Override
public void onActivityConfigurationChanged(Activity activity) { public void onActivityConfigurationChanged(Activity activity) {
SplitController.this.onActivityConfigurationChanged(activity); synchronized (mLock) {
SplitController.this.onActivityConfigurationChanged(activity);
}
} }
@Override @Override
public void onActivityPostDestroyed(Activity activity) { 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); return super.onStartActivity(who, intent, options);
} }
final int taskId = getTaskId(launchingActivity); synchronized (mLock) {
final WindowContainerTransaction wct = new WindowContainerTransaction(); final int taskId = getTaskId(launchingActivity);
final TaskFragmentContainer launchedInTaskFragment = resolveStartActivityIntent(wct, final WindowContainerTransaction wct = new WindowContainerTransaction();
taskId, intent, launchingActivity); final TaskFragmentContainer launchedInTaskFragment = resolveStartActivityIntent(wct,
if (launchedInTaskFragment != null) { taskId, intent, launchingActivity);
mPresenter.applyTransaction(wct); if (launchedInTaskFragment != null) {
// Amend the request to let the WM know that the activity should be placed in the mPresenter.applyTransaction(wct);
// dedicated container. // Amend the request to let the WM know that the activity should be placed in
options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN, // the dedicated container.
launchedInTaskFragment.getTaskFragmentToken()); options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
launchedInTaskFragment.getTaskFragmentToken());
}
} }
return super.onStartActivity(who, intent, options); return super.onStartActivity(who, intent, options);
@@ -1479,7 +1514,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
*/ */
@Override @Override
public boolean isActivityEmbedded(@NonNull Activity activity) { public boolean isActivityEmbedded(@NonNull Activity activity) {
return mPresenter.isActivityEmbedded(activity.getActivityToken()); synchronized (mLock) {
return mPresenter.isActivityEmbedded(activity.getActivityToken());
}
} }
/** /**