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