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