Disable reduced scale if reduced scale config is 0

Change reduced scale implementation to toggle on/off based on
config_lowResTaskSnapshotScale=0 instead of ro.config.low_ram=true

Also, only use _reduced.jpg if reduced scale is enabled.  Previously,
for task snapshots, [0-9]+_reduced.jpg would be used if reduced scale
was disabled, and [0-9]+.jpg would never be used. This patch swaps that
behavior to make the underlying system more intuitive.  Now, if reduced
snapshots are disabled, store the task snapshot in [0-9]+.jpg and never
use [0-9]+_reduced.jpg.

Also, store low-res snapshots at config_lowResTaskSnapshotScale.
Prevously, low-res snapshots were stored at lowResScale * highResScale

Test: TaskSnapshotCacheTest
Test: TaskSnapshotControllerTest
Test: TaskSnapshotPersisterLoaderTest
Test: TaskSnapshotSurfaceTest
Bug: 148099851
Bug: 142063079
Change-Id: I5a0d58766347d875eaec138820323063aa1c2988
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 2092c01..f83b052 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -18,14 +18,12 @@
 
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 
-import static com.android.server.wm.TaskSnapshotPersister.DISABLE_HIGH_RES_BITMAPS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ActivityManager;
 import android.app.ActivityManager.TaskSnapshot;
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
@@ -222,7 +220,7 @@
     @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
             boolean isLowResolution) {
         return mCache.getSnapshot(taskId, userId, restoreFromDisk, isLowResolution
-                || DISABLE_HIGH_RES_BITMAPS);
+                && mPersister.enableLowResSnapshots());
     }
 
     /**
@@ -300,12 +298,9 @@
             return false;
         }
 
-        final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
-
         builder.setIsRealSnapshot(true);
         builder.setId(System.currentTimeMillis());
         builder.setContentInsets(getInsets(mainWindow));
-        builder.setIsLowResolution(isLowRamDevice);
 
         final boolean isWindowTranslucent = mainWindow.getAttrs().format != PixelFormat.OPAQUE;
         final boolean isShowWallpaper = (mainWindow.getAttrs().flags & FLAG_SHOW_WALLPAPER) != 0;
@@ -334,10 +329,8 @@
     SurfaceControl.ScreenshotGraphicBuffer createTaskSnapshot(@NonNull Task task,
             TaskSnapshot.Builder builder) {
         Point taskSize = new Point();
-        float scale = builder.isLowResolution()
-                ? mPersister.getLowResScale() : mHighResTaskSnapshotScale;
-        final SurfaceControl.ScreenshotGraphicBuffer taskSnapshot = createTaskSnapshot(task, scale,
-                builder.getPixelFormat(), taskSize);
+        final SurfaceControl.ScreenshotGraphicBuffer taskSnapshot = createTaskSnapshot(task,
+                mHighResTaskSnapshotScale, builder.getPixelFormat(), taskSize);
         builder.setTaskSize(taskSize);
         return taskSnapshot;
     }
@@ -493,7 +486,7 @@
                 topChild.mActivityComponent, hwBitmap.createGraphicBufferHandle(),
                 hwBitmap.getColorSpace(), mainWindow.getConfiguration().orientation,
                 mainWindow.getWindowConfiguration().getRotation(), new Point(taskWidth, taskHeight),
-                getInsets(mainWindow), ActivityManager.isLowRamDeviceStatic() /* isLowResolution */,
+                getInsets(mainWindow), false /* isLowResolution */,
                 false /* isRealSnapshot */, task.getWindowingMode(),
                 getSystemUiVisibility(task), false);
     }
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
index ed8dd90..c20ce5f 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
@@ -48,37 +48,116 @@
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotLoader" : TAG_WM;
 
-    private static final float LEGACY_REDUCED_SCALE =
-            ActivityManager.isLowRamDeviceStatic() ? 0.6f : 0.5f;
-
     private final TaskSnapshotPersister mPersister;
 
     TaskSnapshotLoader(TaskSnapshotPersister persister) {
         mPersister = persister;
     }
 
+    static class PreRLegacySnapshotConfig {
+        /**
+         * If isPreRLegacy is {@code true}, specifies the scale the snapshot was taken at
+         */
+        final float mScale;
+
+        /**
+         * If {@code true}, always load *_reduced.jpg file, no matter what was requested
+         */
+        final boolean mForceLoadReducedJpeg;
+
+        PreRLegacySnapshotConfig(float scale, boolean forceLoadReducedJpeg) {
+            mScale = scale;
+            mForceLoadReducedJpeg = forceLoadReducedJpeg;
+        }
+    }
+
+    /**
+     * When device is upgraded, we might be loading a legacy snapshot. In those cases,
+     * restore the scale based on how it was configured historically. See history of
+     * TaskSnapshotPersister for more information.
+     *
+     *   | low_ram=false                      | low_ram=true
+     *   +------------------------------------------------------------------------------+
+     * O | *.jpg = 100%, *_reduced.jpg = 50%                                            |
+     *   |                                    +-----------------------------------------|
+     * P |                                    | *.jpg = NONE, *_reduced.jpg = 60%       |
+     *   +------------------------------------+-----------------------------------------+
+     * Q | *.jpg = proto.scale,               | *.jpg = NONE,                           |
+     *   | *_reduced.jpg = 50% * proto.scale  | *_reduced.jpg = proto.scale             |
+     *   +------------------------------------+-----------------------------------------+
+     *
+     * @return null if Android R, otherwise a PreRLegacySnapshotConfig object
+     */
+    PreRLegacySnapshotConfig getLegacySnapshotConfig(int taskWidth, float legacyScale,
+            boolean highResFileExists, boolean loadLowResolutionBitmap) {
+        float preRLegacyScale = 0;
+        boolean forceLoadReducedJpeg = false;
+        boolean isPreRLegacySnapshot = (taskWidth == 0);
+        if (!isPreRLegacySnapshot) {
+            return null;
+        }
+        final boolean isPreQLegacyProto = isPreRLegacySnapshot
+                && (Float.compare(legacyScale, 0f) == 0);
+
+        if (isPreQLegacyProto) {
+            // Android O or Android P
+            if (ActivityManager.isLowRamDeviceStatic() && !highResFileExists) {
+                // Android P w/ low_ram=true
+                preRLegacyScale = 0.6f;
+                // Force bitmapFile to always be *_reduced.jpg
+                forceLoadReducedJpeg = true;
+            } else {
+                // Android O, OR Android P w/ low_ram=false
+                preRLegacyScale = loadLowResolutionBitmap ? 0.5f : 1.0f;
+            }
+        } else if (isPreRLegacySnapshot) {
+            // If not pre-Q but is pre-R, then it must be Android Q
+            if (ActivityManager.isLowRamDeviceStatic()) {
+                preRLegacyScale = legacyScale;
+                // Force bitmapFile to always be *_reduced.jpg
+                forceLoadReducedJpeg = true;
+            } else {
+                preRLegacyScale =
+                        loadLowResolutionBitmap ? 0.5f * legacyScale : legacyScale;
+            }
+        }
+        return new PreRLegacySnapshotConfig(preRLegacyScale, forceLoadReducedJpeg);
+    }
+
     /**
      * Loads a task from the disk.
      * <p>
      * Do not hold the window manager lock when calling this method, as we directly read data from
      * disk here, which might be slow.
      *
-     * @param taskId The id of the task to load.
-     * @param userId The id of the user the task belonged to.
-     * @param isLowResolution Whether to load a reduced resolution version of the snapshot.
+     * @param taskId                  The id of the task to load.
+     * @param userId                  The id of the user the task belonged to.
+     * @param loadLowResolutionBitmap Whether to load a low resolution resolution version of the
+     *                                snapshot.
      * @return The loaded {@link TaskSnapshot} or {@code null} if it couldn't be loaded.
      */
-    TaskSnapshot loadTask(int taskId, int userId, boolean isLowResolution) {
+    TaskSnapshot loadTask(int taskId, int userId, boolean loadLowResolutionBitmap) {
         final File protoFile = mPersister.getProtoFile(taskId, userId);
-        final File bitmapFile = isLowResolution
-                ? mPersister.getLowResolutionBitmapFile(taskId, userId)
-                : mPersister.getHighResolutionBitmapFile(taskId, userId);
-        if (bitmapFile == null || !protoFile.exists() || !bitmapFile.exists()) {
+        if (!protoFile.exists()) {
             return null;
         }
         try {
             final byte[] bytes = Files.readAllBytes(protoFile.toPath());
             final TaskSnapshotProto proto = TaskSnapshotProto.parseFrom(bytes);
+            final File highResBitmap = mPersister.getHighResolutionBitmapFile(taskId, userId);
+
+            PreRLegacySnapshotConfig legacyConfig = getLegacySnapshotConfig(proto.taskWidth,
+                    proto.legacyScale, highResBitmap.exists(), loadLowResolutionBitmap);
+
+            boolean forceLoadReducedJpeg =
+                    legacyConfig != null && legacyConfig.mForceLoadReducedJpeg;
+            File bitmapFile = (loadLowResolutionBitmap || forceLoadReducedJpeg)
+                    ? mPersister.getLowResolutionBitmapFile(taskId, userId) : highResBitmap;
+
+            if (!bitmapFile.exists()) {
+                return null;
+            }
+
             final Options options = new Options();
             options.inPreferredConfig = mPersister.use16BitFormat() && !proto.isTranslucent
                     ? Config.RGB_565
@@ -105,24 +184,19 @@
             final ComponentName topActivityComponent = ComponentName.unflattenFromString(
                     proto.topActivityComponent);
 
-            // For legacy snapshots, restore the scale based on the reduced resolution state
             Point taskSize;
-            if (proto.taskWidth == 0) {
-                // For legacy snapshots, restore the scale based on the reduced resolution state
-                final float preQLegacyScale = isLowResolution ? LEGACY_REDUCED_SCALE : 1f;
-                final float scale = Float.compare(proto.legacyScale, 0f) != 0
-                        ? proto.legacyScale : preQLegacyScale;
-                int taskWidth = (int) ((float) hwBitmap.getWidth() / scale);
-                int taskHeight = (int) ((float) hwBitmap.getHeight() / scale);
+            if (legacyConfig != null) {
+                int taskWidth = (int) ((float) hwBitmap.getWidth() / legacyConfig.mScale);
+                int taskHeight = (int) ((float) hwBitmap.getHeight() / legacyConfig.mScale);
                 taskSize = new Point(taskWidth, taskHeight);
             } else {
                 taskSize = new Point(proto.taskWidth, proto.taskHeight);
             }
 
-            return new TaskSnapshot(proto.id, topActivityComponent, buffer, hwBitmap.getColorSpace(),
-                    proto.orientation, proto.rotation, taskSize,
+            return new TaskSnapshot(proto.id, topActivityComponent, buffer,
+                    hwBitmap.getColorSpace(), proto.orientation, proto.rotation, taskSize,
                     new Rect(proto.insetLeft, proto.insetTop, proto.insetRight, proto.insetBottom),
-                    isLowResolution, proto.isRealSnapshot, proto.windowingMode,
+                    loadLowResolutionBitmap, proto.isRealSnapshot, proto.windowingMode,
                     proto.systemUiVisibility, proto.isTranslucent);
         } catch (IOException e) {
             Slog.w(TAG, "Unable to load task snapshot data for taskId=" + taskId);
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index a5693b1..164d3e0 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -21,8 +21,8 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
+import android.annotation.NonNull;
 import android.annotation.TestApi;
-import android.app.ActivityManager;
 import android.app.ActivityManager.TaskSnapshot;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
@@ -52,8 +52,6 @@
     private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotPersister" : TAG_WM;
     private static final String SNAPSHOTS_DIRNAME = "snapshots";
     private static final String LOW_RES_FILE_POSTFIX = "_reduced";
-    private static final float LOW_RAM_REDUCED_SCALE = .8f;
-    static final boolean DISABLE_HIGH_RES_BITMAPS = ActivityManager.isLowRamDeviceStatic();
     private static final long DELAY_MS = 100;
     private static final int QUALITY = 95;
     private static final String PROTO_EXTENSION = ".proto";
@@ -71,7 +69,8 @@
     private boolean mStarted;
     private final Object mLock = new Object();
     private final DirectoryResolver mDirectoryResolver;
-    private final float mLowResScale;
+    private final float mLowResScaleFactor;
+    private boolean mEnableLowResSnapshots;
     private final boolean mUse16BitFormat;
 
     /**
@@ -83,13 +82,29 @@
 
     TaskSnapshotPersister(WindowManagerService service, DirectoryResolver resolver) {
         mDirectoryResolver = resolver;
+        final float highResTaskSnapshotScale = service.mContext.getResources().getFloat(
+                com.android.internal.R.dimen.config_highResTaskSnapshotScale);
+        final float lowResTaskSnapshotScale = service.mContext.getResources().getFloat(
+                com.android.internal.R.dimen.config_lowResTaskSnapshotScale);
 
-        if (ActivityManager.isLowRamDeviceStatic()) {
-            mLowResScale = LOW_RAM_REDUCED_SCALE;
-        } else {
-            mLowResScale = service.mContext.getResources().getFloat(
-                    com.android.internal.R.dimen.config_lowResTaskSnapshotScale);
+        if (lowResTaskSnapshotScale < 0 || 1 <= lowResTaskSnapshotScale) {
+            throw new RuntimeException("Low-res scale must be between 0 and 1");
         }
+        if (highResTaskSnapshotScale <= 0 || 1 < highResTaskSnapshotScale) {
+            throw new RuntimeException("High-res scale must be between 0 and 1");
+        }
+        if (highResTaskSnapshotScale <= lowResTaskSnapshotScale) {
+            throw new RuntimeException("High-res scale must be greater than low-res scale");
+        }
+
+        if (lowResTaskSnapshotScale > 0) {
+            mLowResScaleFactor = lowResTaskSnapshotScale / highResTaskSnapshotScale;
+            setEnableLowResSnapshots(true);
+        } else {
+            mLowResScaleFactor = 0;
+            setEnableLowResSnapshots(false);
+        }
+
         mUse16BitFormat = service.mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_use16BitTaskSnapshotPixelFormat);
     }
@@ -155,13 +170,16 @@
         }
     }
 
+    boolean enableLowResSnapshots() {
+        return mEnableLowResSnapshots;
+    }
+
     /**
-     * Gets the scaling the persister uses for low resolution task snapshots.
-     *
-     * @return the lowResBitmap scale of task snapshots when they are set to be low res
+     * Not to be used. Only here for testing.
      */
-    float getLowResScale() {
-        return mLowResScale;
+    @VisibleForTesting
+    void setEnableLowResSnapshots(boolean enabled) {
+        mEnableLowResSnapshots = enabled;
     }
 
     /**
@@ -213,14 +231,10 @@
     }
 
     File getHighResolutionBitmapFile(int taskId, int userId) {
-        // Full sized bitmaps are disabled on low ram devices
-        if (DISABLE_HIGH_RES_BITMAPS) {
-            Slog.wtf(TAG, "This device does not support full sized resolution bitmaps.");
-            return null;
-        }
         return new File(getDirectory(userId), taskId + BITMAP_EXTENSION);
     }
 
+    @NonNull
     File getLowResolutionBitmapFile(int taskId, int userId) {
         return new File(getDirectory(userId), taskId + LOW_RES_FILE_POSTFIX + BITMAP_EXTENSION);
     }
@@ -234,11 +248,11 @@
         final File protoFile = getProtoFile(taskId, userId);
         final File bitmapLowResFile = getLowResolutionBitmapFile(taskId, userId);
         protoFile.delete();
-        bitmapLowResFile.delete();
-
-        // Low ram devices do not have a full sized file to delete
-        if (!DISABLE_HIGH_RES_BITMAPS) {
-            final File bitmapFile = getHighResolutionBitmapFile(taskId, userId);
+        if (bitmapLowResFile.exists()) {
+            bitmapLowResFile.delete();
+        }
+        final File bitmapFile = getHighResolutionBitmapFile(taskId, userId);
+        if (bitmapFile.exists()) {
             bitmapFile.delete();
         }
     }
@@ -380,11 +394,26 @@
             }
 
             final Bitmap swBitmap = bitmap.copy(Config.ARGB_8888, false /* isMutable */);
-            final Bitmap lowResBitmap = mSnapshot.isLowResolution()
-                    ? swBitmap
-                    : Bitmap.createScaledBitmap(swBitmap,
-                            (int) (bitmap.getWidth() * mLowResScale),
-                            (int) (bitmap.getHeight() * mLowResScale), true /* filter */);
+
+            final File file = getHighResolutionBitmapFile(mTaskId, mUserId);
+            try {
+                FileOutputStream fos = new FileOutputStream(file);
+                swBitmap.compress(JPEG, QUALITY, fos);
+                fos.close();
+            } catch (IOException e) {
+                Slog.e(TAG, "Unable to open " + file + " for persisting.", e);
+                return false;
+            }
+
+            if (!enableLowResSnapshots()) {
+                swBitmap.recycle();
+                return true;
+            }
+
+            final Bitmap lowResBitmap = Bitmap.createScaledBitmap(swBitmap,
+                    (int) (bitmap.getWidth() * mLowResScaleFactor),
+                    (int) (bitmap.getHeight() * mLowResScaleFactor), true /* filter */);
+            swBitmap.recycle();
 
             final File lowResFile = getLowResolutionBitmapFile(mTaskId, mUserId);
             try {
@@ -397,22 +426,6 @@
             }
             lowResBitmap.recycle();
 
-            // For snapshots with lowResBitmap resolution, do not create or save full sized bitmaps
-            if (mSnapshot.isLowResolution()) {
-                swBitmap.recycle();
-                return true;
-            }
-
-            final File file = getHighResolutionBitmapFile(mTaskId, mUserId);
-            try {
-                FileOutputStream fos = new FileOutputStream(file);
-                swBitmap.compress(JPEG, QUALITY, fos);
-                fos.close();
-            } catch (IOException e) {
-                Slog.e(TAG, "Unable to open " + file + " for persisting.", e);
-                return false;
-            }
-            swBitmap.recycle();
             return true;
         }
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
index 7a2707b..20d9aff 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
@@ -25,6 +25,7 @@
 import static com.android.server.wm.TaskSnapshotController.SNAPSHOT_MODE_REAL;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
@@ -149,7 +150,6 @@
             builder.setSystemUiVisibility(systemUiVisibility);
             builder.setWindowingMode(windowingMode);
             builder.setColorSpace(sRGB);
-            builder.setIsLowResolution(true);
             builder.setOrientation(orientation);
             builder.setContentInsets(contentInsets);
             builder.setIsTranslucent(true);
@@ -167,8 +167,9 @@
             assertEquals(systemUiVisibility, snapshot.getSystemUiVisibility());
             assertEquals(windowingMode, snapshot.getWindowingMode());
             assertEquals(sRGB, snapshot.getColorSpace());
-            assertTrue(snapshot.isLowResolution());
-            assertTrue(builder.isLowResolution());
+            // Snapshots created with the Builder class are always high-res. The only way to get a
+            // low-res snapshot is to load it from the disk in TaskSnapshotLoader.
+            assertFalse(snapshot.isLowResolution());
             assertEquals(orientation, snapshot.getOrientation());
             assertEquals(contentInsets, snapshot.getContentInsets());
             assertTrue(snapshot.isTranslucent());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
index 1d1391b..40f15b7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
@@ -19,12 +19,16 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
 import android.app.ActivityManager.TaskSnapshot;
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -36,10 +40,12 @@
 
 import androidx.test.filters.MediumTest;
 
+import com.android.server.wm.TaskSnapshotLoader.PreRLegacySnapshotConfig;
 import com.android.server.wm.TaskSnapshotPersister.RemoveObsoleteFilesQueueItem;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.MockitoSession;
 
 import java.io.File;
 import java.util.function.Predicate;
@@ -55,6 +61,8 @@
 @RunWith(WindowTestRunner.class)
 public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBase {
 
+    private static final float DELTA = 0.00001f;
+
     private static final Rect TEST_INSETS = new Rect(10, 20, 30, 40);
 
     @Test
@@ -148,7 +156,150 @@
     }
 
     @Test
-    public void testLowResolutionPersistAndLoadSnapshot() {
+    public void testLegacyPLowRamConfig() throws Exception {
+        MockitoSession mockSession = mockitoSession()
+                .initMocks(this)
+                .mockStatic(ActivityManager.class)
+                .startMocking();
+
+        when(ActivityManager.isLowRamDeviceStatic()).thenReturn(true);
+
+        // taskWidth and legacyScale as would be defined in the proto, and presence of a *.jpg file,
+        // for any P low_ram device
+        final int taskWidth = 0;
+        final float legacyScale = 0f;
+        final boolean hasHighResFile = false;
+
+        PreRLegacySnapshotConfig highResConf = mLoader.getLegacySnapshotConfig(
+                taskWidth, legacyScale, hasHighResFile, false /* loadLowResolutionBitmap */);
+        assertNotNull(highResConf);
+        assertEquals(highResConf.mScale, 0.6f, DELTA);
+        assertTrue(highResConf.mForceLoadReducedJpeg);
+
+        PreRLegacySnapshotConfig lowResConf = mLoader.getLegacySnapshotConfig(
+                taskWidth, legacyScale, hasHighResFile, true /* loadLowResolutionBitmap */);
+        assertNotNull(lowResConf);
+        assertEquals(lowResConf.mScale, 0.6f, DELTA);
+        assertTrue(lowResConf.mForceLoadReducedJpeg);
+
+        mockSession.finishMocking();
+    }
+
+    @Test
+    public void testLegacyPNonLowRamConfig() throws Exception {
+        MockitoSession mockSession = mockitoSession()
+                .initMocks(this)
+                .mockStatic(ActivityManager.class)
+                .startMocking();
+
+        when(ActivityManager.isLowRamDeviceStatic()).thenReturn(false);
+
+        // taskWidth and legacyScale as would be defined in the proto, and presence of a *.jpg file,
+        // for any O device, or a P non-low_ram device
+        final int taskWidth = 0;
+        final float legacyScale = 0f;
+        final boolean hasHighResFile = true;
+
+        PreRLegacySnapshotConfig highResConf = mLoader.getLegacySnapshotConfig(
+                taskWidth, legacyScale, hasHighResFile, false /* loadLowResolutionBitmap */);
+        assertNotNull(highResConf);
+        assertEquals(highResConf.mScale, 1.0f, DELTA);
+        assertFalse(highResConf.mForceLoadReducedJpeg);
+
+        PreRLegacySnapshotConfig lowResConf = mLoader.getLegacySnapshotConfig(
+                taskWidth, legacyScale, hasHighResFile, true /* loadLowResolutionBitmap */);
+        assertNotNull(lowResConf);
+        assertEquals(lowResConf.mScale, 0.5f, DELTA);
+        assertFalse(lowResConf.mForceLoadReducedJpeg);
+
+        mockSession.finishMocking();
+    }
+
+    @Test
+    public void testLegacyQLowRamConfig() throws Exception {
+        MockitoSession mockSession = mockitoSession()
+                .initMocks(this)
+                .mockStatic(ActivityManager.class)
+                .startMocking();
+
+        when(ActivityManager.isLowRamDeviceStatic()).thenReturn(true);
+
+        // taskWidth and legacyScale as would be defined in the proto, and presence of a *.jpg file,
+        // for any Q low_ram device
+        final int taskWidth = 0;
+        final float legacyScale = 0.6f;
+        final boolean hasHighResFile = false;
+
+        PreRLegacySnapshotConfig highResConf = mLoader.getLegacySnapshotConfig(
+                taskWidth, legacyScale, hasHighResFile, false /* loadLowResolutionBitmap */);
+        assertNotNull(highResConf);
+        assertEquals(highResConf.mScale, legacyScale, DELTA);
+        assertEquals(highResConf.mScale, 0.6f, DELTA);
+        assertTrue(highResConf.mForceLoadReducedJpeg);
+
+        PreRLegacySnapshotConfig lowResConf = mLoader.getLegacySnapshotConfig(
+                taskWidth, legacyScale, hasHighResFile, true /* loadLowResolutionBitmap */);
+        assertNotNull(lowResConf);
+        assertEquals(lowResConf.mScale, legacyScale, DELTA);
+        assertEquals(lowResConf.mScale, 0.6f, DELTA);
+        assertTrue(lowResConf.mForceLoadReducedJpeg);
+
+        mockSession.finishMocking();
+    }
+
+    @Test
+    public void testLegacyQNonLowRamConfig() throws Exception {
+        MockitoSession mockSession = mockitoSession()
+                .initMocks(this)
+                .mockStatic(ActivityManager.class)
+                .startMocking();
+
+        when(ActivityManager.isLowRamDeviceStatic()).thenReturn(false);
+
+        // taskWidth and legacyScale as would be defined in the proto, and presence of a *.jpg file,
+        // for any Q non-low_ram device
+        final int taskWidth = 0;
+        final float legacyScale = 0.8f;
+        final boolean hasHighResFile = true;
+
+        PreRLegacySnapshotConfig highResConf = mLoader.getLegacySnapshotConfig(
+                taskWidth, legacyScale, hasHighResFile, false /* loadLowResolutionBitmap */);
+        assertNotNull(highResConf);
+        assertEquals(highResConf.mScale, legacyScale, DELTA);
+        assertEquals(highResConf.mScale, 0.8f, DELTA);
+        assertFalse(highResConf.mForceLoadReducedJpeg);
+
+        PreRLegacySnapshotConfig lowResConf = mLoader.getLegacySnapshotConfig(
+                taskWidth, legacyScale, hasHighResFile, true /* loadLowResolutionBitmap */);
+        assertNotNull(lowResConf);
+        assertEquals(lowResConf.mScale, 0.5f * legacyScale, DELTA);
+        assertEquals(lowResConf.mScale, 0.5f * 0.8f, DELTA);
+        assertFalse(lowResConf.mForceLoadReducedJpeg);
+
+        mockSession.finishMocking();
+    }
+
+    @Test
+    public void testNonLegacyRConfig() throws Exception {
+        // taskWidth and legacyScale as would be defined in the proto, and presence of a *.jpg file,
+        // for any R device
+        final int taskWidth = 1440;
+        final float legacyScale = 0f;
+        final boolean hasHighResFile = true;
+
+        PreRLegacySnapshotConfig highResConf = mLoader.getLegacySnapshotConfig(
+                taskWidth, legacyScale, hasHighResFile, false /* loadLowResolutionBitmap */);
+        assertNull(highResConf);
+
+        PreRLegacySnapshotConfig lowResConf = mLoader.getLegacySnapshotConfig(
+                taskWidth, legacyScale, hasHighResFile, true /* loadLowResolutionBitmap */);
+        assertNull(lowResConf);
+    }
+
+    @Test
+    public void testDisabledLowResolutionPersistAndLoadSnapshot() {
+        mPersister.setEnableLowResSnapshots(false);
+
         TaskSnapshot a = new TaskSnapshotBuilder()
                 .setScaleFraction(0.5f)
                 .setIsLowResolution(true)
@@ -157,20 +308,20 @@
         mPersister.persistSnapshot(1, mTestUserId, a);
         mPersister.waitForQueueEmpty();
         final File[] files = new File[]{new File(FILES_DIR.getPath() + "/snapshots/1.proto"),
-                new File(FILES_DIR.getPath() + "/snapshots/1_reduced.jpg")};
+                new File(FILES_DIR.getPath() + "/snapshots/1.jpg")};
         final File[] nonExistsFiles = new File[]{
-                new File(FILES_DIR.getPath() + "/snapshots/1.jpg"),
+                new File(FILES_DIR.getPath() + "/snapshots/1_reduced.jpg"),
         };
         assertTrueForFiles(files, File::exists, " must exist");
         assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist");
-        final TaskSnapshot snapshot = mLoader.loadTask(1, mTestUserId, true /* isLowResolution */);
+        final TaskSnapshot snapshot = mLoader.loadTask(1, mTestUserId, false /* isLowResolution */);
         assertNotNull(snapshot);
         assertEquals(TEST_INSETS, snapshot.getContentInsets());
         assertNotNull(snapshot.getSnapshot());
         assertEquals(Configuration.ORIENTATION_PORTRAIT, snapshot.getOrientation());
 
         final TaskSnapshot snapshotNotExist = mLoader.loadTask(1, mTestUserId,
-                false /* isLowResolution */);
+                true /* isLowResolution */);
         assertNull(snapshotNotExist);
     }