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 }