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) {