• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.window.extensions.embedding;
18 
19 import static android.window.TaskFragmentOrganizer.KEY_RESTORE_TASK_FRAGMENTS_INFO;
20 import static android.window.TaskFragmentOrganizer.KEY_RESTORE_TASK_FRAGMENT_PARENT_INFO;
21 
22 import android.os.Build;
23 import android.os.Bundle;
24 import android.os.IBinder;
25 import android.os.Looper;
26 import android.os.MessageQueue;
27 import android.util.ArrayMap;
28 import android.util.Log;
29 import android.util.SparseArray;
30 import android.window.TaskFragmentInfo;
31 import android.window.TaskFragmentParentInfo;
32 import android.window.WindowContainerTransaction;
33 
34 import androidx.annotation.NonNull;
35 
36 import java.util.ArrayList;
37 import java.util.List;
38 import java.util.Set;
39 
40 /**
41  * Helper class to back up and restore the TaskFragmentOrganizer state, in order to resume
42  * organizing the TaskFragments if the app process is restarted.
43  */
44 @SuppressWarnings("GuardedBy")
45 class BackupHelper {
46     private static final String TAG = "BackupHelper";
47     private static final boolean DEBUG = Build.isDebuggable();
48 
49     private static final String KEY_TASK_CONTAINERS = "KEY_TASK_CONTAINERS";
50     @NonNull
51     private final SplitController mController;
52     @NonNull
53     private final SplitPresenter mPresenter;
54     @NonNull
55     private final BackupIdler mBackupIdler = new BackupIdler();
56     private boolean mBackupIdlerScheduled;
57     private boolean mSaveEmbeddingState = false;
58 
59     private final List<ParcelableTaskContainerData> mParcelableTaskContainerDataList =
60             new ArrayList<>();
61     private final ArrayMap<IBinder, TaskFragmentInfo> mTaskFragmentInfos = new ArrayMap<>();
62     private final SparseArray<TaskFragmentParentInfo> mTaskFragmentParentInfos =
63             new SparseArray<>();
64 
BackupHelper(@onNull SplitController splitController, @NonNull SplitPresenter splitPresenter, @NonNull Bundle savedState)65     BackupHelper(@NonNull SplitController splitController, @NonNull SplitPresenter splitPresenter,
66             @NonNull Bundle savedState) {
67         mController = splitController;
68         mPresenter = splitPresenter;
69 
70         if (!savedState.isEmpty()) {
71             restoreState(savedState);
72         }
73     }
74 
setAutoSaveEmbeddingState(boolean saveEmbeddingState)75     void setAutoSaveEmbeddingState(boolean saveEmbeddingState) {
76         if (mSaveEmbeddingState == saveEmbeddingState) {
77             return;
78         }
79 
80         Log.i(TAG, "Set save embedding state: " + saveEmbeddingState);
81         mSaveEmbeddingState = saveEmbeddingState;
82         if (!mSaveEmbeddingState) {
83             removeSavedState();
84             return;
85         }
86 
87         if (!hasPendingStateToRestore() && !mController.getTaskContainers().isEmpty()) {
88             scheduleBackup();
89         }
90     }
91     /**
92      * Schedules a back-up request. It is no-op if there was a request scheduled and not yet
93      * completed.
94      */
scheduleBackup()95     void scheduleBackup() {
96         if (!mSaveEmbeddingState) {
97             return;
98         }
99 
100         if (!mBackupIdlerScheduled) {
101             mBackupIdlerScheduled = true;
102             Looper.getMainLooper().getQueue().addIdleHandler(mBackupIdler);
103         }
104     }
105 
106     final class BackupIdler implements MessageQueue.IdleHandler {
107         @Override
queueIdle()108         public boolean queueIdle() {
109             synchronized (mController.mLock) {
110                 mBackupIdlerScheduled = false;
111                 saveState();
112             }
113             return false;
114         }
115     }
116 
saveState()117     private void saveState() {
118         final List<TaskContainer> taskContainers = mController.getTaskContainers();
119         if (taskContainers.isEmpty()) {
120             Log.w(TAG, "No task-container to back up");
121             return;
122         }
123 
124         if (DEBUG) Log.d(TAG, "Start to back up " + taskContainers);
125         final List<ParcelableTaskContainerData> parcelableTaskContainerDataList = new ArrayList<>(
126                 taskContainers.size());
127         for (TaskContainer taskContainer : taskContainers) {
128             parcelableTaskContainerDataList.add(taskContainer.getParcelableData());
129         }
130         final Bundle state = new Bundle();
131         state.setClassLoader(ParcelableTaskContainerData.class.getClassLoader());
132         state.putParcelableList(KEY_TASK_CONTAINERS, parcelableTaskContainerDataList);
133         mController.setSavedState(state);
134     }
135 
restoreState(@onNull Bundle savedState)136     private void restoreState(@NonNull Bundle savedState) {
137         if (savedState.isEmpty()) {
138             return;
139         }
140 
141         if (DEBUG) Log.d(TAG, "Start restoring saved-state");
142         mParcelableTaskContainerDataList.addAll(savedState.getParcelableArrayList(
143                 KEY_TASK_CONTAINERS, ParcelableTaskContainerData.class));
144         if (DEBUG) Log.d(TAG, "Retrieved tasks : " + mParcelableTaskContainerDataList.size());
145         if (mParcelableTaskContainerDataList.isEmpty()) {
146             return;
147         }
148 
149         final List<TaskFragmentInfo> infos = savedState.getParcelableArrayList(
150                 KEY_RESTORE_TASK_FRAGMENTS_INFO, TaskFragmentInfo.class);
151         for (TaskFragmentInfo info : infos) {
152             mTaskFragmentInfos.put(info.getFragmentToken(), info);
153             mPresenter.updateTaskFragmentInfo(info);
154         }
155 
156         final List<TaskFragmentParentInfo> parentInfos = savedState.getParcelableArrayList(
157                 KEY_RESTORE_TASK_FRAGMENT_PARENT_INFO,
158                 TaskFragmentParentInfo.class);
159         for (TaskFragmentParentInfo info : parentInfos) {
160             if (DEBUG) Log.d(TAG, "Retrieved: " + info);
161             mTaskFragmentParentInfos.put(info.getTaskId(), info);
162         }
163 
164         if (DEBUG) {
165             Log.d(TAG, "Retrieved task-fragment info: " + infos.size() + ", task info: "
166                     + parentInfos.size());
167         }
168     }
169 
abortTaskContainerRebuilding(@onNull WindowContainerTransaction wct)170     void abortTaskContainerRebuilding(@NonNull WindowContainerTransaction wct) {
171         // Clean-up the legacy states in the system
172         for (int i = mTaskFragmentInfos.size() - 1; i >= 0; i--) {
173             final TaskFragmentInfo info = mTaskFragmentInfos.valueAt(i);
174             mPresenter.deleteTaskFragment(wct, info.getFragmentToken());
175         }
176         removeSavedState();
177     }
178 
removeSavedState()179     private void removeSavedState() {
180         mPresenter.setSavedState(new Bundle());
181         mParcelableTaskContainerDataList.clear();
182         mTaskFragmentInfos.clear();
183         mTaskFragmentParentInfos.clear();
184     }
185 
hasPendingStateToRestore()186     boolean hasPendingStateToRestore() {
187         return !mParcelableTaskContainerDataList.isEmpty();
188     }
189 
190     /**
191      * Returns {@code true} if any of the {@link TaskContainer} is restored.
192      * Otherwise, returns {@code false}.
193      */
rebuildTaskContainers(@onNull WindowContainerTransaction wct, @NonNull Set<EmbeddingRule> rules)194     boolean rebuildTaskContainers(@NonNull WindowContainerTransaction wct,
195             @NonNull Set<EmbeddingRule> rules) {
196         if (mParcelableTaskContainerDataList.isEmpty()) {
197             return false;
198         }
199 
200         if (mTaskFragmentParentInfos.size() == 0) {
201             // No Task left in the WM hierarchy, remove the states and no need to restore.
202             if (DEBUG) Log.d(TAG, "Remove save states due to no task to restore.");
203             removeSavedState();
204             return false;
205         }
206 
207         final ArrayList<Integer> taskIdsInSystem = new ArrayList<>();
208         for (int i = mTaskFragmentParentInfos.size() - 1; i >= 0; --i) {
209             final TaskFragmentParentInfo parentInfo = mTaskFragmentParentInfos.valueAt(i);
210             taskIdsInSystem.add(parentInfo.getTaskId());
211         }
212 
213         if (DEBUG) Log.d(TAG, "Rebuilding TaskContainers.");
214         final ArrayMap<String, EmbeddingRule> embeddingRuleMap = new ArrayMap<>();
215         for (EmbeddingRule rule : rules) {
216             embeddingRuleMap.put(rule.getTag(), rule);
217             if (DEBUG) {
218                 Log.d(TAG, "Tag: " + rule.getTag() + " rule: " + rule);
219             }
220         }
221 
222         boolean restoredAny = false;
223         for (int i = mParcelableTaskContainerDataList.size() - 1; i >= 0; i--) {
224             final ParcelableTaskContainerData parcelableTaskContainerData =
225                     mParcelableTaskContainerDataList.get(i);
226             final List<String> tags = parcelableTaskContainerData.getSplitRuleTags();
227             if (!embeddingRuleMap.containsAll(tags)) {
228                 // has unknown tag, unable to restore.
229                 if (DEBUG) {
230                     Log.d(TAG, "Rebuilding TaskContainer abort! Unknown Tag. Task#"
231                             + parcelableTaskContainerData.mTaskId + ", tags = " + tags);
232                 }
233                 continue;
234             }
235 
236             mParcelableTaskContainerDataList.remove(parcelableTaskContainerData);
237             if (!taskIdsInSystem.contains(parcelableTaskContainerData.mTaskId)) {
238                 if (DEBUG) {
239                     Log.d(TAG, "Rebuilding TaskContainer abort! Not existed. Task#"
240                             + parcelableTaskContainerData.mTaskId);
241                 }
242                 continue;
243             }
244 
245             final TaskContainer taskContainer = new TaskContainer(parcelableTaskContainerData,
246                     mController, mTaskFragmentInfos);
247             if (DEBUG) Log.d(TAG, "Created TaskContainer " + taskContainer.getTaskId());
248             mController.addTaskContainer(taskContainer.getTaskId(), taskContainer);
249 
250             for (ParcelableSplitContainerData splitData :
251                     parcelableTaskContainerData.getParcelableSplitContainerDataList()) {
252                 final SplitRule rule = (SplitRule) embeddingRuleMap.get(splitData.mSplitRuleTag);
253                 assert rule != null;
254                 if (mController.getContainer(splitData.getPrimaryContainerToken()) != null
255                         && mController.getContainer(splitData.getSecondaryContainerToken())
256                         != null) {
257                     taskContainer.addSplitContainer(
258                             new SplitContainer(splitData, mController, rule));
259                 }
260             }
261 
262             mController.onTaskFragmentParentRestored(wct, taskContainer.getTaskId(),
263                     mTaskFragmentParentInfos.get(taskContainer.getTaskId()));
264             mTaskFragmentParentInfos.remove(taskContainer.getTaskId());
265             restoredAny = true;
266         }
267 
268         if (mParcelableTaskContainerDataList.isEmpty()) {
269             mTaskFragmentParentInfos.clear();
270             mTaskFragmentInfos.clear();
271         }
272         return restoredAny;
273     }
274 }