1 /* 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 17 package com.android.server.wm; 18 19 import android.content.ComponentName; 20 import android.content.pm.PackageList; 21 import android.content.pm.PackageManagerInternal; 22 import android.graphics.Rect; 23 import android.os.Environment; 24 import android.util.ArrayMap; 25 import android.util.ArraySet; 26 import android.util.AtomicFile; 27 import android.util.Slog; 28 import android.util.SparseArray; 29 import android.util.Xml; 30 import android.view.DisplayInfo; 31 32 import com.android.internal.annotations.VisibleForTesting; 33 import com.android.internal.util.FastXmlSerializer; 34 import com.android.server.LocalServices; 35 import com.android.server.wm.LaunchParamsController.LaunchParams; 36 37 import libcore.io.IoUtils; 38 39 import org.xmlpull.v1.XmlPullParser; 40 import org.xmlpull.v1.XmlSerializer; 41 42 import java.io.BufferedReader; 43 import java.io.File; 44 import java.io.FileOutputStream; 45 import java.io.FileReader; 46 import java.io.IOException; 47 import java.io.StringWriter; 48 import java.util.ArrayList; 49 import java.util.List; 50 import java.util.Map; 51 import java.util.Objects; 52 import java.util.Set; 53 import 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 */ 63 class 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 LaunchParamsPersister(PersisterQueue persisterQueue, ActivityStackSupervisor supervisor)93 LaunchParamsPersister(PersisterQueue persisterQueue, ActivityStackSupervisor supervisor) { 94 this(persisterQueue, supervisor, Environment::getDataSystemCeDirectory); 95 } 96 97 @VisibleForTesting LaunchParamsPersister(PersisterQueue persisterQueue, ActivityStackSupervisor supervisor, IntFunction<File> userFolderGetter)98 LaunchParamsPersister(PersisterQueue persisterQueue, ActivityStackSupervisor supervisor, 99 IntFunction<File> userFolderGetter) { 100 mPersisterQueue = persisterQueue; 101 mSupervisor = supervisor; 102 mUserFolderGetter = userFolderGetter; 103 } 104 onSystemReady()105 void onSystemReady() { 106 PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class); 107 mPackageList = pmi.getPackageList(new PackageListObserver()); 108 } 109 onUnlockUser(int userId)110 void onUnlockUser(int userId) { 111 loadLaunchParams(userId); 112 } 113 onCleanupUser(int userId)114 void onCleanupUser(int userId) { 115 mMap.remove(userId); 116 } 117 loadLaunchParams(int userId)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 saveTask(TaskRecord task)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 saveTaskToLaunchParam(TaskRecord task, PersistableLaunchParams params)223 private boolean saveTaskToLaunchParam(TaskRecord task, PersistableLaunchParams params) { 224 final ActivityStack stack = task.getStack(); 225 final int displayId = stack.mDisplayId; 226 final ActivityDisplay display = 227 mSupervisor.mRootActivityContainer.getActivityDisplay(displayId); 228 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 getLaunchParams(TaskRecord task, ActivityRecord activity, LaunchParams outParams)248 void getLaunchParams(TaskRecord task, ActivityRecord activity, LaunchParams outParams) { 249 final ComponentName name = task != null ? task.realActivity : activity.mActivityComponent; 250 final int userId = task != null ? task.userId : activity.mUserId; 251 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 263 final ActivityDisplay display = mSupervisor.mRootActivityContainer.getActivityDisplay( 264 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 removeRecordForPackage(String packageName)272 void removeRecordForPackage(String packageName) { 273 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 getParamFile(File launchParamFolder, ComponentName name)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 getLaunchParamFolder(int userId)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 onPackageAdded(String packageName, int uid)309 public void onPackageAdded(String packageName, int uid) { } 310 311 @Override onPackageRemoved(String packageName, int uid)312 public void onPackageRemoved(String packageName, int uid) { 313 removeRecordForPackage(packageName); 314 } 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 LaunchParamsWriteQueueItem(int userId, ComponentName componentName, PersistableLaunchParams launchParams)324 private LaunchParamsWriteQueueItem(int userId, ComponentName componentName, 325 PersistableLaunchParams launchParams) { 326 mUserId = userId; 327 mComponentName = componentName; 328 mLaunchParams = launchParams; 329 } 330 saveParamsToXml()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 process()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 matches(LaunchParamsWriteQueueItem item)380 public boolean matches(LaunchParamsWriteQueueItem item) { 381 return mUserId == item.mUserId && mComponentName.equals(item.mComponentName); 382 } 383 384 @Override updateFrom(LaunchParamsWriteQueueItem item)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 CleanUpComponentQueueItem(List<File> componentFiles)393 private CleanUpComponentQueueItem(List<File> componentFiles) { 394 mComponentFiles = componentFiles; 395 } 396 397 @Override process()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 saveToXml(XmlSerializer serializer)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 restoreFromXml(XmlPullParser parser)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 toString()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 } 460