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.content.ComponentName;
24 import android.graphics.Bitmap;
25 import android.graphics.Bitmap.Config;
26 import android.graphics.BitmapFactory;
27 import android.graphics.BitmapFactory.Options;
28 import android.graphics.Point;
29 import android.graphics.Rect;
30 import android.hardware.HardwareBuffer;
31 import android.util.Slog;
32 import android.window.TaskSnapshot;
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 HardwareBuffer buffer = hwBitmap.getHardwareBuffer();
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                     new Rect(proto.letterboxInsetLeft, proto.letterboxInsetTop,
200                             proto.letterboxInsetRight, proto.letterboxInsetBottom),
201                     loadLowResolutionBitmap, proto.isRealSnapshot, proto.windowingMode,
202                     proto.appearance, proto.isTranslucent, false /* hasImeSurface */);
203         } catch (IOException e) {
204             Slog.w(TAG, "Unable to load task snapshot data for taskId=" + taskId);
205             return null;
206         }
207     }
208 }
209