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