blob: a974332fd8522122f008b723d49ce8669b8aecf8 [file] [log] [blame]
Garfield Tan891146c2018-10-09 12:14:00 -07001/*
2 * Copyright (C) 2018 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
17package com.android.server.wm;
18
Garfield Tan5901e7c2020-02-07 17:12:22 -080019import android.annotation.Nullable;
Garfield Tan891146c2018-10-09 12:14:00 -070020import android.content.ComponentName;
Garfield Tan5901e7c2020-02-07 17:12:22 -080021import android.content.pm.ActivityInfo;
Garfield Tan891146c2018-10-09 12:14:00 -070022import android.content.pm.PackageManagerInternal;
23import android.graphics.Rect;
24import android.os.Environment;
25import android.util.ArrayMap;
26import android.util.ArraySet;
27import android.util.AtomicFile;
28import android.util.Slog;
29import android.util.SparseArray;
30import android.util.Xml;
31import android.view.DisplayInfo;
32
33import com.android.internal.annotations.VisibleForTesting;
34import com.android.internal.util.FastXmlSerializer;
35import com.android.server.LocalServices;
Christopher Tatee4f5f2d2019-10-08 15:49:43 -070036import com.android.server.pm.PackageList;
Garfield Tan891146c2018-10-09 12:14:00 -070037import com.android.server.wm.LaunchParamsController.LaunchParams;
38
39import libcore.io.IoUtils;
40
41import org.xmlpull.v1.XmlPullParser;
42import org.xmlpull.v1.XmlSerializer;
43
44import java.io.BufferedReader;
45import java.io.File;
46import java.io.FileOutputStream;
47import java.io.FileReader;
48import java.io.IOException;
49import java.io.StringWriter;
50import java.util.ArrayList;
51import java.util.List;
52import java.util.Map;
53import java.util.Objects;
54import java.util.Set;
55import java.util.function.IntFunction;
56
57/**
58 * Persister that saves launch parameters in memory and in storage. It saves the last seen state of
59 * tasks key-ed on task's user ID and the activity used to launch the task ({@link
Louis Changcdec0802019-11-11 11:45:07 +080060 * Task#realActivity}) and that's used to determine the launch params when the activity is
Garfield Tan891146c2018-10-09 12:14:00 -070061 * being launched again in {@link LaunchParamsController}.
62 *
63 * Need to hold {@link ActivityTaskManagerService#getGlobalLock()} to access this class.
64 */
65class LaunchParamsPersister {
66 private static final String TAG = "LaunchParamsPersister";
67 private static final String LAUNCH_PARAMS_DIRNAME = "launch_params";
68 private static final String LAUNCH_PARAMS_FILE_SUFFIX = ".xml";
69
70 // Chars below are used to escape the backslash in component name to underscore.
71 private static final char ORIGINAL_COMPONENT_SEPARATOR = '/';
72 private static final char ESCAPED_COMPONENT_SEPARATOR = '_';
73
74 private static final String TAG_LAUNCH_PARAMS = "launch_params";
75
76 private final PersisterQueue mPersisterQueue;
77 private final ActivityStackSupervisor mSupervisor;
78
79 /**
80 * A function that takes in user ID and returns a folder to store information of that user. Used
81 * to differentiate storage location in test environment and production environment.
82 */
83 private final IntFunction<File> mUserFolderGetter;
84
85 private PackageList mPackageList;
86
87 /**
88 * A dual layer map that first maps user ID to a secondary map, which maps component name (the
89 * launching activity of tasks) to {@link PersistableLaunchParams} that stores launch metadata
90 * that are stable across reboots.
91 */
Garfield Tan5901e7c2020-02-07 17:12:22 -080092 private final SparseArray<ArrayMap<ComponentName, PersistableLaunchParams>> mLaunchParamsMap =
Garfield Tan891146c2018-10-09 12:14:00 -070093 new SparseArray<>();
94
Garfield Tan5901e7c2020-02-07 17:12:22 -080095 /**
96 * A map from {@link android.content.pm.ActivityInfo.WindowLayout#windowLayoutAffinity} to
97 * activity's component name for reverse queries from window layout affinities to activities.
98 * Used to decide if we should use another activity's record with the same affinity.
99 */
100 private final ArrayMap<String, ArraySet<ComponentName>> mWindowLayoutAffinityMap =
101 new ArrayMap<>();
102
Garfield Tan891146c2018-10-09 12:14:00 -0700103 LaunchParamsPersister(PersisterQueue persisterQueue, ActivityStackSupervisor supervisor) {
104 this(persisterQueue, supervisor, Environment::getDataSystemCeDirectory);
105 }
106
107 @VisibleForTesting
108 LaunchParamsPersister(PersisterQueue persisterQueue, ActivityStackSupervisor supervisor,
109 IntFunction<File> userFolderGetter) {
110 mPersisterQueue = persisterQueue;
111 mSupervisor = supervisor;
112 mUserFolderGetter = userFolderGetter;
113 }
114
115 void onSystemReady() {
116 PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
117 mPackageList = pmi.getPackageList(new PackageListObserver());
118 }
119
120 void onUnlockUser(int userId) {
121 loadLaunchParams(userId);
122 }
123
124 void onCleanupUser(int userId) {
Garfield Tan5901e7c2020-02-07 17:12:22 -0800125 mLaunchParamsMap.remove(userId);
Garfield Tan891146c2018-10-09 12:14:00 -0700126 }
127
128 private void loadLaunchParams(int userId) {
129 final List<File> filesToDelete = new ArrayList<>();
130 final File launchParamsFolder = getLaunchParamFolder(userId);
131 if (!launchParamsFolder.isDirectory()) {
132 Slog.i(TAG, "Didn't find launch param folder for user " + userId);
133 return;
134 }
135
136 final Set<String> packages = new ArraySet<>(mPackageList.getPackageNames());
137
138 final File[] paramsFiles = launchParamsFolder.listFiles();
139 final ArrayMap<ComponentName, PersistableLaunchParams> map =
140 new ArrayMap<>(paramsFiles.length);
Garfield Tan5901e7c2020-02-07 17:12:22 -0800141 mLaunchParamsMap.put(userId, map);
Garfield Tan891146c2018-10-09 12:14:00 -0700142
143 for (File paramsFile : paramsFiles) {
144 if (!paramsFile.isFile()) {
145 Slog.w(TAG, paramsFile.getAbsolutePath() + " is not a file.");
146 continue;
147 }
148 if (!paramsFile.getName().endsWith(LAUNCH_PARAMS_FILE_SUFFIX)) {
149 Slog.w(TAG, "Unexpected params file name: " + paramsFile.getName());
150 filesToDelete.add(paramsFile);
151 continue;
152 }
153 final String paramsFileName = paramsFile.getName();
154 final String componentNameString = paramsFileName.substring(
155 0 /* beginIndex */,
156 paramsFileName.length() - LAUNCH_PARAMS_FILE_SUFFIX.length())
157 .replace(ESCAPED_COMPONENT_SEPARATOR, ORIGINAL_COMPONENT_SEPARATOR);
158 final ComponentName name = ComponentName.unflattenFromString(
159 componentNameString);
160 if (name == null) {
161 Slog.w(TAG, "Unexpected file name: " + paramsFileName);
162 filesToDelete.add(paramsFile);
163 continue;
164 }
165
166 if (!packages.contains(name.getPackageName())) {
167 // Rare case. PersisterQueue doesn't have a chance to remove files for removed
168 // packages last time.
169 filesToDelete.add(paramsFile);
170 continue;
171 }
172
173 BufferedReader reader = null;
174 try {
175 reader = new BufferedReader(new FileReader(paramsFile));
176 final PersistableLaunchParams params = new PersistableLaunchParams();
177 final XmlPullParser parser = Xml.newPullParser();
178 parser.setInput(reader);
179 int event;
180 while ((event = parser.next()) != XmlPullParser.END_DOCUMENT
181 && event != XmlPullParser.END_TAG) {
182 if (event != XmlPullParser.START_TAG) {
183 continue;
184 }
185
186 final String tagName = parser.getName();
187 if (!TAG_LAUNCH_PARAMS.equals(tagName)) {
188 Slog.w(TAG, "Unexpected tag name: " + tagName);
189 continue;
190 }
191
Garfield Tan5901e7c2020-02-07 17:12:22 -0800192 params.restore(paramsFile, parser);
Garfield Tan891146c2018-10-09 12:14:00 -0700193 }
194
195 map.put(name, params);
Garfield Tan5901e7c2020-02-07 17:12:22 -0800196 addComponentNameToLaunchParamAffinityMapIfNotNull(
197 name, params.mWindowLayoutAffinity);
Garfield Tan891146c2018-10-09 12:14:00 -0700198 } catch (Exception e) {
199 Slog.w(TAG, "Failed to restore launch params for " + name, e);
200 filesToDelete.add(paramsFile);
201 } finally {
202 IoUtils.closeQuietly(reader);
203 }
204 }
205
206 if (!filesToDelete.isEmpty()) {
207 mPersisterQueue.addItem(new CleanUpComponentQueueItem(filesToDelete), true);
208 }
209 }
210
Louis Changcdec0802019-11-11 11:45:07 +0800211 void saveTask(Task task) {
Garfield Tan8b096b22020-01-07 14:55:20 -0800212 saveTask(task, task.getDisplayContent());
213 }
214
215 void saveTask(Task task, DisplayContent display) {
Garfield Tan891146c2018-10-09 12:14:00 -0700216 final ComponentName name = task.realActivity;
Wale Ogunwale4e79a1c2019-10-05 20:52:40 -0700217 final int userId = task.mUserId;
Garfield Tan891146c2018-10-09 12:14:00 -0700218 PersistableLaunchParams params;
Garfield Tan5901e7c2020-02-07 17:12:22 -0800219 ArrayMap<ComponentName, PersistableLaunchParams> map = mLaunchParamsMap.get(userId);
Garfield Tan891146c2018-10-09 12:14:00 -0700220 if (map == null) {
221 map = new ArrayMap<>();
Garfield Tan5901e7c2020-02-07 17:12:22 -0800222 mLaunchParamsMap.put(userId, map);
Garfield Tan891146c2018-10-09 12:14:00 -0700223 }
224
Garfield Tan5901e7c2020-02-07 17:12:22 -0800225 params = map.computeIfAbsent(name, componentName -> new PersistableLaunchParams());
Garfield Tan8b096b22020-01-07 14:55:20 -0800226 final boolean changed = saveTaskToLaunchParam(task, display, params);
Garfield Tan891146c2018-10-09 12:14:00 -0700227
Garfield Tan5901e7c2020-02-07 17:12:22 -0800228 addComponentNameToLaunchParamAffinityMapIfNotNull(name, params.mWindowLayoutAffinity);
229
Garfield Tan891146c2018-10-09 12:14:00 -0700230 if (changed) {
231 mPersisterQueue.updateLastOrAddItem(
232 new LaunchParamsWriteQueueItem(userId, name, params),
233 /* flush */ false);
234 }
235 }
236
Garfield Tan8b096b22020-01-07 14:55:20 -0800237 private boolean saveTaskToLaunchParam(
238 Task task, DisplayContent display, PersistableLaunchParams params) {
Garfield Tan891146c2018-10-09 12:14:00 -0700239 final DisplayInfo info = new DisplayInfo();
240 display.mDisplay.getDisplayInfo(info);
241
242 boolean changed = !Objects.equals(params.mDisplayUniqueId, info.uniqueId);
243 params.mDisplayUniqueId = info.uniqueId;
244
Garfield Tan8b096b22020-01-07 14:55:20 -0800245 changed |= params.mWindowingMode != task.getWindowingMode();
246 params.mWindowingMode = task.getWindowingMode();
Garfield Tan891146c2018-10-09 12:14:00 -0700247
248 if (task.mLastNonFullscreenBounds != null) {
249 changed |= !Objects.equals(params.mBounds, task.mLastNonFullscreenBounds);
250 params.mBounds.set(task.mLastNonFullscreenBounds);
251 } else {
252 changed |= !params.mBounds.isEmpty();
253 params.mBounds.setEmpty();
254 }
255
Garfield Tan5901e7c2020-02-07 17:12:22 -0800256 String launchParamAffinity = task.mWindowLayoutAffinity;
257 changed |= Objects.equals(launchParamAffinity, params.mWindowLayoutAffinity);
258 params.mWindowLayoutAffinity = launchParamAffinity;
259
260 if (changed) {
261 params.mTimestamp = System.currentTimeMillis();
262 }
263
Garfield Tan891146c2018-10-09 12:14:00 -0700264 return changed;
265 }
266
Garfield Tan5901e7c2020-02-07 17:12:22 -0800267 private void addComponentNameToLaunchParamAffinityMapIfNotNull(
268 ComponentName name, String launchParamAffinity) {
269 if (launchParamAffinity == null) {
270 return;
271 }
272 mWindowLayoutAffinityMap.computeIfAbsent(launchParamAffinity, affinity -> new ArraySet<>())
273 .add(name);
274 }
275
Louis Changcdec0802019-11-11 11:45:07 +0800276 void getLaunchParams(Task task, ActivityRecord activity, LaunchParams outParams) {
Wale Ogunwale8b19de92018-11-29 19:58:26 -0800277 final ComponentName name = task != null ? task.realActivity : activity.mActivityComponent;
Wale Ogunwale4e79a1c2019-10-05 20:52:40 -0700278 final int userId = task != null ? task.mUserId : activity.mUserId;
Garfield Tan5901e7c2020-02-07 17:12:22 -0800279 final String windowLayoutAffinity;
280 if (task != null) {
281 windowLayoutAffinity = task.mWindowLayoutAffinity;
282 } else {
283 ActivityInfo.WindowLayout layout = activity.info.windowLayout;
284 windowLayoutAffinity = layout == null ? null : layout.windowLayoutAffinity;
285 }
Garfield Tan891146c2018-10-09 12:14:00 -0700286
287 outParams.reset();
Garfield Tan5901e7c2020-02-07 17:12:22 -0800288 Map<ComponentName, PersistableLaunchParams> map = mLaunchParamsMap.get(userId);
Garfield Tan891146c2018-10-09 12:14:00 -0700289 if (map == null) {
290 return;
291 }
Garfield Tan5901e7c2020-02-07 17:12:22 -0800292
293 // First use its own record as a reference.
294 PersistableLaunchParams persistableParams = map.get(name);
295 // Next we'll compare these params against all existing params with the same affinity and
296 // use the newest one.
297 if (windowLayoutAffinity != null
298 && mWindowLayoutAffinityMap.get(windowLayoutAffinity) != null) {
299 ArraySet<ComponentName> candidates = mWindowLayoutAffinityMap.get(windowLayoutAffinity);
300 for (int i = 0; i < candidates.size(); ++i) {
301 ComponentName candidate = candidates.valueAt(i);
302 final PersistableLaunchParams candidateParams = map.get(candidate);
303 if (candidateParams == null) {
304 continue;
305 }
306
307 if (persistableParams == null
308 || candidateParams.mTimestamp > persistableParams.mTimestamp) {
309 persistableParams = candidateParams;
310 }
311 }
312 }
Garfield Tan891146c2018-10-09 12:14:00 -0700313
314 if (persistableParams == null) {
315 return;
316 }
317
Louis Chang149d5c82019-12-30 09:47:39 +0800318 final DisplayContent display = mSupervisor.mRootWindowContainer.getDisplayContent(
Garfield Tan891146c2018-10-09 12:14:00 -0700319 persistableParams.mDisplayUniqueId);
320 if (display != null) {
Andrii Kulian1cfcae82020-04-10 12:44:38 -0700321 // TODO(b/153764726): Investigate if task display area needs to be persisted vs
322 // always choosing the default one.
323 outParams.mPreferredTaskDisplayArea = display.getDefaultTaskDisplayArea();
Garfield Tan891146c2018-10-09 12:14:00 -0700324 }
325 outParams.mWindowingMode = persistableParams.mWindowingMode;
326 outParams.mBounds.set(persistableParams.mBounds);
327 }
328
Garfield Tan01548632018-11-27 10:15:48 -0800329 void removeRecordForPackage(String packageName) {
Garfield Tan891146c2018-10-09 12:14:00 -0700330 final List<File> fileToDelete = new ArrayList<>();
Garfield Tan5901e7c2020-02-07 17:12:22 -0800331 for (int i = 0; i < mLaunchParamsMap.size(); ++i) {
332 int userId = mLaunchParamsMap.keyAt(i);
Garfield Tan891146c2018-10-09 12:14:00 -0700333 final File launchParamsFolder = getLaunchParamFolder(userId);
Garfield Tan5901e7c2020-02-07 17:12:22 -0800334 ArrayMap<ComponentName, PersistableLaunchParams> map = mLaunchParamsMap.valueAt(i);
Garfield Tan891146c2018-10-09 12:14:00 -0700335 for (int j = map.size() - 1; j >= 0; --j) {
336 final ComponentName name = map.keyAt(j);
337 if (name.getPackageName().equals(packageName)) {
338 map.removeAt(j);
339 fileToDelete.add(getParamFile(launchParamsFolder, name));
340 }
341 }
342 }
343
344 synchronized (mPersisterQueue) {
345 mPersisterQueue.removeItems(
346 item -> item.mComponentName.getPackageName().equals(packageName),
347 LaunchParamsWriteQueueItem.class);
348
349 mPersisterQueue.addItem(new CleanUpComponentQueueItem(fileToDelete), true);
350 }
351 }
352
353 private File getParamFile(File launchParamFolder, ComponentName name) {
354 final String componentNameString = name.flattenToShortString()
355 .replace(ORIGINAL_COMPONENT_SEPARATOR, ESCAPED_COMPONENT_SEPARATOR);
356 return new File(launchParamFolder, componentNameString + LAUNCH_PARAMS_FILE_SUFFIX);
357 }
358
359 private File getLaunchParamFolder(int userId) {
360 final File userFolder = mUserFolderGetter.apply(userId);
361 return new File(userFolder, LAUNCH_PARAMS_DIRNAME);
362 }
363
364 private class PackageListObserver implements PackageManagerInternal.PackageListObserver {
365 @Override
Chenbo Fengde8e3b72019-02-21 14:24:24 -0800366 public void onPackageAdded(String packageName, int uid) { }
Garfield Tan891146c2018-10-09 12:14:00 -0700367
368 @Override
Chenbo Fengde8e3b72019-02-21 14:24:24 -0800369 public void onPackageRemoved(String packageName, int uid) {
Garfield Tan01548632018-11-27 10:15:48 -0800370 removeRecordForPackage(packageName);
Garfield Tan891146c2018-10-09 12:14:00 -0700371 }
372 }
373
374 private class LaunchParamsWriteQueueItem
375 implements PersisterQueue.WriteQueueItem<LaunchParamsWriteQueueItem> {
376 private final int mUserId;
377 private final ComponentName mComponentName;
378
379 private PersistableLaunchParams mLaunchParams;
380
381 private LaunchParamsWriteQueueItem(int userId, ComponentName componentName,
382 PersistableLaunchParams launchParams) {
383 mUserId = userId;
384 mComponentName = componentName;
385 mLaunchParams = launchParams;
386 }
387
388 private StringWriter saveParamsToXml() {
389 final StringWriter writer = new StringWriter();
390 final XmlSerializer serializer = new FastXmlSerializer();
391
392 try {
393 serializer.setOutput(writer);
394 serializer.startDocument(/* encoding */ null, /* standalone */ true);
395 serializer.startTag(null, TAG_LAUNCH_PARAMS);
396
397 mLaunchParams.saveToXml(serializer);
398
399 serializer.endTag(null, TAG_LAUNCH_PARAMS);
400 serializer.endDocument();
401 serializer.flush();
402
403 return writer;
404 } catch (IOException e) {
405 return null;
406 }
407 }
408
409 @Override
410 public void process() {
411 final StringWriter writer = saveParamsToXml();
412
413 final File launchParamFolder = getLaunchParamFolder(mUserId);
414 if (!launchParamFolder.isDirectory() && !launchParamFolder.mkdirs()) {
415 Slog.w(TAG, "Failed to create folder for " + mUserId);
416 return;
417 }
418
419 final File launchParamFile = getParamFile(launchParamFolder, mComponentName);
420 final AtomicFile atomicFile = new AtomicFile(launchParamFile);
421
422 FileOutputStream stream = null;
423 try {
424 stream = atomicFile.startWrite();
425 stream.write(writer.toString().getBytes());
426 } catch (Exception e) {
427 Slog.e(TAG, "Failed to write param file for " + mComponentName, e);
428 if (stream != null) {
429 atomicFile.failWrite(stream);
430 }
431 return;
432 }
433 atomicFile.finishWrite(stream);
434 }
435
436 @Override
437 public boolean matches(LaunchParamsWriteQueueItem item) {
438 return mUserId == item.mUserId && mComponentName.equals(item.mComponentName);
439 }
440
441 @Override
442 public void updateFrom(LaunchParamsWriteQueueItem item) {
443 mLaunchParams = item.mLaunchParams;
444 }
445 }
446
447 private class CleanUpComponentQueueItem implements PersisterQueue.WriteQueueItem {
448 private final List<File> mComponentFiles;
449
450 private CleanUpComponentQueueItem(List<File> componentFiles) {
451 mComponentFiles = componentFiles;
452 }
453
454 @Override
455 public void process() {
456 for (File file : mComponentFiles) {
457 if (!file.delete()) {
458 Slog.w(TAG, "Failed to delete " + file.getAbsolutePath());
459 }
460 }
461 }
462 }
463
464 private class PersistableLaunchParams {
465 private static final String ATTR_WINDOWING_MODE = "windowing_mode";
466 private static final String ATTR_DISPLAY_UNIQUE_ID = "display_unique_id";
467 private static final String ATTR_BOUNDS = "bounds";
Garfield Tan5901e7c2020-02-07 17:12:22 -0800468 private static final String ATTR_WINDOW_LAYOUT_AFFINITY = "window_layout_affinity";
Garfield Tan891146c2018-10-09 12:14:00 -0700469
470 /** The bounds within the parent container. */
471 final Rect mBounds = new Rect();
472
Louis Changcdec0802019-11-11 11:45:07 +0800473 /** The unique id of the display the {@link Task} would prefer to be on. */
Garfield Tan891146c2018-10-09 12:14:00 -0700474 String mDisplayUniqueId;
475
476 /** The windowing mode to be in. */
477 int mWindowingMode;
478
Garfield Tan5901e7c2020-02-07 17:12:22 -0800479 /**
480 * Last {@link android.content.pm.ActivityInfo.WindowLayout#windowLayoutAffinity} of the
481 * window.
482 */
483 @Nullable String mWindowLayoutAffinity;
484
485 /**
486 * Timestamp from {@link System#currentTimeMillis()} when this record is captured, or last
487 * modified time when the record is restored from storage.
488 */
489 long mTimestamp;
490
Garfield Tan891146c2018-10-09 12:14:00 -0700491 void saveToXml(XmlSerializer serializer) throws IOException {
492 serializer.attribute(null, ATTR_DISPLAY_UNIQUE_ID, mDisplayUniqueId);
493 serializer.attribute(null, ATTR_WINDOWING_MODE,
494 Integer.toString(mWindowingMode));
495 serializer.attribute(null, ATTR_BOUNDS, mBounds.flattenToString());
Garfield Tan5901e7c2020-02-07 17:12:22 -0800496 if (mWindowLayoutAffinity != null) {
497 serializer.attribute(null, ATTR_WINDOW_LAYOUT_AFFINITY, mWindowLayoutAffinity);
498 }
Garfield Tan891146c2018-10-09 12:14:00 -0700499 }
500
Garfield Tan5901e7c2020-02-07 17:12:22 -0800501 void restore(File xmlFile, XmlPullParser parser) {
Garfield Tan891146c2018-10-09 12:14:00 -0700502 for (int i = 0; i < parser.getAttributeCount(); ++i) {
503 final String attrValue = parser.getAttributeValue(i);
504 switch (parser.getAttributeName(i)) {
505 case ATTR_DISPLAY_UNIQUE_ID:
506 mDisplayUniqueId = attrValue;
507 break;
508 case ATTR_WINDOWING_MODE:
509 mWindowingMode = Integer.parseInt(attrValue);
510 break;
511 case ATTR_BOUNDS: {
512 final Rect bounds = Rect.unflattenFromString(attrValue);
513 if (bounds != null) {
514 mBounds.set(bounds);
515 }
516 break;
517 }
Garfield Tan5901e7c2020-02-07 17:12:22 -0800518 case ATTR_WINDOW_LAYOUT_AFFINITY:
519 mWindowLayoutAffinity = attrValue;
520 break;
Garfield Tan891146c2018-10-09 12:14:00 -0700521 }
522 }
Garfield Tan5901e7c2020-02-07 17:12:22 -0800523
524 // The modified time could be a few seconds later than the timestamp when the record is
525 // captured, which is a good enough estimate to the capture time after a reboot or a
526 // user switch.
527 mTimestamp = xmlFile.lastModified();
Garfield Tan891146c2018-10-09 12:14:00 -0700528 }
529
530 @Override
531 public String toString() {
532 final StringBuilder builder = new StringBuilder("PersistableLaunchParams{");
Garfield Tan5901e7c2020-02-07 17:12:22 -0800533 builder.append(" windowingMode=" + mWindowingMode);
Garfield Tan891146c2018-10-09 12:14:00 -0700534 builder.append(" displayUniqueId=" + mDisplayUniqueId);
535 builder.append(" bounds=" + mBounds);
Garfield Tan5901e7c2020-02-07 17:12:22 -0800536 if (mWindowLayoutAffinity != null) {
537 builder.append(" launchParamsAffinity=" + mWindowLayoutAffinity);
538 }
539 builder.append(" timestamp=" + mTimestamp);
Garfield Tan891146c2018-10-09 12:14:00 -0700540 builder.append(" }");
541 return builder.toString();
542 }
543 }
544}