diff options
6 files changed, 188 insertions, 9 deletions
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 000000000000..69a68c85a2fe --- /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 26d180cdcb1a..bb384c5d0e84 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 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen 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 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen 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 99716e7cc69e..fb8efc4ad490 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 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { private final WindowLayoutComponentImpl mWindowLayoutComponent; private final SplitController mController; + @NonNull + private final BackupHelper mBackupHelper; SplitPresenter(@NonNull Executor executor, @NonNull WindowLayoutComponentImpl windowLayoutComponent, @@ -165,7 +167,18 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { 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 20ad53ee19a8..5795e8da18c2 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.app.WindowConfiguration.WindowingMode; 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; 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 @@ class TaskContainer { @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 @@ class TaskContainer { /** * 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 @@ class TaskContainer { true /* visible */, true /* hasDirectActivity */, null /* decorSurface */); + mSplitController = splitController; } int getTaskId() { @@ -571,6 +581,12 @@ class TaskContainer { // 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 @@ class TaskContainer { 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 ee3e6f368505..dc6506b070af 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 @@ class TaskFragmentContainer { 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 7dc78fdd601f..5c85778ee42d 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 @@ public class EmbeddingTestUtils { 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) { |