• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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