1 /*
2  * Copyright (C) 2017 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.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
20 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
21 
22 import android.app.ActivityManager;
23 import android.app.ActivityManager.TaskSnapshot;
24 import android.content.ComponentName;
25 import android.graphics.Bitmap;
26 import android.graphics.Bitmap.Config;
27 import android.graphics.BitmapFactory;
28 import android.graphics.BitmapFactory.Options;
29 import android.graphics.GraphicBuffer;
30 import android.graphics.Point;
31 import android.graphics.Rect;
32 import android.util.Slog;
33 
34 import com.android.server.wm.nano.WindowManagerProtos.TaskSnapshotProto;
35 
36 import java.io.File;
37 import java.io.IOException;
38 import java.nio.file.Files;
39 
40 /**
41  * Loads a persisted {@link TaskSnapshot} from disk.
42  * <p>
43  * Do not hold the window manager lock when accessing this class.
44  * <p>
45  * Test class: {@link TaskSnapshotPersisterLoaderTest}
46  */
47 class TaskSnapshotLoader {
48 
49     private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotLoader" : TAG_WM;
50 
51     private final TaskSnapshotPersister mPersister;
52 
TaskSnapshotLoader(TaskSnapshotPersister persister)53     TaskSnapshotLoader(TaskSnapshotPersister persister) {
54         mPersister = persister;
55     }
56 
57     static class PreRLegacySnapshotConfig {
58         /**
59          * If isPreRLegacy is {@code true}, specifies the scale the snapshot was taken at
60          */
61         final float mScale;
62 
63         /**
64          * If {@code true}, always load *_reduced.jpg file, no matter what was requested
65          */
66         final boolean mForceLoadReducedJpeg;
67 
PreRLegacySnapshotConfig(float scale, boolean forceLoadReducedJpeg)68         PreRLegacySnapshotConfig(float scale, boolean forceLoadReducedJpeg) {
69             mScale = scale;
70             mForceLoadReducedJpeg = forceLoadReducedJpeg;
71         }
72     }
73 
74     /**
75      * When device is upgraded, we might be loading a legacy snapshot. In those cases,
76      * restore the scale based on how it was configured historically. See history of
77      * TaskSnapshotPersister for more information.
78      *
79      *   | low_ram=false                      | low_ram=true
80      *   +------------------------------------------------------------------------------+
81      * O | *.jpg = 100%, *_reduced.jpg = 50%                                            |
82      *   |                                    +-----------------------------------------|
83      * P |                                    | *.jpg = NONE, *_reduced.jpg = 60%       |
84      *   +------------------------------------+-----------------------------------------+
85      * Q | *.jpg = proto.scale,               | *.jpg = NONE,                           |
86      *   | *_reduced.jpg = 50% * proto.scale  | *_reduced.jpg = proto.scale             |
87      *   +------------------------------------+-----------------------------------------+
88      *
89      * @return null if Android R, otherwise a PreRLegacySnapshotConfig object
90      */
getLegacySnapshotConfig(int taskWidth, float legacyScale, boolean highResFileExists, boolean loadLowResolutionBitmap)91     PreRLegacySnapshotConfig getLegacySnapshotConfig(int taskWidth, float legacyScale,
92             boolean highResFileExists, boolean loadLowResolutionBitmap) {
93         float preRLegacyScale = 0;
94         boolean forceLoadReducedJpeg = false;
95         boolean isPreRLegacySnapshot = (taskWidth == 0);
96         if (!isPreRLegacySnapshot) {
97             return null;
98         }
99         final boolean isPreQLegacyProto = isPreRLegacySnapshot
100                 && (Float.compare(legacyScale, 0f) == 0);
101 
102         if (isPreQLegacyProto) {
103             // Android O or Android P
104             if (ActivityManager.isLowRamDeviceStatic() && !highResFileExists) {
105                 // Android P w/ low_ram=true
106                 preRLegacyScale = 0.6f;
107                 // Force bitmapFile to always be *_reduced.jpg
108                 forceLoadReducedJpeg = true;
109             } else {
110                 // Android O, OR Android P w/ low_ram=false
111                 preRLegacyScale = loadLowResolutionBitmap ? 0.5f : 1.0f;
112             }
113         } else if (isPreRLegacySnapshot) {
114             // If not pre-Q but is pre-R, then it must be Android Q
115             if (ActivityManager.isLowRamDeviceStatic()) {
116                 preRLegacyScale = legacyScale;
117                 // Force bitmapFile to always be *_reduced.jpg
118                 forceLoadReducedJpeg = true;
119             } else {
120                 preRLegacyScale =
121                         loadLowResolutionBitmap ? 0.5f * legacyScale : legacyScale;
122             }
123         }
124         return new PreRLegacySnapshotConfig(preRLegacyScale, forceLoadReducedJpeg);
125     }
126 
127     /**
128      * Loads a task from the disk.
129      * <p>
130      * Do not hold the window manager lock when calling this method, as we directly read data from
131      * disk here, which might be slow.
132      *
133      * @param taskId                  The id of the task to load.
134      * @param userId                  The id of the user the task belonged to.
135      * @param loadLowResolutionBitmap Whether to load a low resolution resolution version of the
136      *                                snapshot.
137      * @return The loaded {@link TaskSnapshot} or {@code null} if it couldn't be loaded.
138      */
loadTask(int taskId, int userId, boolean loadLowResolutionBitmap)139     TaskSnapshot loadTask(int taskId, int userId, boolean loadLowResolutionBitmap) {
140         final File protoFile = mPersister.getProtoFile(taskId, userId);
141         if (!protoFile.exists()) {
142             return null;
143         }
144         try {
145             final byte[] bytes = Files.readAllBytes(protoFile.toPath());
146             final TaskSnapshotProto proto = TaskSnapshotProto.parseFrom(bytes);
147             final File highResBitmap = mPersister.getHighResolutionBitmapFile(taskId, userId);
148 
149             PreRLegacySnapshotConfig legacyConfig = getLegacySnapshotConfig(proto.taskWidth,
150                     proto.legacyScale, highResBitmap.exists(), loadLowResolutionBitmap);
151 
152             boolean forceLoadReducedJpeg =
153                     legacyConfig != null && legacyConfig.mForceLoadReducedJpeg;
154             File bitmapFile = (loadLowResolutionBitmap || forceLoadReducedJpeg)
155                     ? mPersister.getLowResolutionBitmapFile(taskId, userId) : highResBitmap;
156 
157             if (!bitmapFile.exists()) {
158                 return null;
159             }
160 
161             final Options options = new Options();
162             options.inPreferredConfig = mPersister.use16BitFormat() && !proto.isTranslucent
163                     ? Config.RGB_565
164                     : Config.ARGB_8888;
165             final Bitmap bitmap = BitmapFactory.decodeFile(bitmapFile.getPath(), options);
166             if (bitmap == null) {
167                 Slog.w(TAG, "Failed to load bitmap: " + bitmapFile.getPath());
168                 return null;
169             }
170 
171             final Bitmap hwBitmap = bitmap.copy(Config.HARDWARE, false);
172             bitmap.recycle();
173             if (hwBitmap == null) {
174                 Slog.w(TAG, "Failed to create hardware bitmap: " + bitmapFile.getPath());
175                 return null;
176             }
177             final GraphicBuffer buffer = hwBitmap.createGraphicBufferHandle();
178             if (buffer == null) {
179                 Slog.w(TAG, "Failed to retrieve gralloc buffer for bitmap: "
180                         + bitmapFile.getPath());
181                 return null;
182             }
183 
184             final ComponentName topActivityComponent = ComponentName.unflattenFromString(
185                     proto.topActivityComponent);
186 
187             Point taskSize;
188             if (legacyConfig != null) {
189                 int taskWidth = (int) ((float) hwBitmap.getWidth() / legacyConfig.mScale);
190                 int taskHeight = (int) ((float) hwBitmap.getHeight() / legacyConfig.mScale);
191                 taskSize = new Point(taskWidth, taskHeight);
192             } else {
193                 taskSize = new Point(proto.taskWidth, proto.taskHeight);
194             }
195 
196             return new TaskSnapshot(proto.id, topActivityComponent, buffer,
197                     hwBitmap.getColorSpace(), proto.orientation, proto.rotation, taskSize,
198                     new Rect(proto.insetLeft, proto.insetTop, proto.insetRight, proto.insetBottom),
199                     loadLowResolutionBitmap, proto.isRealSnapshot, proto.windowingMode,
200                     proto.systemUiVisibility, proto.isTranslucent);
201         } catch (IOException e) {
202             Slog.w(TAG, "Unable to load task snapshot data for taskId=" + taskId);
203             return null;
204         }
205     }
206 }
207