• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.wallpaperbackup;
18 
19 import static android.app.Flags.liveWallpaperContentHandling;
20 import static android.app.WallpaperManager.FLAG_LOCK;
21 import static android.app.WallpaperManager.FLAG_SYSTEM;
22 import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
23 import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
24 
25 import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_INELIGIBLE;
26 import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_METADATA;
27 import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_WALLPAPER;
28 import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_QUOTA_EXCEEDED;
29 import static com.android.window.flags.Flags.multiCrop;
30 
31 import android.annotation.Nullable;
32 import android.app.AppGlobals;
33 import android.app.WallpaperManager;
34 import android.app.backup.BackupAgent;
35 import android.app.backup.BackupDataInput;
36 import android.app.backup.BackupDataOutput;
37 import android.app.backup.BackupManager;
38 import android.app.backup.BackupRestoreEventLogger.BackupRestoreError;
39 import android.app.backup.FullBackupDataOutput;
40 import android.app.wallpaper.WallpaperDescription;
41 import android.content.ComponentName;
42 import android.content.Context;
43 import android.content.SharedPreferences;
44 import android.content.pm.IPackageManager;
45 import android.content.pm.PackageInfo;
46 import android.graphics.BitmapFactory;
47 import android.graphics.Point;
48 import android.graphics.Rect;
49 import android.hardware.display.DisplayManager;
50 import android.os.FileUtils;
51 import android.os.ParcelFileDescriptor;
52 import android.os.RemoteException;
53 import android.os.UserHandle;
54 import android.provider.Settings;
55 import android.util.Pair;
56 import android.util.Slog;
57 import android.util.SparseArray;
58 import android.util.Xml;
59 import android.view.Display;
60 import android.view.DisplayInfo;
61 
62 import com.android.internal.annotations.VisibleForTesting;
63 import com.android.internal.content.PackageMonitor;
64 import com.android.modules.utils.TypedXmlPullParser;
65 import com.android.modules.utils.TypedXmlSerializer;
66 
67 import org.xmlpull.v1.XmlPullParser;
68 import org.xmlpull.v1.XmlPullParserException;
69 
70 import java.io.File;
71 import java.io.FileInputStream;
72 import java.io.FileOutputStream;
73 import java.io.IOException;
74 import java.util.ArrayList;
75 import java.util.List;
76 
77 /**
78  * Backs up and restores wallpaper and metadata related to it.
79  *
80  * This agent has its own package because it does full backup as opposed to SystemBackupAgent
81  * which does key/value backup.
82  *
83  * This class stages wallpaper files for backup by copying them into its own directory because of
84  * the following reasons:
85  *
86  * <ul>
87  *     <li>Non-system users don't have permission to read the directory that the system stores
88  *     the wallpaper files in</li>
89  *     <li>{@link BackupAgent} enforces that backed up files must live inside the package's
90  *     {@link Context#getFilesDir()}</li>
91  * </ul>
92  *
93  * There are 3 files to back up:
94  * <ul>
95  *     <li>The "wallpaper info"  file which contains metadata like the crop applied to the
96  *     wallpaper or the live wallpaper component name.</li>
97  *     <li>The "system" wallpaper file.</li>
98  *     <li>An optional "lock" wallpaper, which is shown on the lockscreen instead of the system
99  *     wallpaper if set.</li>
100  * </ul>
101  *
102  * On restore, the metadata file is parsed and {@link WallpaperManager} APIs are used to set the
103  * wallpaper. Note that if there's a live wallpaper, the live wallpaper package name will be
104  * part of the metadata file and the wallpaper will be applied when the package it's installed.
105  */
106 public class WallpaperBackupAgent extends BackupAgent {
107     private static final String TAG = "WallpaperBackup";
108     private static final boolean DEBUG = false;
109 
110     // Names of our local-data stage files
111     @VisibleForTesting
112     static final String SYSTEM_WALLPAPER_STAGE = "wallpaper-stage";
113     @VisibleForTesting
114     static final String LOCK_WALLPAPER_STAGE = "wallpaper-lock-stage";
115     @VisibleForTesting
116     static final String WALLPAPER_INFO_STAGE = "wallpaper-info-stage";
117     @VisibleForTesting
118     static final String WALLPAPER_BACKUP_DEVICE_INFO_STAGE = "wallpaper-backup-device-info-stage";
119     static final String EMPTY_SENTINEL = "empty";
120     static final String QUOTA_SENTINEL = "quota";
121     // Shared preferences constants.
122     static final String PREFS_NAME = "wbprefs.xml";
123     static final String SYSTEM_GENERATION = "system_gen";
124     static final String LOCK_GENERATION = "lock_gen";
125 
126     static final String DEVICE_CONFIG_WIDTH = "device_config_width";
127 
128     static final String DEVICE_CONFIG_HEIGHT = "device_config_height";
129 
130     static final String DEVICE_CONFIG_SECONDARY_WIDTH = "device_config_secondary_width";
131 
132     static final String DEVICE_CONFIG_SECONDARY_HEIGHT = "device_config_secondary_height";
133 
134     static final float DEFAULT_ACCEPTABLE_PARALLAX = 0.2f;
135 
136     // If this file exists, it means we exceeded our quota last time
137     private File mQuotaFile;
138     private boolean mQuotaExceeded;
139 
140     private WallpaperManager mWallpaperManager;
141     private WallpaperEventLogger mEventLogger;
142     private BackupManager mBackupManager;
143 
144     private boolean mSystemHasLiveComponent;
145     private boolean mLockHasLiveComponent;
146 
147     private DisplayManager mDisplayManager;
148 
149     @Override
onCreate()150     public void onCreate() {
151         if (DEBUG) {
152             Slog.v(TAG, "onCreate()");
153         }
154 
155         mWallpaperManager = getSystemService(WallpaperManager.class);
156 
157         mQuotaFile = new File(getFilesDir(), QUOTA_SENTINEL);
158         mQuotaExceeded = mQuotaFile.exists();
159         if (DEBUG) {
160             Slog.v(TAG, "quota file " + mQuotaFile.getPath() + " exists=" + mQuotaExceeded);
161         }
162 
163         mBackupManager = new BackupManager(getBaseContext());
164         mEventLogger = new WallpaperEventLogger(mBackupManager, /* wallpaperAgent */ this);
165 
166         mDisplayManager = getSystemService(DisplayManager.class);
167     }
168 
169     @Override
onFullBackup(FullBackupDataOutput data)170     public void onFullBackup(FullBackupDataOutput data) throws IOException {
171         try {
172             // We always back up this 'empty' file to ensure that the absence of
173             // storable wallpaper imagery still produces a non-empty backup data
174             // stream, otherwise it'd simply be ignored in preflight.
175             final File empty = new File(getFilesDir(), EMPTY_SENTINEL);
176             if (!empty.exists()) {
177                 FileOutputStream touch = new FileOutputStream(empty);
178                 touch.close();
179             }
180             backupFile(empty, data);
181 
182             SharedPreferences sharedPrefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
183 
184             // Check the IDs of the wallpapers that we backed up last time. If they haven't
185             // changed, we won't re-stage them for backup and use the old staged versions to avoid
186             // disk churn.
187             final int lastSysGeneration = sharedPrefs.getInt(SYSTEM_GENERATION, /* defValue= */ -1);
188             final int lastLockGeneration = sharedPrefs.getInt(LOCK_GENERATION, /* defValue= */ -1);
189 
190             final int deviceConfigWidth = sharedPrefs.getInt(
191                     DEVICE_CONFIG_WIDTH, /* defValue= */ -1);
192             final int deviceConfigHeight = sharedPrefs.getInt(
193                     DEVICE_CONFIG_HEIGHT, /* defValue= */ -1);
194             final int deviceConfigSecondaryWidth = sharedPrefs.getInt(
195                     DEVICE_CONFIG_SECONDARY_WIDTH, /* defValue= */ -1);
196             final int deviceConfigSecondaryHeight = sharedPrefs.getInt(
197                     DEVICE_CONFIG_SECONDARY_HEIGHT, /* defValue= */ -1);
198 
199             final int sysGeneration = mWallpaperManager.getWallpaperId(FLAG_SYSTEM);
200             final int lockGeneration = mWallpaperManager.getWallpaperId(FLAG_LOCK);
201             final boolean sysChanged = (sysGeneration != lastSysGeneration);
202             final boolean lockChanged = (lockGeneration != lastLockGeneration);
203 
204             if (DEBUG) {
205                 Slog.v(TAG, "sysGen=" + sysGeneration + " : sysChanged=" + sysChanged);
206                 Slog.v(TAG, "lockGen=" + lockGeneration + " : lockChanged=" + lockChanged);
207             }
208 
209             // Due to the way image vs live wallpaper backup logic is intermingled, for logging
210             // purposes first check if we have live components for each wallpaper to avoid
211             // over-reporting errors.
212             mSystemHasLiveComponent = mWallpaperManager.getWallpaperInfo(FLAG_SYSTEM) != null;
213             mLockHasLiveComponent = mWallpaperManager.getWallpaperInfo(FLAG_LOCK) != null;
214 
215             // performing backup of each file based on order of importance
216             backupWallpaperInfoFile(/* sysOrLockChanged= */ sysChanged || lockChanged, data);
217             backupSystemWallpaperFile(sharedPrefs, sysChanged, sysGeneration, data);
218             backupLockWallpaperFileIfItExists(sharedPrefs, lockChanged, lockGeneration, data);
219 
220             final boolean isDeviceConfigChanged = isDeviceConfigChanged(deviceConfigWidth,
221                     deviceConfigHeight, deviceConfigSecondaryWidth, deviceConfigSecondaryHeight);
222 
223             backupDeviceInfoFile(sharedPrefs, isDeviceConfigChanged, data);
224         } catch (Exception e) {
225             Slog.e(TAG, "Unable to back up wallpaper", e);
226             mEventLogger.onBackupException(e);
227         } finally {
228             // Even if this time we had to back off on attempting to store the lock image
229             // due to exceeding the data quota, try again next time.  This will alternate
230             // between "try both" and "only store the primary image" until either there
231             // is no lock image to store, or the quota is raised, or both fit under the
232             // quota.
233             mQuotaFile.delete();
234         }
235     }
236 
isDeviceConfigChanged(int width, int height, int secondaryWidth, int secondaryHeight)237     private boolean isDeviceConfigChanged(int width, int height, int secondaryWidth,
238             int secondaryHeight) {
239         Point currentDimensions = getScreenDimensions();
240         Display smallerDisplay = getSmallerDisplayIfExists();
241         Point currentSecondaryDimensions = smallerDisplay != null ? getRealSize(smallerDisplay) :
242                 new Point(0, 0);
243 
244         return (currentDimensions.x != width
245                 || currentDimensions.y != height
246                 || currentSecondaryDimensions.x != secondaryWidth
247                 || currentSecondaryDimensions.y != secondaryHeight);
248     }
249 
250     /**
251      * This method backs up the device dimension information. The device data will always get
252      * overwritten when triggering a backup
253      */
backupDeviceInfoFile(SharedPreferences sharedPrefs, boolean isDeviceConfigChanged, FullBackupDataOutput data)254     private void backupDeviceInfoFile(SharedPreferences sharedPrefs, boolean isDeviceConfigChanged,
255             FullBackupDataOutput data)
256             throws IOException {
257         final File deviceInfoStage = new File(getFilesDir(), WALLPAPER_BACKUP_DEVICE_INFO_STAGE);
258 
259         if (isDeviceConfigChanged) {
260             // save the dimensions of the device with xml formatting
261             Point dimensions = getScreenDimensions();
262             Display smallerDisplay = getSmallerDisplayIfExists();
263             Point secondaryDimensions = smallerDisplay != null ? getRealSize(smallerDisplay) :
264                     new Point(0, 0);
265 
266             deviceInfoStage.createNewFile();
267             FileOutputStream fstream = new FileOutputStream(deviceInfoStage, false);
268             TypedXmlSerializer out = Xml.resolveSerializer(fstream);
269             out.startDocument(null, true);
270             out.startTag(null, "dimensions");
271 
272             out.startTag(null, "width");
273             out.text(String.valueOf(dimensions.x));
274             out.endTag(null, "width");
275 
276             out.startTag(null, "height");
277             out.text(String.valueOf(dimensions.y));
278             out.endTag(null, "height");
279 
280             if (smallerDisplay != null) {
281                 out.startTag(null, "secondarywidth");
282                 out.text(String.valueOf(secondaryDimensions.x));
283                 out.endTag(null, "secondarywidth");
284 
285                 out.startTag(null, "secondaryheight");
286                 out.text(String.valueOf(secondaryDimensions.y));
287                 out.endTag(null, "secondaryheight");
288             }
289 
290             out.endTag(null, "dimensions");
291             out.endDocument();
292             fstream.flush();
293             FileUtils.sync(fstream);
294             fstream.close();
295 
296             SharedPreferences.Editor editor = sharedPrefs.edit();
297             editor.putInt(DEVICE_CONFIG_WIDTH, dimensions.x);
298             editor.putInt(DEVICE_CONFIG_HEIGHT, dimensions.y);
299             editor.putInt(DEVICE_CONFIG_SECONDARY_WIDTH, secondaryDimensions.x);
300             editor.putInt(DEVICE_CONFIG_SECONDARY_HEIGHT, secondaryDimensions.y);
301             editor.apply();
302         }
303         if (DEBUG) Slog.v(TAG, "Storing device dimension data");
304         backupFile(deviceInfoStage, data);
305     }
306 
backupWallpaperInfoFile(boolean sysOrLockChanged, FullBackupDataOutput data)307     private void backupWallpaperInfoFile(boolean sysOrLockChanged, FullBackupDataOutput data)
308             throws IOException {
309         final ParcelFileDescriptor wallpaperInfoFd = mWallpaperManager.getWallpaperInfoFile();
310 
311         if (wallpaperInfoFd == null) {
312             Slog.w(TAG, "Wallpaper metadata file doesn't exist");
313             // If we have live components, getting the file to back up somehow failed, so log it
314             // as an error.
315             if (mSystemHasLiveComponent) {
316                 mEventLogger.onSystemLiveWallpaperBackupFailed(ERROR_NO_METADATA);
317             }
318             if (mLockHasLiveComponent) {
319                 mEventLogger.onLockLiveWallpaperBackupFailed(ERROR_NO_METADATA);
320             }
321             return;
322         }
323 
324         final File infoStage = new File(getFilesDir(), WALLPAPER_INFO_STAGE);
325 
326         if (sysOrLockChanged || !infoStage.exists()) {
327             if (DEBUG) Slog.v(TAG, "New wallpaper configuration; copying");
328             copyFromPfdToFileAndClosePfd(wallpaperInfoFd, infoStage);
329         }
330 
331         if (DEBUG) Slog.v(TAG, "Storing wallpaper metadata");
332         backupFile(infoStage, data);
333 
334         // We've backed up the info file which contains the live component, so log it as success
335         if (mSystemHasLiveComponent) {
336             mEventLogger.onSystemLiveWallpaperBackedUp(
337                     mWallpaperManager.getWallpaperInfo(FLAG_SYSTEM));
338         }
339         if (mLockHasLiveComponent) {
340             mEventLogger.onLockLiveWallpaperBackedUp(mWallpaperManager.getWallpaperInfo(FLAG_LOCK));
341         }
342     }
343 
backupSystemWallpaperFile(SharedPreferences sharedPrefs, boolean sysChanged, int sysGeneration, FullBackupDataOutput data)344     private void backupSystemWallpaperFile(SharedPreferences sharedPrefs,
345             boolean sysChanged, int sysGeneration, FullBackupDataOutput data) throws IOException {
346         if (!mWallpaperManager.isWallpaperBackupEligible(FLAG_SYSTEM)) {
347             Slog.d(TAG, "System wallpaper ineligible for backup");
348             logSystemImageErrorIfNoLiveComponent(ERROR_INELIGIBLE);
349             return;
350         }
351 
352         final ParcelFileDescriptor systemWallpaperImageFd = mWallpaperManager.getWallpaperFile(
353                 FLAG_SYSTEM,
354                 /* getCropped= */ false);
355 
356         if (systemWallpaperImageFd == null) {
357             Slog.w(TAG, "System wallpaper doesn't exist");
358             logSystemImageErrorIfNoLiveComponent(ERROR_NO_WALLPAPER);
359             return;
360         }
361 
362         final File imageStage = new File(getFilesDir(), SYSTEM_WALLPAPER_STAGE);
363 
364         if (sysChanged || !imageStage.exists()) {
365             if (DEBUG) Slog.v(TAG, "New system wallpaper; copying");
366             copyFromPfdToFileAndClosePfd(systemWallpaperImageFd, imageStage);
367         }
368 
369         if (DEBUG) Slog.v(TAG, "Storing system wallpaper image");
370         backupFile(imageStage, data);
371         sharedPrefs.edit().putInt(SYSTEM_GENERATION, sysGeneration).apply();
372         mEventLogger.onSystemImageWallpaperBackedUp();
373     }
374 
logSystemImageErrorIfNoLiveComponent(@ackupRestoreError String error)375     private void logSystemImageErrorIfNoLiveComponent(@BackupRestoreError String error) {
376         if (mSystemHasLiveComponent) {
377             return;
378         }
379         mEventLogger.onSystemImageWallpaperBackupFailed(error);
380     }
381 
backupLockWallpaperFileIfItExists(SharedPreferences sharedPrefs, boolean lockChanged, int lockGeneration, FullBackupDataOutput data)382     private void backupLockWallpaperFileIfItExists(SharedPreferences sharedPrefs,
383             boolean lockChanged, int lockGeneration, FullBackupDataOutput data) throws IOException {
384         final File lockImageStage = new File(getFilesDir(), LOCK_WALLPAPER_STAGE);
385 
386         // This means there's no lock wallpaper set by the user.
387         if (lockGeneration == -1) {
388             if (lockChanged && lockImageStage.exists()) {
389                 if (DEBUG) Slog.v(TAG, "Removed lock wallpaper; deleting");
390                 lockImageStage.delete();
391             }
392             Slog.d(TAG, "No lockscreen wallpaper set, add nothing to backup");
393             sharedPrefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply();
394             logLockImageErrorIfNoLiveComponent(ERROR_NO_WALLPAPER);
395             return;
396         }
397 
398         if (!mWallpaperManager.isWallpaperBackupEligible(FLAG_LOCK)) {
399             Slog.d(TAG, "Lock screen wallpaper ineligible for backup");
400             logLockImageErrorIfNoLiveComponent(ERROR_INELIGIBLE);
401             return;
402         }
403 
404         final ParcelFileDescriptor lockWallpaperFd = mWallpaperManager.getWallpaperFile(
405                 FLAG_LOCK, /* getCropped= */ false);
406 
407         // If we get to this point, that means lockGeneration != -1 so there's a lock wallpaper
408         // set, but we can't find it.
409         if (lockWallpaperFd == null) {
410             Slog.w(TAG, "Lock wallpaper doesn't exist");
411             logLockImageErrorIfNoLiveComponent(ERROR_NO_WALLPAPER);
412             return;
413         }
414 
415         if (mQuotaExceeded) {
416             Slog.w(TAG, "Not backing up lock screen wallpaper. Quota was exceeded last time");
417             logLockImageErrorIfNoLiveComponent(ERROR_QUOTA_EXCEEDED);
418             return;
419         }
420 
421         if (lockChanged || !lockImageStage.exists()) {
422             if (DEBUG) Slog.v(TAG, "New lock wallpaper; copying");
423             copyFromPfdToFileAndClosePfd(lockWallpaperFd, lockImageStage);
424         }
425 
426         if (DEBUG) Slog.v(TAG, "Storing lock wallpaper image");
427         backupFile(lockImageStage, data);
428         sharedPrefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply();
429         mEventLogger.onLockImageWallpaperBackedUp();
430     }
431 
logLockImageErrorIfNoLiveComponent(@ackupRestoreError String error)432     private void logLockImageErrorIfNoLiveComponent(@BackupRestoreError String error) {
433         if (mLockHasLiveComponent) {
434             return;
435         }
436         mEventLogger.onLockImageWallpaperBackupFailed(error);
437     }
438 
439     /**
440      * Copies the contents of the given {@code pfd} to the given {@code file}.
441      *
442      * All resources used in the process including the {@code pfd} will be closed.
443      */
copyFromPfdToFileAndClosePfd(ParcelFileDescriptor pfd, File file)444     private static void copyFromPfdToFileAndClosePfd(ParcelFileDescriptor pfd, File file)
445             throws IOException {
446         try (ParcelFileDescriptor.AutoCloseInputStream inputStream =
447                      new ParcelFileDescriptor.AutoCloseInputStream(pfd);
448              FileOutputStream outputStream = new FileOutputStream(file)
449         ) {
450             FileUtils.copy(inputStream, outputStream);
451         }
452     }
453 
readText(TypedXmlPullParser parser)454     private static String readText(TypedXmlPullParser parser)
455             throws IOException, XmlPullParserException {
456         String result = "";
457         if (parser.next() == XmlPullParser.TEXT) {
458             result = parser.getText();
459             parser.nextTag();
460         }
461         return result;
462     }
463 
464     @VisibleForTesting
465     // fullBackupFile is final, so we intercept backups here in tests.
backupFile(File file, FullBackupDataOutput data)466     protected void backupFile(File file, FullBackupDataOutput data) {
467         fullBackupFile(file, data);
468     }
469 
470     @Override
onQuotaExceeded(long backupDataBytes, long quotaBytes)471     public void onQuotaExceeded(long backupDataBytes, long quotaBytes) {
472         Slog.i(TAG, "Quota exceeded (" + backupDataBytes + " vs " + quotaBytes + ')');
473         try (FileOutputStream f = new FileOutputStream(mQuotaFile)) {
474             f.write(0);
475         } catch (Exception e) {
476             Slog.w(TAG, "Unable to record quota-exceeded: " + e.getMessage());
477         }
478     }
479 
480     // We use the default onRestoreFile() implementation that will recreate our stage files,
481     // then post-process in onRestoreFinished() to apply the new wallpaper.
482     @Override
onRestoreFinished()483     public void onRestoreFinished() {
484         Slog.v(TAG, "onRestoreFinished()");
485         final File filesDir = getFilesDir();
486         final File infoStage = new File(filesDir, WALLPAPER_INFO_STAGE);
487         final File imageStage = new File(filesDir, SYSTEM_WALLPAPER_STAGE);
488         final File lockImageStage = new File(filesDir, LOCK_WALLPAPER_STAGE);
489         final File deviceDimensionsStage = new File(filesDir, WALLPAPER_BACKUP_DEVICE_INFO_STAGE);
490         boolean lockImageStageExists = lockImageStage.exists();
491 
492         try {
493             // Parse the device dimensions of the source device
494             Pair<Point, Point> sourceDeviceDimensions = parseDeviceDimensions(
495                     deviceDimensionsStage);
496 
497             // First parse the live component name so that we know for logging if we care about
498             // logging errors with the image restore.
499             Pair<ComponentName, WallpaperDescription> wpService = parseWallpaperComponent(infoStage,
500                     "wp");
501             mSystemHasLiveComponent = wpService.first != null;
502 
503             Pair<ComponentName, WallpaperDescription> kwpService = parseWallpaperComponent(
504                     infoStage, "kwp");
505             mLockHasLiveComponent = kwpService.first != null;
506             boolean separateLockWallpaper = mLockHasLiveComponent || lockImageStage.exists();
507 
508             // if there's no separate lock wallpaper, apply the system wallpaper to both screens.
509             final int sysWhich = separateLockWallpaper ? FLAG_SYSTEM : FLAG_SYSTEM | FLAG_LOCK;
510 
511             // It is valid for the imagery to be absent; it means that we were not permitted
512             // to back up the original image on the source device, or there was no user-supplied
513             // wallpaper image present.
514             if (lockImageStageExists) {
515                 restoreFromStage(lockImageStage, infoStage, "kwp", FLAG_LOCK,
516                         sourceDeviceDimensions);
517             }
518             restoreFromStage(imageStage, infoStage, "wp", sysWhich, sourceDeviceDimensions);
519 
520             // And reset to the wallpaper service we should be using
521             if (mLockHasLiveComponent) {
522                 updateWallpaperComponent(kwpService, FLAG_LOCK);
523             }
524             updateWallpaperComponent(wpService, sysWhich);
525         } catch (Exception e) {
526             Slog.e(TAG, "Unable to restore wallpaper: " + e.getMessage());
527             mEventLogger.onRestoreException(e);
528         } finally {
529             Slog.v(TAG, "Restore finished; clearing backup bookkeeping");
530             infoStage.delete();
531             imageStage.delete();
532             lockImageStage.delete();
533             deviceDimensionsStage.delete();
534 
535             SharedPreferences prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
536             prefs.edit()
537                     .putInt(SYSTEM_GENERATION, -1)
538                     .putInt(LOCK_GENERATION, -1)
539                     .commit();
540         }
541     }
542 
543     /**
544      * This method parses the given file for the backed up device dimensions
545      *
546      * @param deviceDimensions the file which holds the device dimensions
547      * @return the backed up device dimensions
548      */
parseDeviceDimensions(File deviceDimensions)549     private Pair<Point, Point> parseDeviceDimensions(File deviceDimensions) {
550         int width = 0, height = 0, secondaryHeight = 0, secondaryWidth = 0;
551         try {
552             TypedXmlPullParser parser = Xml.resolvePullParser(
553                     new FileInputStream(deviceDimensions));
554 
555             while (parser.next() != XmlPullParser.END_TAG) {
556                 if (parser.getEventType() != XmlPullParser.START_TAG) {
557                     continue;
558                 }
559 
560                 String name = parser.getName();
561 
562                 switch (name) {
563                     case "width":
564                         String widthText = readText(parser);
565                         width = Integer.valueOf(widthText);
566                         break;
567 
568                     case "height":
569                         String textHeight = readText(parser);
570                         height = Integer.valueOf(textHeight);
571                         break;
572 
573                     case "secondarywidth":
574                         String secondaryWidthText = readText(parser);
575                         secondaryWidth = Integer.valueOf(secondaryWidthText);
576                         break;
577 
578                     case "secondaryheight":
579                         String secondaryHeightText = readText(parser);
580                         secondaryHeight = Integer.valueOf(secondaryHeightText);
581                         break;
582                     default:
583                         break;
584                 }
585             }
586             return new Pair<>(new Point(width, height), new Point(secondaryWidth, secondaryHeight));
587 
588         } catch (Exception e) {
589             return null;
590         }
591     }
592 
593     @VisibleForTesting
updateWallpaperComponent(Pair<ComponentName, WallpaperDescription> wpService, int which)594     void updateWallpaperComponent(Pair<ComponentName, WallpaperDescription> wpService, int which)
595             throws IOException {
596         WallpaperDescription description = wpService.second;
597         boolean hasDescription = (liveWallpaperContentHandling() && description != null);
598         ComponentName component = hasDescription ? description.getComponent() : wpService.first;
599         if (servicePackageExists(component)) {
600             if (hasDescription) {
601                 Slog.i(TAG, "Using wallpaper description " + description);
602                 mWallpaperManager.setWallpaperComponentWithDescription(description, which);
603                 if ((which & FLAG_LOCK) != 0) {
604                     mEventLogger.onLockLiveWallpaperRestoredWithDescription(description);
605                 }
606                 if ((which & FLAG_SYSTEM) != 0) {
607                     mEventLogger.onSystemLiveWallpaperRestoredWithDescription(description);
608                 }
609             } else {
610                 Slog.i(TAG, "Using wallpaper service " + component);
611                 mWallpaperManager.setWallpaperComponentWithFlags(component, which);
612                 if ((which & FLAG_LOCK) != 0) {
613                     mEventLogger.onLockLiveWallpaperRestored(component);
614                 }
615                 if ((which & FLAG_SYSTEM) != 0) {
616                     mEventLogger.onSystemLiveWallpaperRestored(component);
617                 }
618             }
619         } else {
620             // If we've restored a live wallpaper, but the component doesn't exist,
621             // we should log it as an error so we can easily identify the problem
622             // in reports from users
623             if (component != null) {
624                 // TODO(b/268471749): Handle delayed case
625                 applyComponentAtInstall(component, description, which);
626                 Slog.w(TAG, "Wallpaper service " + component + " isn't available. "
627                         + " Will try to apply later");
628             }
629         }
630     }
631 
restoreFromStage(File stage, File info, String hintTag, int which, Pair<Point, Point> sourceDeviceDimensions)632     private void restoreFromStage(File stage, File info, String hintTag, int which,
633             Pair<Point, Point> sourceDeviceDimensions)
634             throws IOException {
635         if (stage.exists()) {
636             if (multiCrop()) {
637                 // TODO(b/332937943): implement offset adjustment by manually adjusting crop to
638                 //  adhere to device aspect ratio
639                 SparseArray<Rect> cropHints = parseCropHints(info, hintTag);
640                 if (cropHints != null) {
641                     Slog.i(TAG, "Got restored wallpaper; applying which=" + which
642                             + "; cropHints = " + cropHints);
643                     try (FileInputStream in = new FileInputStream(stage)) {
644                         mWallpaperManager.setStreamWithCrops(in, cropHints, true, which);
645                     }
646                     // And log the success
647                     if ((which & FLAG_SYSTEM) > 0) {
648                         mEventLogger.onSystemImageWallpaperRestored();
649                     }
650                     if ((which & FLAG_LOCK) > 0) {
651                         mEventLogger.onLockImageWallpaperRestored();
652                     }
653                 } else {
654                     logRestoreError(which, ERROR_NO_METADATA);
655                 }
656                 return;
657             }
658             // Parse the restored info file to find the crop hint.  Note that this currently
659             // relies on a priori knowledge of the wallpaper info file schema.
660             Rect cropHint = parseCropHint(info, hintTag);
661             if (cropHint != null) {
662                 Slog.i(TAG, "Got restored wallpaper; applying which=" + which
663                         + "; cropHint = " + cropHint);
664                 try (FileInputStream in = new FileInputStream(stage)) {
665 
666                     if (sourceDeviceDimensions != null && sourceDeviceDimensions.first != null) {
667                         BitmapFactory.Options options = new BitmapFactory.Options();
668                         options.inJustDecodeBounds = true;
669                         ParcelFileDescriptor pdf = ParcelFileDescriptor.open(stage, MODE_READ_ONLY);
670                         BitmapFactory.decodeFileDescriptor(pdf.getFileDescriptor(),
671                                 null, options);
672                         Point bitmapSize = new Point(options.outWidth, options.outHeight);
673                         Point sourceDeviceSize = new Point(sourceDeviceDimensions.first.x,
674                                 sourceDeviceDimensions.first.y);
675                         Point targetDeviceDimensions = getScreenDimensions();
676 
677                         // TODO: for now we handle only the case where the target device has smaller
678                         // aspect ratio than the source device i.e. the target device is more narrow
679                         // than the source device
680                         if (isTargetMoreNarrowThanSource(targetDeviceDimensions,
681                                 sourceDeviceSize)) {
682                             Rect adjustedCrop = findNewCropfromOldCrop(cropHint,
683                                     sourceDeviceDimensions.first, true, targetDeviceDimensions,
684                                     bitmapSize, true);
685 
686                             cropHint.set(adjustedCrop);
687                         }
688                     }
689 
690                     mWallpaperManager.setStream(in, cropHint.isEmpty() ? null : cropHint,
691                             true, which);
692 
693                     // And log the success
694                     if ((which & FLAG_SYSTEM) > 0) {
695                         mEventLogger.onSystemImageWallpaperRestored();
696                     }
697                     if ((which & FLAG_LOCK) > 0) {
698                         mEventLogger.onLockImageWallpaperRestored();
699                     }
700                 }
701             } else {
702                 logRestoreError(which, ERROR_NO_METADATA);
703             }
704         } else {
705             Slog.d(TAG, "Restore data doesn't exist for file " + stage.getPath());
706             logRestoreErrorIfNoLiveComponent(which, ERROR_NO_WALLPAPER);
707         }
708     }
709 
710     /**
711      * This method computes the crop of the stored wallpaper to preserve its center point as the
712      * user had set it in the previous device.
713      *
714      * The algorithm involves first computing the original crop of the user (without parallax). Then
715      * manually adjusting the user's original crop to respect the current device's aspect ratio
716      * (thereby preserving the center point). Then finally, adding any leftover image real-estate
717      * (i.e. space left over on the horizontal axis) to add parallax effect. Parallax is only added
718      * if was present in the old device's settings.
719      */
findNewCropfromOldCrop(Rect oldCrop, Point oldDisplaySize, boolean oldRtl, Point newDisplaySize, Point bitmapSize, boolean newRtl)720     private Rect findNewCropfromOldCrop(Rect oldCrop, Point oldDisplaySize, boolean oldRtl,
721             Point newDisplaySize, Point bitmapSize, boolean newRtl) {
722         Rect cropWithoutParallax = withoutParallax(oldCrop, oldDisplaySize, oldRtl, bitmapSize);
723         oldCrop = oldCrop.isEmpty() ? new Rect(0, 0, bitmapSize.x, bitmapSize.y) : oldCrop;
724         float oldParallaxAmount = ((float) oldCrop.width() / cropWithoutParallax.width()) - 1;
725 
726         Rect newCropWithSameCenterWithoutParallax = sameCenter(newDisplaySize, bitmapSize,
727                 cropWithoutParallax);
728 
729         Rect newCrop = newCropWithSameCenterWithoutParallax;
730 
731         // calculate the amount of left-over space there is in the image after adjusting the crop
732         // from the above operation i.e. in a rtl configuration, this is the remaining space in the
733         // image after subtracting the new crop's right edge coordinate from the image itself, and
734         // for ltr, its just the new crop's left edge coordinate (as it's the distance from the
735         // beginning of the image)
736         int widthAvailableForParallaxOnTheNewDevice =
737                 (newRtl) ? newCrop.left : bitmapSize.x - newCrop.right;
738 
739         // calculate relatively how much this available space is as a fraction of the total cropped
740         // image
741         float availableParallaxAmount =
742                 (float) widthAvailableForParallaxOnTheNewDevice / newCrop.width();
743 
744         float minAcceptableParallax = Math.min(DEFAULT_ACCEPTABLE_PARALLAX, oldParallaxAmount);
745 
746         if (DEBUG) {
747             Slog.d(TAG, "- cropWithoutParallax: " + cropWithoutParallax);
748             Slog.d(TAG, "- oldParallaxAmount: " + oldParallaxAmount);
749             Slog.d(TAG, "- newCropWithSameCenterWithoutParallax: "
750                     + newCropWithSameCenterWithoutParallax);
751             Slog.d(TAG, "- widthAvailableForParallaxOnTheNewDevice: "
752                     + widthAvailableForParallaxOnTheNewDevice);
753             Slog.d(TAG, "- availableParallaxAmount: " + availableParallaxAmount);
754             Slog.d(TAG, "- minAcceptableParallax: " + minAcceptableParallax);
755             Slog.d(TAG, "- oldCrop: " + oldCrop);
756             Slog.d(TAG, "- oldDisplaySize: " + oldDisplaySize);
757             Slog.d(TAG, "- oldRtl: " + oldRtl);
758             Slog.d(TAG, "- newDisplaySize: " + newDisplaySize);
759             Slog.d(TAG, "- bitmapSize: " + bitmapSize);
760             Slog.d(TAG, "- newRtl: " + newRtl);
761         }
762         if (availableParallaxAmount >= minAcceptableParallax) {
763             // but in any case, don't put more parallax than the amount of the old device
764             float parallaxToAdd = Math.min(availableParallaxAmount, oldParallaxAmount);
765 
766             int widthToAddForParallax = (int) (newCrop.width() * parallaxToAdd);
767             if (DEBUG) {
768                 Slog.d(TAG, "- parallaxToAdd: " + parallaxToAdd);
769                 Slog.d(TAG, "- widthToAddForParallax: " + widthToAddForParallax);
770             }
771             if (newRtl) {
772                 newCrop.left -= widthToAddForParallax;
773             } else {
774                 newCrop.right += widthToAddForParallax;
775             }
776         }
777         return newCrop;
778     }
779 
780     /**
781      * This method computes the original crop of the user without parallax.
782      *
783      * NOTE: When the user sets the wallpaper with a specific crop, there may additional image added
784      * to the crop to support parallax. In order to determine the user's actual crop the parallax
785      * must be removed if it exists.
786      */
withoutParallax(Rect crop, Point displaySize, boolean rtl, Point bitmapSize)787     Rect withoutParallax(Rect crop, Point displaySize, boolean rtl, Point bitmapSize) {
788         // in the case an image's crop is not set, we assume the image itself is cropped
789         if (crop.isEmpty()) {
790             crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
791         }
792 
793         if (DEBUG) {
794             Slog.w(TAG, "- crop: " + crop);
795         }
796 
797         Rect adjustedCrop = new Rect(crop);
798         float suggestedDisplayRatio = (float) displaySize.x / displaySize.y;
799 
800         // here we calculate the width of the wallpaper image such that it has the same aspect ratio
801         // as the given display i.e. the width of the image on a single page of the device without
802         // parallax (i.e. displaySize will correspond to the display the crop was originally set on)
803         int wallpaperWidthWithoutParallax = (int) (0.5f + (float) displaySize.x * crop.height()
804                 / displaySize.y);
805         // subtracting wallpaperWidthWithoutParallax from the wallpaper crop gives the amount of
806         // parallax added
807         int widthToRemove = Math.max(0, crop.width() - wallpaperWidthWithoutParallax);
808 
809         if (DEBUG) {
810             Slog.d(TAG, "- adjustedCrop: " + adjustedCrop);
811             Slog.d(TAG, "- suggestedDisplayRatio: " + suggestedDisplayRatio);
812             Slog.d(TAG, "- wallpaperWidthWithoutParallax: " + wallpaperWidthWithoutParallax);
813             Slog.d(TAG, "- widthToRemove: " + widthToRemove);
814         }
815         if (rtl) {
816             adjustedCrop.left += widthToRemove;
817         } else {
818             adjustedCrop.right -= widthToRemove;
819         }
820 
821         if (DEBUG) {
822             Slog.d(TAG, "- adjustedCrop: " + crop);
823         }
824         return adjustedCrop;
825     }
826 
827     /**
828      * This method computes a new crop based on the given crop in order to preserve the center point
829      * of the given crop on the provided displaySize. This is only for the case where the device
830      * displaySize has a smaller aspect ratio than the cropped image.
831      *
832      * NOTE: If the width to height ratio is less in the device display than cropped image
833      * this means the aspect ratios are off and there will be distortions in the image
834      * if the image is applied to the current display (i.e. the image will be skewed ->
835      * pixels in the image will not align correctly with the same pixels in the image that are
836      * above them)
837      */
sameCenter(Point displaySize, Point bitmapSize, Rect crop)838     Rect sameCenter(Point displaySize, Point bitmapSize, Rect crop) {
839 
840         // in the case an image's crop is not set, we assume the image itself is cropped
841         if (crop.isEmpty()) {
842             crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
843         }
844 
845         float screenRatio = (float) displaySize.x / displaySize.y;
846         float cropRatio = (float) crop.width() / crop.height();
847 
848         Rect adjustedCrop = new Rect(crop);
849 
850         if (screenRatio < cropRatio) {
851             // the screen is more narrow than the image, and as such, the image will need to be
852             // zoomed in till it fits in the vertical axis. Due to this, we need to manually adjust
853             // the image's crop in order for it to fit into the screen without having the framework
854             // do it (since the framework left aligns the image after zooming)
855 
856             // Calculate the height of the adjusted wallpaper crop so it respects the aspect ratio
857             // of the device. To calculate the height, we will use the width of the current crop.
858             // This is so we find the largest height possible which also respects the device aspect
859             // ratio.
860             int heightToAdd = (int) (0.5f + crop.width() / screenRatio - crop.height());
861 
862             // Calculate how much extra image space available that can be used to adjust
863             // the crop. If this amount is less than heightToAdd, from above, then that means we
864             // can't use heightToAdd. Instead we will need to use the maximum possible height, which
865             // is the height of the original bitmap. NOTE: the bitmap height may be different than
866             // the crop.
867             // since there is no guarantee to have height available on both sides
868             // (e.g. the available height might be fully at the bottom), grab the minimum
869             int availableHeight = 2 * Math.min(crop.top, bitmapSize.y - crop.bottom);
870             int actualHeightToAdd = Math.min(heightToAdd, availableHeight);
871 
872             // half of the additional height is added to the top and bottom of the crop
873             adjustedCrop.top -= actualHeightToAdd / 2 + actualHeightToAdd % 2;
874             adjustedCrop.bottom += actualHeightToAdd / 2;
875 
876             // Calculate the width of the adjusted crop. Initially we used the fixed width of the
877             // crop to calculate the heightToAdd, but since this height may be invalid (based on
878             // the calculation above) we calculate the width again instead of using the fixed width,
879             // using the adjustedCrop's updated height.
880             int widthToRemove = (int) (0.5f + crop.width() - adjustedCrop.height() * screenRatio);
881 
882             // half of the additional width is subtracted from the left and right side of the crop
883             int widthToRemoveLeft = widthToRemove / 2;
884             int widthToRemoveRight = widthToRemove / 2 + widthToRemove % 2;
885 
886             adjustedCrop.left += widthToRemoveLeft;
887             adjustedCrop.right -= widthToRemoveRight;
888 
889             if (DEBUG) {
890                 Slog.d(TAG, "cropRatio: " + cropRatio);
891                 Slog.d(TAG, "screenRatio: " + screenRatio);
892                 Slog.d(TAG, "heightToAdd: " + heightToAdd);
893                 Slog.d(TAG, "actualHeightToAdd: " + actualHeightToAdd);
894                 Slog.d(TAG, "availableHeight: " + availableHeight);
895                 Slog.d(TAG, "widthToRemove: " + widthToRemove);
896                 Slog.d(TAG, "adjustedCrop: " + adjustedCrop);
897             }
898 
899             return adjustedCrop;
900         }
901 
902         return adjustedCrop;
903     }
904 
isTargetMoreNarrowThanSource(Point targetDisplaySize, Point srcDisplaySize)905     private boolean isTargetMoreNarrowThanSource(Point targetDisplaySize, Point srcDisplaySize) {
906         float targetScreenRatio = (float) targetDisplaySize.x / targetDisplaySize.y;
907         float srcScreenRatio = (float) srcDisplaySize.x / srcDisplaySize.y;
908 
909         return (targetScreenRatio < srcScreenRatio);
910     }
911 
logRestoreErrorIfNoLiveComponent(int which, String error)912     private void logRestoreErrorIfNoLiveComponent(int which, String error) {
913         if (mSystemHasLiveComponent) {
914             return;
915         }
916         logRestoreError(which, error);
917     }
918 
logRestoreError(int which, String error)919     private void logRestoreError(int which, String error) {
920         if ((which & FLAG_SYSTEM) == FLAG_SYSTEM) {
921             mEventLogger.onSystemImageWallpaperRestoreFailed(error);
922         }
923         if ((which & FLAG_LOCK) == FLAG_LOCK) {
924             mEventLogger.onLockImageWallpaperRestoreFailed(error);
925         }
926     }
927 
parseCropHint(File wallpaperInfo, String sectionTag)928     private Rect parseCropHint(File wallpaperInfo, String sectionTag) {
929         Rect cropHint = new Rect();
930         try (FileInputStream stream = new FileInputStream(wallpaperInfo)) {
931             XmlPullParser parser = Xml.resolvePullParser(stream);
932 
933             int type;
934             do {
935                 type = parser.next();
936                 if (type == XmlPullParser.START_TAG) {
937                     String tag = parser.getName();
938                     if (sectionTag.equals(tag)) {
939                         cropHint.left = getAttributeInt(parser, "cropLeft", 0);
940                         cropHint.top = getAttributeInt(parser, "cropTop", 0);
941                         cropHint.right = getAttributeInt(parser, "cropRight", 0);
942                         cropHint.bottom = getAttributeInt(parser, "cropBottom", 0);
943                     }
944                 }
945             } while (type != XmlPullParser.END_DOCUMENT);
946         } catch (Exception e) {
947             // Whoops; can't process the info file at all.  Report failure.
948             Slog.w(TAG, "Failed to parse restored crop: " + e.getMessage());
949             return null;
950         }
951 
952         return cropHint;
953     }
954 
parseCropHints(File wallpaperInfo, String sectionTag)955     private SparseArray<Rect> parseCropHints(File wallpaperInfo, String sectionTag) {
956         SparseArray<Rect> cropHints = new SparseArray<>();
957         try (FileInputStream stream = new FileInputStream(wallpaperInfo)) {
958             XmlPullParser parser = Xml.resolvePullParser(stream);
959             int type;
960             do {
961                 type = parser.next();
962                 if (type != XmlPullParser.START_TAG) continue;
963                 String tag = parser.getName();
964                 if (!sectionTag.equals(tag)) continue;
965                 for (Pair<Integer, String> pair : List.of(
966                         new Pair<>(WallpaperManager.ORIENTATION_PORTRAIT, "Portrait"),
967                         new Pair<>(WallpaperManager.ORIENTATION_LANDSCAPE, "Landscape"),
968                         new Pair<>(WallpaperManager.ORIENTATION_SQUARE_PORTRAIT, "SquarePortrait"),
969                         new Pair<>(WallpaperManager.ORIENTATION_SQUARE_LANDSCAPE,
970                                 "SquareLandscape"))) {
971                     Rect cropHint = new Rect(
972                             getAttributeInt(parser, "cropLeft" + pair.second, 0),
973                             getAttributeInt(parser, "cropTop" + pair.second, 0),
974                             getAttributeInt(parser, "cropRight" + pair.second, 0),
975                             getAttributeInt(parser, "cropBottom" + pair.second, 0));
976                     if (!cropHint.isEmpty()) cropHints.put(pair.first, cropHint);
977                 }
978                 if (cropHints.size() == 0) {
979                     // migration case: the crops per screen orientation are not specified.
980                     // use the old attributes to restore the crop for one screen orientation.
981                     Rect cropHint = new Rect(
982                             getAttributeInt(parser, "cropLeft", 0),
983                             getAttributeInt(parser, "cropTop", 0),
984                             getAttributeInt(parser, "cropRight", 0),
985                             getAttributeInt(parser, "cropBottom", 0));
986                     if (!cropHint.isEmpty()) cropHints.put(ORIENTATION_UNKNOWN, cropHint);
987                 }
988             } while (type != XmlPullParser.END_DOCUMENT);
989         } catch (Exception e) {
990             // Whoops; can't process the info file at all.  Report failure.
991             Slog.w(TAG, "Failed to parse restored crops: " + e.getMessage());
992             return null;
993         }
994         return cropHints;
995     }
996 
parseWallpaperComponent(File wallpaperInfo, String sectionTag)997     private Pair<ComponentName, WallpaperDescription> parseWallpaperComponent(File wallpaperInfo,
998             String sectionTag) {
999         ComponentName name = null;
1000         WallpaperDescription description = null;
1001         try (FileInputStream stream = new FileInputStream(wallpaperInfo)) {
1002             final TypedXmlPullParser parser = Xml.resolvePullParser(stream);
1003 
1004             int type;
1005             do {
1006                 type = parser.next();
1007                 if (type == XmlPullParser.START_TAG) {
1008                     String tag = parser.getName();
1009                     if (sectionTag.equals(tag)) {
1010                         final String parsedName = parser.getAttributeValue(null, "component");
1011                         name = (parsedName != null)
1012                                 ? ComponentName.unflattenFromString(parsedName)
1013                                 : null;
1014                         description = parseWallpaperDescription(parser, name);
1015                         break;
1016                     }
1017                 }
1018             } while (type != XmlPullParser.END_DOCUMENT);
1019         } catch (Exception e) {
1020             // Whoops; can't process the info file at all.  Report failure.
1021             Slog.w(TAG, "Failed to parse restored component: " + e.getMessage());
1022             return new Pair<>(null, null);
1023         }
1024         return new Pair<>(name, description);
1025     }
1026 
1027     // Copied from com.android.server.wallpaper.WallpaperDataParser
parseWallpaperDescription(TypedXmlPullParser parser, ComponentName component)1028     private WallpaperDescription parseWallpaperDescription(TypedXmlPullParser parser,
1029             ComponentName component) throws XmlPullParserException, IOException {
1030 
1031         WallpaperDescription description = null;
1032         int type = parser.next();
1033         if (type == XmlPullParser.START_TAG && "description".equals(parser.getName())) {
1034             // Always read the description if it's there - there may be one from a previous save
1035             // with content handling enabled even if it's enabled now
1036             description = WallpaperDescription.restoreFromXml(parser);
1037             if (liveWallpaperContentHandling()) {
1038                 // null component means that wallpaper was last saved without content handling, so
1039                 // populate description from saved component
1040                 if (description.getComponent() == null) {
1041                     description = description.toBuilder().setComponent(component).build();
1042                 }
1043             }
1044         }
1045         return description;
1046     }
1047 
getAttributeInt(XmlPullParser parser, String name, int defValue)1048     private int getAttributeInt(XmlPullParser parser, String name, int defValue) {
1049         final String value = parser.getAttributeValue(null, name);
1050         return (value == null) ? defValue : Integer.parseInt(value);
1051     }
1052 
1053     @VisibleForTesting
servicePackageExists(ComponentName comp)1054     boolean servicePackageExists(ComponentName comp) {
1055         try {
1056             if (comp != null) {
1057                 final IPackageManager pm = AppGlobals.getPackageManager();
1058                 final PackageInfo info = pm.getPackageInfo(comp.getPackageName(),
1059                         0, getUserId());
1060                 return (info != null);
1061             }
1062         } catch (RemoteException e) {
1063             Slog.e(TAG, "Unable to contact package manager");
1064         }
1065         return false;
1066     }
1067 
1068     /** Unused Key/Value API. */
1069     @Override
onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState)1070     public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
1071             ParcelFileDescriptor newState) throws IOException {
1072         // Intentionally blank
1073     }
1074 
1075     /** Unused Key/Value API. */
1076     @Override
onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)1077     public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
1078             throws IOException {
1079         // Intentionally blank
1080     }
1081 
applyComponentAtInstall(ComponentName componentName, @Nullable WallpaperDescription description, int which)1082     private void applyComponentAtInstall(ComponentName componentName,
1083             @Nullable WallpaperDescription description, int which) {
1084         PackageMonitor packageMonitor = getWallpaperPackageMonitor(componentName, description,
1085                 which);
1086         packageMonitor.register(getBaseContext(), null, UserHandle.ALL, true);
1087     }
1088 
1089     @VisibleForTesting
getWallpaperPackageMonitor(ComponentName componentName, @Nullable WallpaperDescription description, int which)1090     PackageMonitor getWallpaperPackageMonitor(ComponentName componentName,
1091             @Nullable WallpaperDescription description, int which) {
1092         return new PackageMonitor() {
1093             @Override
1094             public void onPackageAdded(String packageName, int uid) {
1095                 if (!isDeviceInRestore()) {
1096                     // We don't want to reapply the wallpaper outside a restore.
1097                     unregister();
1098 
1099                     // We have finished restore and not succeeded, so let's log that as an error.
1100                     WallpaperEventLogger logger = new WallpaperEventLogger(
1101                             mBackupManager.getDelayedRestoreLogger());
1102                     if ((which & FLAG_SYSTEM) != 0) {
1103                         logger.onSystemLiveWallpaperRestoreFailed(
1104                                 WallpaperEventLogger.ERROR_LIVE_PACKAGE_NOT_INSTALLED);
1105                     }
1106                     if ((which & FLAG_LOCK) != 0) {
1107                         logger.onLockLiveWallpaperRestoreFailed(
1108                                 WallpaperEventLogger.ERROR_LIVE_PACKAGE_NOT_INSTALLED);
1109                     }
1110                     mBackupManager.reportDelayedRestoreResult(logger.getBackupRestoreLogger());
1111 
1112                     return;
1113                 }
1114 
1115                 boolean useDescription = (liveWallpaperContentHandling() && description != null
1116                         && description.getComponent() != null);
1117                 if (useDescription && description.getComponent().getPackageName().equals(
1118                         packageName)) {
1119                     Slog.d(TAG, "Applying description " + description);
1120                     boolean success = mWallpaperManager.setWallpaperComponentWithDescription(
1121                             description, which);
1122                     WallpaperEventLogger logger = new WallpaperEventLogger(
1123                             mBackupManager.getDelayedRestoreLogger());
1124                     if (success) {
1125                         if ((which & FLAG_SYSTEM) != 0) {
1126                             logger.onSystemLiveWallpaperRestoredWithDescription(description);
1127                         }
1128                         if ((which & FLAG_LOCK) != 0) {
1129                             logger.onLockLiveWallpaperRestoredWithDescription(description);
1130                         }
1131                     } else {
1132                         if ((which & FLAG_SYSTEM) != 0) {
1133                             logger.onSystemLiveWallpaperRestoreFailed(
1134                                     WallpaperEventLogger.ERROR_SET_DESCRIPTION_EXCEPTION);
1135                         }
1136                         if ((which & FLAG_LOCK) != 0) {
1137                             logger.onLockLiveWallpaperRestoreFailed(
1138                                     WallpaperEventLogger.ERROR_SET_DESCRIPTION_EXCEPTION);
1139                         }
1140                     }
1141                 } else if (componentName.getPackageName().equals(packageName)) {
1142                     Slog.d(TAG, "Applying component " + componentName);
1143                     boolean success = mWallpaperManager.setWallpaperComponentWithFlags(
1144                             componentName, which);
1145                     WallpaperEventLogger logger = new WallpaperEventLogger(
1146                             mBackupManager.getDelayedRestoreLogger());
1147                     if (success) {
1148                         if ((which & FLAG_SYSTEM) != 0) {
1149                             logger.onSystemLiveWallpaperRestored(componentName);
1150                         }
1151                         if ((which & FLAG_LOCK) != 0) {
1152                             logger.onLockLiveWallpaperRestored(componentName);
1153                         }
1154                     } else {
1155                         if ((which & FLAG_SYSTEM) != 0) {
1156                             logger.onSystemLiveWallpaperRestoreFailed(
1157                                     WallpaperEventLogger.ERROR_SET_COMPONENT_EXCEPTION);
1158                         }
1159                         if ((which & FLAG_LOCK) != 0) {
1160                             logger.onLockLiveWallpaperRestoreFailed(
1161                                     WallpaperEventLogger.ERROR_SET_COMPONENT_EXCEPTION);
1162                         }
1163                     }
1164                     // We're only expecting to restore the wallpaper component once.
1165                     unregister();
1166                     mBackupManager.reportDelayedRestoreResult(logger.getBackupRestoreLogger());
1167                 }
1168             }
1169         };
1170     }
1171 
1172     /**
1173      * This method retrieves the dimensions of the largest display of the device
1174      *
1175      * @return a @{Point} object that contains the dimensions of the largest display on the device
1176      */
1177     private Point getScreenDimensions() {
1178         Point largetDimensions = null;
1179         int maxArea = 0;
1180 
1181         for (Display display : getInternalDisplays()) {
1182             Point displaySize = getRealSize(display);
1183 
1184             int width = displaySize.x;
1185             int height = displaySize.y;
1186             int area = width * height;
1187 
1188             if (area > maxArea) {
1189                 maxArea = area;
1190                 largetDimensions = displaySize;
1191             }
1192         }
1193 
1194         return largetDimensions;
1195     }
1196 
1197     private Point getRealSize(Display display) {
1198         DisplayInfo displayInfo = new DisplayInfo();
1199         display.getDisplayInfo(displayInfo);
1200         return new Point(displayInfo.logicalWidth, displayInfo.logicalHeight);
1201     }
1202 
1203     /**
1204      * This method returns the smaller display on a multi-display device
1205      *
1206      * @return Display that corresponds to the smaller display on a device or null if ther is only
1207      * one Display on a device
1208      */
1209     private Display getSmallerDisplayIfExists() {
1210         List<Display> internalDisplays = getInternalDisplays();
1211         Point largestDisplaySize = getScreenDimensions();
1212 
1213         // Find the first non-matching internal display
1214         for (Display display : internalDisplays) {
1215             Point displaySize = getRealSize(display);
1216             if (displaySize.x != largestDisplaySize.x || displaySize.y != largestDisplaySize.y) {
1217                 return display;
1218             }
1219         }
1220 
1221         // If no smaller display found, return null, as there is only a single display
1222         return null;
1223     }
1224 
1225     /**
1226      * This method retrieves the collection of Display objects available in the device.
1227      * i.e. non-external displays are ignored
1228      *
1229      * @return list of displays corresponding to each display in the device
1230      */
1231     private List<Display> getInternalDisplays() {
1232         Display[] allDisplays = mDisplayManager.getDisplays(
1233                 DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);
1234 
1235         List<Display> internalDisplays = new ArrayList<>();
1236         for (Display display : allDisplays) {
1237             if (display.getType() == Display.TYPE_INTERNAL) {
1238                 internalDisplays.add(display);
1239             }
1240         }
1241         return internalDisplays;
1242     }
1243 
1244     @VisibleForTesting
1245     boolean isDeviceInRestore() {
1246         try {
1247             boolean isInSetup = Settings.Secure.getInt(getBaseContext().getContentResolver(),
1248                     Settings.Secure.USER_SETUP_COMPLETE) == 0;
1249             boolean isInDeferredSetup = Settings.Secure.getInt(getBaseContext()
1250                             .getContentResolver(),
1251                     Settings.Secure.USER_SETUP_PERSONALIZATION_STATE) ==
1252                     Settings.Secure.USER_SETUP_PERSONALIZATION_STARTED;
1253             return isInSetup || isInDeferredSetup;
1254         } catch (Settings.SettingNotFoundException e) {
1255             Slog.w(TAG, "Failed to check if the user is in restore: " + e);
1256             return false;
1257         }
1258     }
1259 
1260     @VisibleForTesting
1261     void setBackupManagerForTesting(BackupManager backupManager) {
1262         mBackupManager = backupManager;
1263     }
1264 }
1265