blob: 3062d34780b650d04021593c38336fe7733ec0c0 [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
19import android.content.ComponentName;
20import android.content.pm.PackageList;
21import android.content.pm.PackageManagerInternal;
22import android.graphics.Rect;
23import android.os.Environment;
24import android.util.ArrayMap;
25import android.util.ArraySet;
26import android.util.AtomicFile;
27import android.util.Slog;
28import android.util.SparseArray;
29import android.util.Xml;
30import android.view.DisplayInfo;
31
32import com.android.internal.annotations.VisibleForTesting;
33import com.android.internal.util.FastXmlSerializer;
34import com.android.server.LocalServices;
35import com.android.server.wm.LaunchParamsController.LaunchParams;
36
37import libcore.io.IoUtils;
38
39import org.xmlpull.v1.XmlPullParser;
40import org.xmlpull.v1.XmlSerializer;
41
42import java.io.BufferedReader;
43import java.io.File;
44import java.io.FileOutputStream;
45import java.io.FileReader;
46import java.io.IOException;
47import java.io.StringWriter;
48import java.util.ArrayList;
49import java.util.List;
50import java.util.Map;
51import java.util.Objects;
52import java.util.Set;
53import java.util.function.IntFunction;
54
55/**
56 * Persister that saves launch parameters in memory and in storage. It saves the last seen state of
57 * tasks key-ed on task's user ID and the activity used to launch the task ({@link
58 * TaskRecord#realActivity}) and that's used to determine the launch params when the activity is
59 * being launched again in {@link LaunchParamsController}.
60 *
61 * Need to hold {@link ActivityTaskManagerService#getGlobalLock()} to access this class.
62 */
63class LaunchParamsPersister {
64 private static final String TAG = "LaunchParamsPersister";
65 private static final String LAUNCH_PARAMS_DIRNAME = "launch_params";
66 private static final String LAUNCH_PARAMS_FILE_SUFFIX = ".xml";
67
68 // Chars below are used to escape the backslash in component name to underscore.
69 private static final char ORIGINAL_COMPONENT_SEPARATOR = '/';
70 private static final char ESCAPED_COMPONENT_SEPARATOR = '_';
71
72 private static final String TAG_LAUNCH_PARAMS = "launch_params";
73
74 private final PersisterQueue mPersisterQueue;
75 private final ActivityStackSupervisor mSupervisor;
76
77 /**
78 * A function that takes in user ID and returns a folder to store information of that user. Used
79 * to differentiate storage location in test environment and production environment.
80 */
81 private final IntFunction<File> mUserFolderGetter;
82
83 private PackageList mPackageList;
84
85 /**
86 * A dual layer map that first maps user ID to a secondary map, which maps component name (the
87 * launching activity of tasks) to {@link PersistableLaunchParams} that stores launch metadata
88 * that are stable across reboots.
89 */
90 private final SparseArray<ArrayMap<ComponentName, PersistableLaunchParams>> mMap =
91 new SparseArray<>();
92
93 LaunchParamsPersister(PersisterQueue persisterQueue, ActivityStackSupervisor supervisor) {
94 this(persisterQueue, supervisor, Environment::getDataSystemCeDirectory);
95 }
96
97 @VisibleForTesting
98 LaunchParamsPersister(PersisterQueue persisterQueue, ActivityStackSupervisor supervisor,
99 IntFunction<File> userFolderGetter) {
100 mPersisterQueue = persisterQueue;
101 mSupervisor = supervisor;
102 mUserFolderGetter = userFolderGetter;
103 }
104
105 void onSystemReady() {
106 PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
107 mPackageList = pmi.getPackageList(new PackageListObserver());
108 }
109
110 void onUnlockUser(int userId) {
111 loadLaunchParams(userId);
112 }
113
114 void onCleanupUser(int userId) {
115 mMap.remove(userId);
116 }
117
118 private void loadLaunchParams(int userId) {
119 final List<File> filesToDelete = new ArrayList<>();
120 final File launchParamsFolder = getLaunchParamFolder(userId);
121 if (!launchParamsFolder.isDirectory()) {
122 Slog.i(TAG, "Didn't find launch param folder for user " + userId);
123 return;
124 }
125
126 final Set<String> packages = new ArraySet<>(mPackageList.getPackageNames());
127
128 final File[] paramsFiles = launchParamsFolder.listFiles();
129 final ArrayMap<ComponentName, PersistableLaunchParams> map =
130 new ArrayMap<>(paramsFiles.length);
131 mMap.put(userId, map);
132
133 for (File paramsFile : paramsFiles) {
134 if (!paramsFile.isFile()) {
135 Slog.w(TAG, paramsFile.getAbsolutePath() + " is not a file.");
136 continue;
137 }
138 if (!paramsFile.getName().endsWith(LAUNCH_PARAMS_FILE_SUFFIX)) {
139 Slog.w(TAG, "Unexpected params file name: " + paramsFile.getName());
140 filesToDelete.add(paramsFile);
141 continue;
142 }
143 final String paramsFileName = paramsFile.getName();
144 final String componentNameString = paramsFileName.substring(
145 0 /* beginIndex */,
146 paramsFileName.length() - LAUNCH_PARAMS_FILE_SUFFIX.length())
147 .replace(ESCAPED_COMPONENT_SEPARATOR, ORIGINAL_COMPONENT_SEPARATOR);
148 final ComponentName name = ComponentName.unflattenFromString(
149 componentNameString);
150 if (name == null) {
151 Slog.w(TAG, "Unexpected file name: " + paramsFileName);
152 filesToDelete.add(paramsFile);
153 continue;
154 }
155
156 if (!packages.contains(name.getPackageName())) {
157 // Rare case. PersisterQueue doesn't have a chance to remove files for removed
158 // packages last time.
159 filesToDelete.add(paramsFile);
160 continue;
161 }
162
163 BufferedReader reader = null;
164 try {
165 reader = new BufferedReader(new FileReader(paramsFile));
166 final PersistableLaunchParams params = new PersistableLaunchParams();
167 final XmlPullParser parser = Xml.newPullParser();
168 parser.setInput(reader);
169 int event;
170 while ((event = parser.next()) != XmlPullParser.END_DOCUMENT
171 && event != XmlPullParser.END_TAG) {
172 if (event != XmlPullParser.START_TAG) {
173 continue;
174 }
175
176 final String tagName = parser.getName();
177 if (!TAG_LAUNCH_PARAMS.equals(tagName)) {
178 Slog.w(TAG, "Unexpected tag name: " + tagName);
179 continue;
180 }
181
182 params.restoreFromXml(parser);
183 }
184
185 map.put(name, params);
186 } catch (Exception e) {
187 Slog.w(TAG, "Failed to restore launch params for " + name, e);
188 filesToDelete.add(paramsFile);
189 } finally {
190 IoUtils.closeQuietly(reader);
191 }
192 }
193
194 if (!filesToDelete.isEmpty()) {
195 mPersisterQueue.addItem(new CleanUpComponentQueueItem(filesToDelete), true);
196 }
197 }
198
199 void saveTask(TaskRecord task) {
200 final ComponentName name = task.realActivity;
201 final int userId = task.userId;
202 PersistableLaunchParams params;
203 ArrayMap<ComponentName, PersistableLaunchParams> map = mMap.get(userId);
204 if (map == null) {
205 map = new ArrayMap<>();
206 mMap.put(userId, map);
207 }
208
209 params = map.get(name);
210 if (params == null) {
211 params = new PersistableLaunchParams();
212 map.put(name, params);
213 }
214 final boolean changed = saveTaskToLaunchParam(task, params);
215
216 if (changed) {
217 mPersisterQueue.updateLastOrAddItem(
218 new LaunchParamsWriteQueueItem(userId, name, params),
219 /* flush */ false);
220 }
221 }
222
223 private boolean saveTaskToLaunchParam(TaskRecord task, PersistableLaunchParams params) {
224 final ActivityStack<?> stack = task.getStack();
225 final int displayId = stack.mDisplayId;
Wale Ogunwaled32da472018-11-16 07:19:28 -0800226 final ActivityDisplay display =
227 mSupervisor.mRootActivityContainer.getActivityDisplay(displayId);
Garfield Tan891146c2018-10-09 12:14:00 -0700228 final DisplayInfo info = new DisplayInfo();
229 display.mDisplay.getDisplayInfo(info);
230
231 boolean changed = !Objects.equals(params.mDisplayUniqueId, info.uniqueId);
232 params.mDisplayUniqueId = info.uniqueId;
233
234 changed |= params.mWindowingMode != stack.getWindowingMode();
235 params.mWindowingMode = stack.getWindowingMode();
236
237 if (task.mLastNonFullscreenBounds != null) {
238 changed |= !Objects.equals(params.mBounds, task.mLastNonFullscreenBounds);
239 params.mBounds.set(task.mLastNonFullscreenBounds);
240 } else {
241 changed |= !params.mBounds.isEmpty();
242 params.mBounds.setEmpty();
243 }
244
245 return changed;
246 }
247
248 void getLaunchParams(TaskRecord task, ActivityRecord activity, LaunchParams outParams) {
Wale Ogunwale8b19de92018-11-29 19:58:26 -0800249 final ComponentName name = task != null ? task.realActivity : activity.mActivityComponent;
250 final int userId = task != null ? task.userId : activity.mUserId;
Garfield Tan891146c2018-10-09 12:14:00 -0700251
252 outParams.reset();
253 Map<ComponentName, PersistableLaunchParams> map = mMap.get(userId);
254 if (map == null) {
255 return;
256 }
257 final PersistableLaunchParams persistableParams = map.get(name);
258
259 if (persistableParams == null) {
260 return;
261 }
262
Wale Ogunwaled32da472018-11-16 07:19:28 -0800263 final ActivityDisplay display = mSupervisor.mRootActivityContainer.getActivityDisplay(
Garfield Tan891146c2018-10-09 12:14:00 -0700264 persistableParams.mDisplayUniqueId);
265 if (display != null) {
266 outParams.mPreferredDisplayId = display.mDisplayId;
267 }
268 outParams.mWindowingMode = persistableParams.mWindowingMode;
269 outParams.mBounds.set(persistableParams.mBounds);
270 }
271
Garfield Tan01548632018-11-27 10:15:48 -0800272 void removeRecordForPackage(String packageName) {
Garfield Tan891146c2018-10-09 12:14:00 -0700273 final List<File> fileToDelete = new ArrayList<>();
274 for (int i = 0; i < mMap.size(); ++i) {
275 int userId = mMap.keyAt(i);
276 final File launchParamsFolder = getLaunchParamFolder(userId);
277 ArrayMap<ComponentName, PersistableLaunchParams> map = mMap.valueAt(i);
278 for (int j = map.size() - 1; j >= 0; --j) {
279 final ComponentName name = map.keyAt(j);
280 if (name.getPackageName().equals(packageName)) {
281 map.removeAt(j);
282 fileToDelete.add(getParamFile(launchParamsFolder, name));
283 }
284 }
285 }
286
287 synchronized (mPersisterQueue) {
288 mPersisterQueue.removeItems(
289 item -> item.mComponentName.getPackageName().equals(packageName),
290 LaunchParamsWriteQueueItem.class);
291
292 mPersisterQueue.addItem(new CleanUpComponentQueueItem(fileToDelete), true);
293 }
294 }
295
296 private File getParamFile(File launchParamFolder, ComponentName name) {
297 final String componentNameString = name.flattenToShortString()
298 .replace(ORIGINAL_COMPONENT_SEPARATOR, ESCAPED_COMPONENT_SEPARATOR);
299 return new File(launchParamFolder, componentNameString + LAUNCH_PARAMS_FILE_SUFFIX);
300 }
301
302 private File getLaunchParamFolder(int userId) {
303 final File userFolder = mUserFolderGetter.apply(userId);
304 return new File(userFolder, LAUNCH_PARAMS_DIRNAME);
305 }
306
307 private class PackageListObserver implements PackageManagerInternal.PackageListObserver {
308 @Override
309 public void onPackageAdded(String packageName) { }
310
311 @Override
312 public void onPackageRemoved(String packageName) {
Garfield Tan01548632018-11-27 10:15:48 -0800313 removeRecordForPackage(packageName);
Garfield Tan891146c2018-10-09 12:14:00 -0700314 }
315 }
316
317 private class LaunchParamsWriteQueueItem
318 implements PersisterQueue.WriteQueueItem<LaunchParamsWriteQueueItem> {
319 private final int mUserId;
320 private final ComponentName mComponentName;
321
322 private PersistableLaunchParams mLaunchParams;
323
324 private LaunchParamsWriteQueueItem(int userId, ComponentName componentName,
325 PersistableLaunchParams launchParams) {
326 mUserId = userId;
327 mComponentName = componentName;
328 mLaunchParams = launchParams;
329 }
330
331 private StringWriter saveParamsToXml() {
332 final StringWriter writer = new StringWriter();
333 final XmlSerializer serializer = new FastXmlSerializer();
334
335 try {
336 serializer.setOutput(writer);
337 serializer.startDocument(/* encoding */ null, /* standalone */ true);
338 serializer.startTag(null, TAG_LAUNCH_PARAMS);
339
340 mLaunchParams.saveToXml(serializer);
341
342 serializer.endTag(null, TAG_LAUNCH_PARAMS);
343 serializer.endDocument();
344 serializer.flush();
345
346 return writer;
347 } catch (IOException e) {
348 return null;
349 }
350 }
351
352 @Override
353 public void process() {
354 final StringWriter writer = saveParamsToXml();
355
356 final File launchParamFolder = getLaunchParamFolder(mUserId);
357 if (!launchParamFolder.isDirectory() && !launchParamFolder.mkdirs()) {
358 Slog.w(TAG, "Failed to create folder for " + mUserId);
359 return;
360 }
361
362 final File launchParamFile = getParamFile(launchParamFolder, mComponentName);
363 final AtomicFile atomicFile = new AtomicFile(launchParamFile);
364
365 FileOutputStream stream = null;
366 try {
367 stream = atomicFile.startWrite();
368 stream.write(writer.toString().getBytes());
369 } catch (Exception e) {
370 Slog.e(TAG, "Failed to write param file for " + mComponentName, e);
371 if (stream != null) {
372 atomicFile.failWrite(stream);
373 }
374 return;
375 }
376 atomicFile.finishWrite(stream);
377 }
378
379 @Override
380 public boolean matches(LaunchParamsWriteQueueItem item) {
381 return mUserId == item.mUserId && mComponentName.equals(item.mComponentName);
382 }
383
384 @Override
385 public void updateFrom(LaunchParamsWriteQueueItem item) {
386 mLaunchParams = item.mLaunchParams;
387 }
388 }
389
390 private class CleanUpComponentQueueItem implements PersisterQueue.WriteQueueItem {
391 private final List<File> mComponentFiles;
392
393 private CleanUpComponentQueueItem(List<File> componentFiles) {
394 mComponentFiles = componentFiles;
395 }
396
397 @Override
398 public void process() {
399 for (File file : mComponentFiles) {
400 if (!file.delete()) {
401 Slog.w(TAG, "Failed to delete " + file.getAbsolutePath());
402 }
403 }
404 }
405 }
406
407 private class PersistableLaunchParams {
408 private static final String ATTR_WINDOWING_MODE = "windowing_mode";
409 private static final String ATTR_DISPLAY_UNIQUE_ID = "display_unique_id";
410 private static final String ATTR_BOUNDS = "bounds";
411
412 /** The bounds within the parent container. */
413 final Rect mBounds = new Rect();
414
415 /** The unique id of the display the {@link TaskRecord} would prefer to be on. */
416 String mDisplayUniqueId;
417
418 /** The windowing mode to be in. */
419 int mWindowingMode;
420
421 void saveToXml(XmlSerializer serializer) throws IOException {
422 serializer.attribute(null, ATTR_DISPLAY_UNIQUE_ID, mDisplayUniqueId);
423 serializer.attribute(null, ATTR_WINDOWING_MODE,
424 Integer.toString(mWindowingMode));
425 serializer.attribute(null, ATTR_BOUNDS, mBounds.flattenToString());
426 }
427
428 void restoreFromXml(XmlPullParser parser) {
429 for (int i = 0; i < parser.getAttributeCount(); ++i) {
430 final String attrValue = parser.getAttributeValue(i);
431 switch (parser.getAttributeName(i)) {
432 case ATTR_DISPLAY_UNIQUE_ID:
433 mDisplayUniqueId = attrValue;
434 break;
435 case ATTR_WINDOWING_MODE:
436 mWindowingMode = Integer.parseInt(attrValue);
437 break;
438 case ATTR_BOUNDS: {
439 final Rect bounds = Rect.unflattenFromString(attrValue);
440 if (bounds != null) {
441 mBounds.set(bounds);
442 }
443 break;
444 }
445 }
446 }
447 }
448
449 @Override
450 public String toString() {
451 final StringBuilder builder = new StringBuilder("PersistableLaunchParams{");
452 builder.append("windowingMode=" + mWindowingMode);
453 builder.append(" displayUniqueId=" + mDisplayUniqueId);
454 builder.append(" bounds=" + mBounds);
455 builder.append(" }");
456 return builder.toString();
457 }
458 }
459}