Merge "Add device dimension data to wallpaper backup flow" into 24D1-dev
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index f31eb44..23e269a 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -39,7 +39,9 @@
 import android.content.SharedPreferences;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
+import android.graphics.Point;
 import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
@@ -49,16 +51,22 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.Xml;
+import android.view.Display;
+import android.view.DisplayInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.PackageMonitor;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
 
 import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -102,6 +110,9 @@
     @VisibleForTesting
     static final String WALLPAPER_INFO_STAGE = "wallpaper-info-stage";
 
+    @VisibleForTesting
+    static final String WALLPAPER_BACKUP_DEVICE_INFO_STAGE = "wallpaper-backup-device-info-stage";
+
     static final String EMPTY_SENTINEL = "empty";
     static final String QUOTA_SENTINEL = "quota";
 
@@ -110,6 +121,11 @@
     static final String SYSTEM_GENERATION = "system_gen";
     static final String LOCK_GENERATION = "lock_gen";
 
+    /**
+     * An approximate area threshold to compare device dimension similarity
+     */
+    static final int AREA_THRESHOLD = 50; // TODO (b/327637867): determine appropriate threshold
+
     // If this file exists, it means we exceeded our quota last time
     private File mQuotaFile;
     private boolean mQuotaExceeded;
@@ -121,6 +137,8 @@
     private boolean mSystemHasLiveComponent;
     private boolean mLockHasLiveComponent;
 
+    private DisplayManager mDisplayManager;
+
     @Override
     public void onCreate() {
         if (DEBUG) {
@@ -137,6 +155,8 @@
 
         mBackupManager = new BackupManager(getBaseContext());
         mEventLogger = new WallpaperEventLogger(mBackupManager, /* wallpaperAgent */ this);
+
+        mDisplayManager = getSystemService(DisplayManager.class);
     }
 
     @Override
@@ -175,9 +195,11 @@
             mSystemHasLiveComponent = mWallpaperManager.getWallpaperInfo(FLAG_SYSTEM) != null;
             mLockHasLiveComponent = mWallpaperManager.getWallpaperInfo(FLAG_LOCK) != null;
 
+            // performing backup of each file based on order of importance
             backupWallpaperInfoFile(/* sysOrLockChanged= */ sysChanged || lockChanged, data);
             backupSystemWallpaperFile(sharedPrefs, sysChanged, sysGeneration, data);
             backupLockWallpaperFileIfItExists(sharedPrefs, lockChanged, lockGeneration, data);
+            backupDeviceInfoFile(data);
         } catch (Exception e) {
             Slog.e(TAG, "Unable to back up wallpaper", e);
             mEventLogger.onBackupException(e);
@@ -191,6 +213,54 @@
         }
     }
 
+    /**
+     * This method backs up the device dimension information. The device data will always get
+     * overwritten when triggering a backup
+     */
+    private void backupDeviceInfoFile(FullBackupDataOutput data)
+            throws IOException {
+        final File deviceInfoStage = new File(getFilesDir(), WALLPAPER_BACKUP_DEVICE_INFO_STAGE);
+
+        // save the dimensions of the device with xml formatting
+        Point dimensions = getScreenDimensions();
+        Display smallerDisplay = getSmallerDisplayIfExists();
+        Point secondaryDimensions = smallerDisplay != null ? getRealSize(smallerDisplay) :
+                new Point(0, 0);
+
+        deviceInfoStage.createNewFile();
+        FileOutputStream fstream = new FileOutputStream(deviceInfoStage, false);
+        TypedXmlSerializer out = Xml.resolveSerializer(fstream);
+        out.startDocument(null, true);
+        out.startTag(null, "dimensions");
+
+        out.startTag(null, "width");
+        out.text(String.valueOf(dimensions.x));
+        out.endTag(null, "width");
+
+        out.startTag(null, "height");
+        out.text(String.valueOf(dimensions.y));
+        out.endTag(null, "height");
+
+        if (smallerDisplay != null) {
+            out.startTag(null, "secondarywidth");
+            out.text(String.valueOf(secondaryDimensions.x));
+            out.endTag(null, "secondarywidth");
+
+            out.startTag(null, "secondaryheight");
+            out.text(String.valueOf(secondaryDimensions.y));
+            out.endTag(null, "secondaryheight");
+        }
+
+        out.endTag(null, "dimensions");
+        out.endDocument();
+        fstream.flush();
+        FileUtils.sync(fstream);
+        fstream.close();
+
+        if (DEBUG) Slog.v(TAG, "Storing device dimension data");
+        backupFile(deviceInfoStage, data);
+    }
+
     private void backupWallpaperInfoFile(boolean sysOrLockChanged, FullBackupDataOutput data)
             throws IOException {
         final ParcelFileDescriptor wallpaperInfoFd = mWallpaperManager.getWallpaperInfoFile();
@@ -364,9 +434,22 @@
         final File infoStage = new File(filesDir, WALLPAPER_INFO_STAGE);
         final File imageStage = new File(filesDir, SYSTEM_WALLPAPER_STAGE);
         final File lockImageStage = new File(filesDir, LOCK_WALLPAPER_STAGE);
+        final File deviceDimensionsStage = new File(filesDir, WALLPAPER_BACKUP_DEVICE_INFO_STAGE);
         boolean lockImageStageExists = lockImageStage.exists();
 
         try {
+            // Parse the device dimensions of the source device and compare with target to
+            // to identify whether we need to skip the remainder of the restore process
+            Pair<Point, Point> sourceDeviceDimensions = parseDeviceDimensions(
+                    deviceDimensionsStage);
+
+            Point targetDeviceDimensions = getScreenDimensions();
+            if (sourceDeviceDimensions != null && targetDeviceDimensions != null
+                    && isSourceDeviceSignificantlySmallerThanTarget(sourceDeviceDimensions.first,
+                    targetDeviceDimensions)) {
+                Slog.d(TAG, "The source device is significantly smaller than target");
+            }
+
             // First parse the live component name so that we know for logging if we care about
             // logging errors with the image restore.
             ComponentName wpService = parseWallpaperComponent(infoStage, "wp");
@@ -400,6 +483,7 @@
             infoStage.delete();
             imageStage.delete();
             lockImageStage.delete();
+            deviceDimensionsStage.delete();
 
             SharedPreferences prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
             prefs.edit()
@@ -409,6 +493,66 @@
         }
     }
 
+    /**
+     * This method parses the given file for the backed up device dimensions
+     *
+     * @param deviceDimensions the file which holds the device dimensions
+     * @return the backed up device dimensions
+     */
+    private Pair<Point, Point> parseDeviceDimensions(File deviceDimensions) {
+        int width = 0, height = 0, secondaryHeight = 0, secondaryWidth = 0;
+        try {
+            TypedXmlPullParser parser = Xml.resolvePullParser(
+                    new FileInputStream(deviceDimensions));
+
+            while (parser.next() != XmlPullParser.END_TAG) {
+                if (parser.getEventType() != XmlPullParser.START_TAG) {
+                    continue;
+                }
+
+                String name = parser.getName();
+
+                switch (name) {
+                    case "width":
+                        String widthText = readText(parser);
+                        width = Integer.valueOf(widthText);
+                        break;
+
+                    case "height":
+                        String textHeight = readText(parser);
+                        height = Integer.valueOf(textHeight);
+                        break;
+
+                    case "secondarywidth":
+                        String secondaryWidthText = readText(parser);
+                        secondaryWidth = Integer.valueOf(secondaryWidthText);
+                        break;
+
+                    case "secondaryheight":
+                        String secondaryHeightText = readText(parser);
+                        secondaryHeight = Integer.valueOf(secondaryHeightText);
+                        break;
+                    default:
+                        break;
+                }
+            }
+            return new Pair<>(new Point(width, height), new Point(secondaryWidth, secondaryHeight));
+
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    private static String readText(TypedXmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        String result = "";
+        if (parser.next() == XmlPullParser.TEXT) {
+            result = parser.getText();
+            parser.nextTag();
+        }
+        return result;
+    }
+
     @VisibleForTesting
     void updateWallpaperComponent(ComponentName wpService, int which)
             throws IOException {
@@ -691,6 +835,94 @@
         };
     }
 
+    /**
+     * This method retrieves the dimensions of the largest display of the device
+     *
+     * @return a @{Point} object that contains the dimensions of the largest display on the device
+     */
+    private Point getScreenDimensions() {
+        Point largetDimensions = null;
+        int maxArea = 0;
+
+        for (Display display : getInternalDisplays()) {
+            Point displaySize = getRealSize(display);
+
+            int width = displaySize.x;
+            int height = displaySize.y;
+            int area = width * height;
+
+            if (area > maxArea) {
+                maxArea = area;
+                largetDimensions = displaySize;
+            }
+        }
+
+        return largetDimensions;
+    }
+
+    private Point getRealSize(Display display) {
+        DisplayInfo displayInfo = new DisplayInfo();
+        display.getDisplayInfo(displayInfo);
+        return new Point(displayInfo.logicalWidth, displayInfo.logicalHeight);
+    }
+
+    /**
+     * This method returns the smaller display on a multi-display device
+     *
+     * @return Display that corresponds to the smaller display on a device or null if ther is only
+     * one Display on a device
+     */
+    private Display getSmallerDisplayIfExists() {
+        List<Display> internalDisplays = getInternalDisplays();
+        Point largestDisplaySize = getScreenDimensions();
+
+        // Find the first non-matching internal display
+        for (Display display : internalDisplays) {
+            Point displaySize = getRealSize(display);
+            if (displaySize.x != largestDisplaySize.x || displaySize.y != largestDisplaySize.y) {
+                return display;
+            }
+        }
+
+        // If no smaller display found, return null, as there is only a single display
+        return null;
+    }
+
+    /**
+     * This method retrieves the collection of Display objects available in the device.
+     * i.e. non-external displays are ignored
+     *
+     * @return list of displays corresponding to each display in the device
+     */
+    private List<Display> getInternalDisplays() {
+        Display[] allDisplays = mDisplayManager.getDisplays(
+                DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);
+
+        List<Display> internalDisplays = new ArrayList<>();
+        for (Display display : allDisplays) {
+            if (display.getType() == Display.TYPE_INTERNAL) {
+                internalDisplays.add(display);
+            }
+        }
+        return internalDisplays;
+    }
+
+    /**
+     * This method compares the source and target dimensions, and returns true if there is a
+     * significant difference in area between them and the source dimensions are smaller than the
+     * target dimensions.
+     *
+     * @param sourceDimensions is the dimensions of the source device
+     * @param targetDimensions is the dimensions of the target device
+     */
+    @VisibleForTesting
+    boolean isSourceDeviceSignificantlySmallerThanTarget(Point sourceDimensions,
+            Point targetDimensions) {
+        int rawAreaDelta = (targetDimensions.x * targetDimensions.y)
+                - (sourceDimensions.x * sourceDimensions.y);
+        return rawAreaDelta > AREA_THRESHOLD;
+    }
+
     @VisibleForTesting
     boolean isDeviceInRestore() {
         try {
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index 3ecdf3f..ec9223c 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -59,6 +59,7 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
@@ -840,6 +841,26 @@
         testParseCropHints(testMap);
     }
 
+    @Test
+    public void test_sourceDimensionsAreLargerThanTarget() {
+        // source device is larger than target, expecting to get false
+        Point sourceDimensions = new Point(2208, 1840);
+        Point targetDimensions = new Point(1080, 2092);
+        boolean isSourceSmaller = mWallpaperBackupAgent
+                .isSourceDeviceSignificantlySmallerThanTarget(sourceDimensions, targetDimensions);
+        assertThat(isSourceSmaller).isEqualTo(false);
+    }
+
+    @Test
+    public void test_sourceDimensionsMuchSmallerThanTarget() {
+        // source device is smaller than target, expecting to get true
+        Point sourceDimensions = new Point(1080, 2092);
+        Point targetDimensions = new Point(2208, 1840);
+        boolean isSourceSmaller = mWallpaperBackupAgent
+                .isSourceDeviceSignificantlySmallerThanTarget(sourceDimensions, targetDimensions);
+        assertThat(isSourceSmaller).isEqualTo(true);
+    }
+
     private void testParseCropHints(Map<Integer, Rect> testMap) throws Exception {
         assumeTrue(multiCrop());
         mockRestoredStaticWallpaperFile(testMap);