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