Merge "Adding BackupHelper to help backup and restore AE state" into main
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
new file mode 100644
index 0000000..69a68c8
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2024 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 androidx.window.extensions.embedding;
+
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.MessageQueue;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+
+/**
+ * Helper class to back up and restore the TaskFragmentOrganizer state, in order to resume
+ * organizing the TaskFragments if the app process is restarted.
+ */
+@SuppressWarnings("GuardedBy")
+class BackupHelper {
+ private static final String TAG = "BackupHelper";
+ private static final boolean DEBUG = Build.isDebuggable();
+
+ private static final String KEY_TASK_CONTAINERS = "KEY_TASK_CONTAINERS";
+ @NonNull
+ private final SplitController mController;
+ @NonNull
+ private final BackupIdler mBackupIdler = new BackupIdler();
+ private boolean mBackupIdlerScheduled;
+
+ BackupHelper(@NonNull SplitController splitController, @NonNull Bundle savedState) {
+ mController = splitController;
+
+ if (!savedState.isEmpty()) {
+ restoreState(savedState);
+ }
+ }
+
+ /**
+ * Schedules a back-up request. It is no-op if there was a request scheduled and not yet
+ * completed.
+ */
+ void scheduleBackup() {
+ if (!mBackupIdlerScheduled) {
+ mBackupIdlerScheduled = true;
+ Looper.myQueue().addIdleHandler(mBackupIdler);
+ }
+ }
+
+ final class BackupIdler implements MessageQueue.IdleHandler {
+ @Override
+ public boolean queueIdle() {
+ synchronized (mController.mLock) {
+ mBackupIdlerScheduled = false;
+ startBackup();
+ }
+ return false;
+ }
+ }
+
+ private void startBackup() {
+ final List<TaskContainer> taskContainers = mController.getTaskContainers();
+ if (taskContainers.isEmpty()) {
+ Log.w(TAG, "No task-container to back up");
+ return;
+ }
+
+ if (DEBUG) Log.d(TAG, "Start to back up " + taskContainers);
+ final Bundle state = new Bundle();
+ state.setClassLoader(TaskContainer.class.getClassLoader());
+ state.putParcelableList(KEY_TASK_CONTAINERS, taskContainers);
+ mController.setSavedState(state);
+ }
+
+ private void restoreState(@NonNull Bundle savedState) {
+ if (savedState.isEmpty()) {
+ return;
+ }
+
+ final List<TaskContainer> taskContainers = savedState.getParcelableArrayList(
+ KEY_TASK_CONTAINERS, TaskContainer.class);
+ for (TaskContainer taskContainer : taskContainers) {
+ if (DEBUG) Log.d(TAG, "restore task " + taskContainer.getTaskId());
+ // TODO(b/289875940): implement the TaskContainer restoration.
+ }
+ }
+}
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 26d180c..bb384c5 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -2536,6 +2536,21 @@
return mTaskContainers.get(taskId);
}
+ @NonNull
+ @GuardedBy("mLock")
+ List<TaskContainer> getTaskContainers() {
+ final ArrayList<TaskContainer> taskContainers = new ArrayList<>();
+ for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
+ taskContainers.add(mTaskContainers.valueAt(i));
+ }
+ return taskContainers;
+ }
+
+ @GuardedBy("mLock")
+ void setSavedState(@NonNull Bundle savedState) {
+ mPresenter.setSavedState(savedState);
+ }
+
@GuardedBy("mLock")
void addTaskContainer(int taskId, TaskContainer taskContainer) {
mTaskContainers.put(taskId, taskContainer);
@@ -2829,6 +2844,12 @@
return getActiveSplitForContainer(container) != null;
}
+ void scheduleBackup() {
+ synchronized (mLock) {
+ mPresenter.scheduleBackup();
+ }
+ }
+
private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter {
@Override
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 99716e7..fb8efc4 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -158,6 +158,8 @@
private final WindowLayoutComponentImpl mWindowLayoutComponent;
private final SplitController mController;
+ @NonNull
+ private final BackupHelper mBackupHelper;
SplitPresenter(@NonNull Executor executor,
@NonNull WindowLayoutComponentImpl windowLayoutComponent,
@@ -165,7 +167,18 @@
super(executor, controller);
mWindowLayoutComponent = windowLayoutComponent;
mController = controller;
- registerOrganizer();
+ final Bundle outSavedState = new Bundle();
+ if (Flags.aeBackStackRestore()) {
+ outSavedState.setClassLoader(TaskContainer.class.getClassLoader());
+ registerOrganizer(false /* isSystemOrganizer */, outSavedState);
+ } else {
+ registerOrganizer();
+ }
+ mBackupHelper = new BackupHelper(controller, outSavedState);
+ }
+
+ void scheduleBackup() {
+ mBackupHelper.scheduleBackup();
}
/**
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 20ad53e..5795e8d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -32,6 +32,8 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -48,12 +50,14 @@
import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType;
import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType;
+import com.android.window.flags.Flags;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/** Represents TaskFragments and split pairs below a Task. */
-class TaskContainer {
+class TaskContainer implements Parcelable {
private static final String TAG = TaskContainer.class.getSimpleName();
/** The unique task id. */
@@ -80,6 +84,9 @@
@NonNull
private TaskFragmentParentInfo mInfo;
+ @NonNull
+ private SplitController mSplitController;
+
/**
* TaskFragments that the organizer has requested to be closed. They should be removed when
* the organizer receives
@@ -116,12 +123,14 @@
/**
* The {@link TaskContainer} constructor
*
- * @param taskId The ID of the Task, which must match {@link Activity#getTaskId()} with
- * {@code activityInTask}.
- * @param activityInTask The {@link Activity} in the Task with {@code taskId}. It is used to
- * initialize the {@link TaskContainer} properties.
+ * @param taskId The ID of the Task, which must match {@link Activity#getTaskId()} with
+ * {@code activityInTask}.
+ * @param activityInTask The {@link Activity} in the Task with {@code taskId}. It is used to
+ * initialize the {@link TaskContainer} properties.
+ * @param splitController The {@link SplitController}.
*/
- TaskContainer(int taskId, @NonNull Activity activityInTask) {
+ TaskContainer(int taskId, @NonNull Activity activityInTask,
+ @Nullable SplitController splitController) {
if (taskId == INVALID_TASK_ID) {
throw new IllegalArgumentException("Invalid Task id");
}
@@ -136,6 +145,7 @@
true /* visible */,
true /* hasDirectActivity */,
null /* decorSurface */);
+ mSplitController = splitController;
}
int getTaskId() {
@@ -571,6 +581,12 @@
// Update overlay container after split pin container since the overlay should be on top of
// pin container.
updateAlwaysOnTopOverlayIfNecessary();
+
+ // TODO(b/289875940): Making backup-restore as an opt-in solution, before the flag goes
+ // to next-food.
+ if (Flags.aeBackStackRestore()) {
+ mSplitController.scheduleBackup();
+ }
}
private void updateAlwaysOnTopOverlayIfNecessary() {
@@ -664,6 +680,34 @@
return activityStacks;
}
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mTaskId);
+ // TODO(b/289875940)
+ }
+
+ protected TaskContainer(Parcel in) {
+ mTaskId = in.readInt();
+ // TODO(b/289875940)
+ }
+
+ public static final Creator<TaskContainer> CREATOR = new Creator<>() {
+ @Override
+ public TaskContainer createFromParcel(Parcel in) {
+ return new TaskContainer(in);
+ }
+
+ @Override
+ public TaskContainer[] newArray(int size) {
+ return new TaskContainer[size];
+ }
+ };
+
/** A wrapper class which contains the information of {@link TaskContainer} */
static final class TaskProperties {
private final int mDisplayId;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index ee3e6f3..dc6506b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -1203,7 +1203,7 @@
if (taskContainer == null) {
// Adding a TaskContainer if no existed one.
- taskContainer = new TaskContainer(mTaskId, mActivityInTask);
+ taskContainer = new TaskContainer(mTaskId, mActivityInTask, mSplitController);
mSplitController.addTaskContainer(mTaskId, taskContainer);
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
index 7dc78fd..5c85778 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
@@ -222,7 +222,7 @@
doReturn(resources).when(activity).getResources();
doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
- return new TaskContainer(TASK_ID, activity);
+ return new TaskContainer(TASK_ID, activity, mock(SplitController.class));
}
static TaskContainer createTestTaskContainer(@NonNull SplitController controller) {