Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit da11ec56 authored by Louis Chang's avatar Louis Chang
Browse files

Adding BackupHelper to help backup and restore AE state

The backup will be scheduled whenever a TaskFragmentContainer is
updated and the backup is done while the app is idled.

Bug: 289875940
Test: wm presubmit
Flag: com.android.window.flags.ae_back_stack_restore

Change-Id: Ie9d6d7f235269b9e4b321d51beea28b3e84274f7
parent 2962f79d
Loading
Loading
Loading
Loading
+101 −0
Original line number Original line Diff line number Diff line
/*
 * 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.
        }
    }
}
+21 −0
Original line number Original line Diff line number Diff line
@@ -2536,6 +2536,21 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        return mTaskContainers.get(taskId);
        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")
    @GuardedBy("mLock")
    void addTaskContainer(int taskId, TaskContainer taskContainer) {
    void addTaskContainer(int taskId, TaskContainer taskContainer) {
        mTaskContainers.put(taskId, taskContainer);
        mTaskContainers.put(taskId, taskContainer);
@@ -2829,6 +2844,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        return getActiveSplitForContainer(container) != null;
        return getActiveSplitForContainer(container) != null;
    }
    }


    void scheduleBackup() {
        synchronized (mLock) {
            mPresenter.scheduleBackup();
        }
    }

    private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter {
    private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter {


        @Override
        @Override
+14 −1
Original line number Original line Diff line number Diff line
@@ -158,6 +158,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {


    private final WindowLayoutComponentImpl mWindowLayoutComponent;
    private final WindowLayoutComponentImpl mWindowLayoutComponent;
    private final SplitController mController;
    private final SplitController mController;
    @NonNull
    private final BackupHelper mBackupHelper;


    SplitPresenter(@NonNull Executor executor,
    SplitPresenter(@NonNull Executor executor,
            @NonNull WindowLayoutComponentImpl windowLayoutComponent,
            @NonNull WindowLayoutComponentImpl windowLayoutComponent,
@@ -165,8 +167,19 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
        super(executor, controller);
        super(executor, controller);
        mWindowLayoutComponent = windowLayoutComponent;
        mWindowLayoutComponent = windowLayoutComponent;
        mController = controller;
        mController = controller;
        final Bundle outSavedState = new Bundle();
        if (Flags.aeBackStackRestore()) {
            outSavedState.setClassLoader(TaskContainer.class.getClassLoader());
            registerOrganizer(false /* isSystemOrganizer */, outSavedState);
        } else {
            registerOrganizer();
            registerOrganizer();
        }
        }
        mBackupHelper = new BackupHelper(controller, outSavedState);
    }

    void scheduleBackup() {
        mBackupHelper.scheduleBackup();
    }


    /**
    /**
     * Deletes the specified container and all other associated and dependent containers in the same
     * Deletes the specified container and all other associated and dependent containers in the same
+50 −6
Original line number Original line Diff line number Diff line
@@ -32,6 +32,8 @@ import android.app.WindowConfiguration.WindowingMode;
import android.content.res.Configuration;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.graphics.Rect;
import android.os.IBinder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArraySet;
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.DisplayMetrics;
import android.util.Log;
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.ExpandContainersSplitType;
import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType;
import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType;


import com.android.window.flags.Flags;

import java.util.ArrayList;
import java.util.ArrayList;
import java.util.List;
import java.util.List;
import java.util.Set;
import java.util.Set;


/** Represents TaskFragments and split pairs below a Task. */
/** Represents TaskFragments and split pairs below a Task. */
class TaskContainer {
class TaskContainer implements Parcelable {
    private static final String TAG = TaskContainer.class.getSimpleName();
    private static final String TAG = TaskContainer.class.getSimpleName();


    /** The unique task id. */
    /** The unique task id. */
@@ -80,6 +84,9 @@ class TaskContainer {
    @NonNull
    @NonNull
    private TaskFragmentParentInfo mInfo;
    private TaskFragmentParentInfo mInfo;


    @NonNull
    private SplitController mSplitController;

    /**
    /**
     * TaskFragments that the organizer has requested to be closed. They should be removed when
     * TaskFragments that the organizer has requested to be closed. They should be removed when
     * the organizer receives
     * the organizer receives
@@ -120,8 +127,10 @@ class TaskContainer {
     *                        {@code activityInTask}.
     *                        {@code activityInTask}.
     * @param activityInTask  The {@link Activity} in the Task with {@code taskId}. It is used to
     * @param activityInTask  The {@link Activity} in the Task with {@code taskId}. It is used to
     *                        initialize the {@link TaskContainer} properties.
     *                        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) {
        if (taskId == INVALID_TASK_ID) {
            throw new IllegalArgumentException("Invalid Task id");
            throw new IllegalArgumentException("Invalid Task id");
        }
        }
@@ -136,6 +145,7 @@ class TaskContainer {
                true /* visible */,
                true /* visible */,
                true /* hasDirectActivity */,
                true /* hasDirectActivity */,
                null /* decorSurface */);
                null /* decorSurface */);
        mSplitController = splitController;
    }
    }


    int getTaskId() {
    int getTaskId() {
@@ -571,6 +581,12 @@ class TaskContainer {
        // Update overlay container after split pin container since the overlay should be on top of
        // Update overlay container after split pin container since the overlay should be on top of
        // pin container.
        // pin container.
        updateAlwaysOnTopOverlayIfNecessary();
        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() {
    private void updateAlwaysOnTopOverlayIfNecessary() {
@@ -664,6 +680,34 @@ class TaskContainer {
        return activityStacks;
        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} */
    /** A wrapper class which contains the information of {@link TaskContainer} */
    static final class TaskProperties {
    static final class TaskProperties {
        private final int mDisplayId;
        private final int mDisplayId;
+1 −1
Original line number Original line Diff line number Diff line
@@ -1203,7 +1203,7 @@ class TaskFragmentContainer {


            if (taskContainer == null) {
            if (taskContainer == null) {
                // Adding a TaskContainer if no existed one.
                // Adding a TaskContainer if no existed one.
                taskContainer = new TaskContainer(mTaskId, mActivityInTask);
                taskContainer = new TaskContainer(mTaskId, mActivityInTask, mSplitController);
                mSplitController.addTaskContainer(mTaskId, taskContainer);
                mSplitController.addTaskContainer(mTaskId, taskContainer);
            }
            }


Loading