1 /* 2 * Copyright (C) 2022 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 static com.android.server.wm.AbsAppSnapshotController.TAG; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.util.Slog; 24 import android.util.SparseArray; 25 import android.util.SparseBooleanArray; 26 import android.window.TaskSnapshot; 27 28 import com.android.internal.annotations.VisibleForTesting; 29 import com.android.window.flags.Flags; 30 31 import java.io.File; 32 import java.util.UUID; 33 34 class BaseAppSnapshotPersister { 35 static final String LOW_RES_FILE_POSTFIX = "_reduced"; 36 static final String PROTO_EXTENSION = ".proto"; 37 static final String BITMAP_EXTENSION = ".jpg"; 38 39 // Shared with SnapshotPersistQueue 40 protected final Object mLock; 41 protected final SnapshotPersistQueue mSnapshotPersistQueue; 42 @VisibleForTesting 43 protected final PersistInfoProvider mPersistInfoProvider; 44 BaseAppSnapshotPersister(SnapshotPersistQueue persistQueue, PersistInfoProvider persistInfoProvider)45 BaseAppSnapshotPersister(SnapshotPersistQueue persistQueue, 46 PersistInfoProvider persistInfoProvider) { 47 mSnapshotPersistQueue = persistQueue; 48 mPersistInfoProvider = persistInfoProvider; 49 mLock = persistQueue.getLock(); 50 } 51 52 /** 53 * Persists a snapshot of a task to disk. 54 * 55 * @param id The id of the object that needs to be persisted. 56 * @param userId The id of the user this tasks belongs to. 57 * @param snapshot The snapshot to persist. 58 */ persistSnapshot(int id, int userId, TaskSnapshot snapshot)59 void persistSnapshot(int id, int userId, TaskSnapshot snapshot) { 60 synchronized (mLock) { 61 mSnapshotPersistQueue.sendToQueueLocked(mSnapshotPersistQueue 62 .createStoreWriteQueueItem(id, userId, snapshot, mPersistInfoProvider)); 63 } 64 } 65 66 /** 67 * Called to remove the persisted file 68 * 69 * @param id The id of task that has been removed. 70 * @param userId The id of the user the task belonged to. 71 */ removeSnapshot(int id, int userId)72 void removeSnapshot(int id, int userId) { 73 synchronized (mLock) { 74 mSnapshotPersistQueue.sendToQueueLocked(mSnapshotPersistQueue 75 .createDeleteWriteQueueItem(id, userId, mPersistInfoProvider)); 76 } 77 } 78 79 interface DirectoryResolver { getSystemDirectoryForUser(int userId)80 File getSystemDirectoryForUser(int userId); 81 } 82 83 /** 84 * Persist information provider, the snapshot persister and loader can know where the file is, 85 * and the scale of a snapshot, etc. 86 */ 87 static class PersistInfoProvider { 88 protected final DirectoryResolver mDirectoryResolver; 89 private final String mDirName; 90 private final boolean mEnableLowResSnapshots; 91 private final float mLowResScaleFactor; 92 private final boolean mUse16BitFormat; 93 private final SparseBooleanArray mInitializedUsers = new SparseBooleanArray(); 94 private final SparseArray<File> mScrambleDirectories = new SparseArray<>(); 95 PersistInfoProvider(DirectoryResolver directoryResolver, String dirName, boolean enableLowResSnapshots, float lowResScaleFactor, boolean use16BitFormat)96 PersistInfoProvider(DirectoryResolver directoryResolver, String dirName, 97 boolean enableLowResSnapshots, float lowResScaleFactor, boolean use16BitFormat) { 98 mDirectoryResolver = directoryResolver; 99 mDirName = dirName; 100 mEnableLowResSnapshots = enableLowResSnapshots; 101 mLowResScaleFactor = lowResScaleFactor; 102 mUse16BitFormat = use16BitFormat; 103 } 104 105 @NonNull getDirectory(int userId)106 File getDirectory(int userId) { 107 if (Flags.scrambleSnapshotFileName()) { 108 final File directory = getOrInitScrambleDirectory(userId); 109 if (directory != null) { 110 return directory; 111 } 112 } 113 return getBaseDirectory(userId); 114 } 115 116 @NonNull getBaseDirectory(int userId)117 private File getBaseDirectory(int userId) { 118 return new File(mDirectoryResolver.getSystemDirectoryForUser(userId), mDirName); 119 } 120 121 @Nullable getOrInitScrambleDirectory(int userId)122 private File getOrInitScrambleDirectory(int userId) { 123 synchronized (mScrambleDirectories) { 124 if (mInitializedUsers.get(userId)) { 125 return mScrambleDirectories.get(userId); 126 } 127 mInitializedUsers.put(userId, true); 128 final File scrambledDirectory = getScrambleDirectory(userId); 129 final File baseDir = getBaseDirectory(userId); 130 String newName = null; 131 // If directory exists, rename 132 if (scrambledDirectory.exists()) { 133 newName = UUID.randomUUID().toString(); 134 final File scrambleTo = new File(baseDir, newName); 135 if (!scrambledDirectory.renameTo(scrambleTo)) { 136 Slog.w(TAG, "SnapshotPersister rename scramble folder fail."); 137 return null; 138 } 139 } else { 140 // If directory not exists, mkDir. 141 if (!baseDir.exists() && !baseDir.mkdir()) { 142 Slog.w(TAG, "SnapshotPersister make base folder fail."); 143 return null; 144 } 145 if (!scrambledDirectory.mkdir()) { 146 Slog.e(TAG, "SnapshotPersister make scramble folder fail"); 147 return null; 148 } 149 // Move any existing files to this folder. 150 final String[] files = baseDir.list(); 151 if (files != null) { 152 for (String file : files) { 153 final File original = new File(baseDir, file); 154 if (original.isDirectory()) { 155 newName = file; 156 } else { 157 File to = new File(scrambledDirectory, file); 158 original.renameTo(to); 159 } 160 } 161 } 162 } 163 final File newFolder = new File(baseDir, newName); 164 mScrambleDirectories.put(userId, newFolder); 165 return newFolder; 166 } 167 } 168 169 @NonNull getScrambleDirectory(int userId)170 private File getScrambleDirectory(int userId) { 171 final File dir = getBaseDirectory(userId); 172 final String[] directories = dir.list( 173 (current, name) -> new File(current, name).isDirectory()); 174 if (directories != null && directories.length > 0) { 175 return new File(dir, directories[0]); 176 } else { 177 return new File(dir, UUID.randomUUID().toString()); 178 } 179 } 180 181 /** 182 * Return if task snapshots are stored in 16 bit pixel format. 183 * 184 * @return true if task snapshots are stored in 16 bit pixel format. 185 */ use16BitFormat()186 boolean use16BitFormat() { 187 return mUse16BitFormat; 188 } 189 createDirectory(int userId)190 boolean createDirectory(int userId) { 191 final File dir = getDirectory(userId); 192 return dir.exists() || dir.mkdir(); 193 } 194 getProtoFile(int index, int userId)195 File getProtoFile(int index, int userId) { 196 return new File(getDirectory(userId), index + PROTO_EXTENSION); 197 } 198 getLowResolutionBitmapFile(int index, int userId)199 File getLowResolutionBitmapFile(int index, int userId) { 200 return new File(getDirectory(userId), index + LOW_RES_FILE_POSTFIX + BITMAP_EXTENSION); 201 } 202 getHighResolutionBitmapFile(int index, int userId)203 File getHighResolutionBitmapFile(int index, int userId) { 204 return new File(getDirectory(userId), index + BITMAP_EXTENSION); 205 } 206 enableLowResSnapshots()207 boolean enableLowResSnapshots() { 208 return mEnableLowResSnapshots; 209 } 210 lowResScaleFactor()211 float lowResScaleFactor() { 212 return mLowResScaleFactor; 213 } 214 } 215 } 216