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.WallpaperManager.FLAG_LOCK;
20 import static android.app.WallpaperManager.FLAG_SYSTEM;
21 
22 import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_INELIGIBLE;
23 import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_METADATA;
24 import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_WALLPAPER;
25 import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_QUOTA_EXCEEDED;
26 
27 import android.app.AppGlobals;
28 import android.app.WallpaperManager;
29 import android.app.backup.BackupAgent;
30 import android.app.backup.BackupDataInput;
31 import android.app.backup.BackupDataOutput;
32 import android.app.backup.BackupManager;
33 import android.app.backup.BackupRestoreEventLogger.BackupRestoreError;
34 import android.app.backup.FullBackupDataOutput;
35 import android.content.ComponentName;
36 import android.content.Context;
37 import android.content.SharedPreferences;
38 import android.content.pm.IPackageManager;
39 import android.content.pm.PackageInfo;
40 import android.graphics.Rect;
41 import android.os.FileUtils;
42 import android.os.ParcelFileDescriptor;
43 import android.os.RemoteException;
44 import android.os.UserHandle;
45 import android.provider.Settings;
46 import android.util.Slog;
47 import android.util.Xml;
48 
49 import com.android.internal.annotations.VisibleForTesting;
50 import com.android.internal.content.PackageMonitor;
51 
52 import org.xmlpull.v1.XmlPullParser;
53 
54 import java.io.File;
55 import java.io.FileInputStream;
56 import java.io.FileOutputStream;
57 import java.io.IOException;
58 
59 /**
60  * Backs up and restores wallpaper and metadata related to it.
61  *
62  * This agent has its own package because it does full backup as opposed to SystemBackupAgent
63  * which does key/value backup.
64  *
65  * This class stages wallpaper files for backup by copying them into its own directory because of
66  * the following reasons:
67  *
68  * <ul>
69  *     <li>Non-system users don't have permission to read the directory that the system stores
70  *     the wallpaper files in</li>
71  *     <li>{@link BackupAgent} enforces that backed up files must live inside the package's
72  *     {@link Context#getFilesDir()}</li>
73  * </ul>
74  *
75  * There are 3 files to back up:
76  * <ul>
77  *     <li>The "wallpaper info"  file which contains metadata like the crop applied to the
78  *     wallpaper or the live wallpaper component name.</li>
79  *     <li>The "system" wallpaper file.</li>
80  *     <li>An optional "lock" wallpaper, which is shown on the lockscreen instead of the system
81  *     wallpaper if set.</li>
82  * </ul>
83  *
84  * On restore, the metadata file is parsed and {@link WallpaperManager} APIs are used to set the
85  * wallpaper. Note that if there's a live wallpaper, the live wallpaper package name will be
86  * part of the metadata file and the wallpaper will be applied when the package it's installed.
87  */
88 public class WallpaperBackupAgent extends BackupAgent {
89     private static final String TAG = "WallpaperBackup";
90     private static final boolean DEBUG = false;
91 
92     // Names of our local-data stage files
93     @VisibleForTesting
94     static final String SYSTEM_WALLPAPER_STAGE = "wallpaper-stage";
95     @VisibleForTesting
96     static final String LOCK_WALLPAPER_STAGE = "wallpaper-lock-stage";
97     @VisibleForTesting
98     static final String WALLPAPER_INFO_STAGE = "wallpaper-info-stage";
99 
100     static final String EMPTY_SENTINEL = "empty";
101     static final String QUOTA_SENTINEL = "quota";
102 
103     // Shared preferences constants.
104     static final String PREFS_NAME = "wbprefs.xml";
105     static final String SYSTEM_GENERATION = "system_gen";
106     static final String LOCK_GENERATION = "lock_gen";
107 
108     // If this file exists, it means we exceeded our quota last time
109     private File mQuotaFile;
110     private boolean mQuotaExceeded;
111 
112     private WallpaperManager mWallpaperManager;
113     private WallpaperEventLogger mEventLogger;
114     private BackupManager mBackupManager;
115 
116     private boolean mSystemHasLiveComponent;
117     private boolean mLockHasLiveComponent;
118 
119     @Override
onCreate()120     public void onCreate() {
121         if (DEBUG) {
122             Slog.v(TAG, "onCreate()");
123         }
124 
125         mWallpaperManager = getSystemService(WallpaperManager.class);
126 
127         mQuotaFile = new File(getFilesDir(), QUOTA_SENTINEL);
128         mQuotaExceeded = mQuotaFile.exists();
129         if (DEBUG) {
130             Slog.v(TAG, "quota file " + mQuotaFile.getPath() + " exists=" + mQuotaExceeded);
131         }
132 
133         mBackupManager = new BackupManager(getBaseContext());
134         mEventLogger = new WallpaperEventLogger(mBackupManager, /* wallpaperAgent */ this);
135     }
136 
137     @Override
onFullBackup(FullBackupDataOutput data)138     public void onFullBackup(FullBackupDataOutput data) throws IOException {
139         try {
140             // We always back up this 'empty' file to ensure that the absence of
141             // storable wallpaper imagery still produces a non-empty backup data
142             // stream, otherwise it'd simply be ignored in preflight.
143             final File empty = new File(getFilesDir(), EMPTY_SENTINEL);
144             if (!empty.exists()) {
145                 FileOutputStream touch = new FileOutputStream(empty);
146                 touch.close();
147             }
148             backupFile(empty, data);
149 
150             SharedPreferences sharedPrefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
151 
152             // Check the IDs of the wallpapers that we backed up last time. If they haven't
153             // changed, we won't re-stage them for backup and use the old staged versions to avoid
154             // disk churn.
155             final int lastSysGeneration = sharedPrefs.getInt(SYSTEM_GENERATION, /* defValue= */ -1);
156             final int lastLockGeneration = sharedPrefs.getInt(LOCK_GENERATION, /* defValue= */ -1);
157             final int sysGeneration = mWallpaperManager.getWallpaperId(FLAG_SYSTEM);
158             final int lockGeneration = mWallpaperManager.getWallpaperId(FLAG_LOCK);
159             final boolean sysChanged = (sysGeneration != lastSysGeneration);
160             final boolean lockChanged = (lockGeneration != lastLockGeneration);
161 
162             if (DEBUG) {
163                 Slog.v(TAG, "sysGen=" + sysGeneration + " : sysChanged=" + sysChanged);
164                 Slog.v(TAG, "lockGen=" + lockGeneration + " : lockChanged=" + lockChanged);
165             }
166 
167             // Due to the way image vs live wallpaper backup logic is intermingled, for logging
168             // purposes first check if we have live components for each wallpaper to avoid
169             // over-reporting errors.
170             mSystemHasLiveComponent = mWallpaperManager.getWallpaperInfo(FLAG_SYSTEM) != null;
171             mLockHasLiveComponent = mWallpaperManager.getWallpaperInfo(FLAG_LOCK) != null;
172 
173             backupWallpaperInfoFile(/* sysOrLockChanged= */ sysChanged || lockChanged, data);
174             backupSystemWallpaperFile(sharedPrefs, sysChanged, sysGeneration, data);
175             backupLockWallpaperFileIfItExists(sharedPrefs, lockChanged, lockGeneration, data);
176         } catch (Exception e) {
177             Slog.e(TAG, "Unable to back up wallpaper", e);
178             mEventLogger.onBackupException(e);
179         } finally {
180             // Even if this time we had to back off on attempting to store the lock image
181             // due to exceeding the data quota, try again next time.  This will alternate
182             // between "try both" and "only store the primary image" until either there
183             // is no lock image to store, or the quota is raised, or both fit under the
184             // quota.
185             mQuotaFile.delete();
186         }
187     }
188 
backupWallpaperInfoFile(boolean sysOrLockChanged, FullBackupDataOutput data)189     private void backupWallpaperInfoFile(boolean sysOrLockChanged, FullBackupDataOutput data)
190             throws IOException {
191         final ParcelFileDescriptor wallpaperInfoFd = mWallpaperManager.getWallpaperInfoFile();
192 
193         if (wallpaperInfoFd == null) {
194             Slog.w(TAG, "Wallpaper metadata file doesn't exist");
195             // If we have live components, getting the file to back up somehow failed, so log it
196             // as an error.
197             if (mSystemHasLiveComponent) {
198                 mEventLogger.onSystemLiveWallpaperBackupFailed(ERROR_NO_METADATA);
199             }
200             if (mLockHasLiveComponent) {
201                 mEventLogger.onLockLiveWallpaperBackupFailed(ERROR_NO_METADATA);
202             }
203             return;
204         }
205 
206         final File infoStage = new File(getFilesDir(), WALLPAPER_INFO_STAGE);
207 
208         if (sysOrLockChanged || !infoStage.exists()) {
209             if (DEBUG) Slog.v(TAG, "New wallpaper configuration; copying");
210             copyFromPfdToFileAndClosePfd(wallpaperInfoFd, infoStage);
211         }
212 
213         if (DEBUG) Slog.v(TAG, "Storing wallpaper metadata");
214         backupFile(infoStage, data);
215 
216         // We've backed up the info file which contains the live component, so log it as success
217         if (mSystemHasLiveComponent) {
218             mEventLogger.onSystemLiveWallpaperBackedUp(
219                     mWallpaperManager.getWallpaperInfo(FLAG_SYSTEM));
220         }
221         if (mLockHasLiveComponent) {
222             mEventLogger.onLockLiveWallpaperBackedUp(mWallpaperManager.getWallpaperInfo(FLAG_LOCK));
223         }
224     }
225 
backupSystemWallpaperFile(SharedPreferences sharedPrefs, boolean sysChanged, int sysGeneration, FullBackupDataOutput data)226     private void backupSystemWallpaperFile(SharedPreferences sharedPrefs,
227             boolean sysChanged, int sysGeneration, FullBackupDataOutput data) throws IOException {
228         if (!mWallpaperManager.isWallpaperBackupEligible(FLAG_SYSTEM)) {
229             Slog.d(TAG, "System wallpaper ineligible for backup");
230             logSystemImageErrorIfNoLiveComponent(ERROR_INELIGIBLE);
231             return;
232         }
233 
234         final ParcelFileDescriptor systemWallpaperImageFd = mWallpaperManager.getWallpaperFile(
235                 FLAG_SYSTEM,
236                 /* getCropped= */ false);
237 
238         if (systemWallpaperImageFd == null) {
239             Slog.w(TAG, "System wallpaper doesn't exist");
240             logSystemImageErrorIfNoLiveComponent(ERROR_NO_WALLPAPER);
241             return;
242         }
243 
244         final File imageStage = new File(getFilesDir(), SYSTEM_WALLPAPER_STAGE);
245 
246         if (sysChanged || !imageStage.exists()) {
247             if (DEBUG) Slog.v(TAG, "New system wallpaper; copying");
248             copyFromPfdToFileAndClosePfd(systemWallpaperImageFd, imageStage);
249         }
250 
251         if (DEBUG) Slog.v(TAG, "Storing system wallpaper image");
252         backupFile(imageStage, data);
253         sharedPrefs.edit().putInt(SYSTEM_GENERATION, sysGeneration).apply();
254         mEventLogger.onSystemImageWallpaperBackedUp();
255     }
256 
logSystemImageErrorIfNoLiveComponent(@ackupRestoreError String error)257     private void logSystemImageErrorIfNoLiveComponent(@BackupRestoreError String error) {
258         if (mSystemHasLiveComponent) {
259             return;
260         }
261         mEventLogger.onSystemImageWallpaperBackupFailed(error);
262     }
263 
264 
backupLockWallpaperFileIfItExists(SharedPreferences sharedPrefs, boolean lockChanged, int lockGeneration, FullBackupDataOutput data)265     private void backupLockWallpaperFileIfItExists(SharedPreferences sharedPrefs,
266             boolean lockChanged, int lockGeneration, FullBackupDataOutput data) throws IOException {
267         final File lockImageStage = new File(getFilesDir(), LOCK_WALLPAPER_STAGE);
268 
269         // This means there's no lock wallpaper set by the user.
270         if (lockGeneration == -1) {
271             if (lockChanged && lockImageStage.exists()) {
272                 if (DEBUG) Slog.v(TAG, "Removed lock wallpaper; deleting");
273                 lockImageStage.delete();
274             }
275             Slog.d(TAG, "No lockscreen wallpaper set, add nothing to backup");
276             sharedPrefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply();
277             logLockImageErrorIfNoLiveComponent(ERROR_NO_WALLPAPER);
278             return;
279         }
280 
281         if (!mWallpaperManager.isWallpaperBackupEligible(FLAG_LOCK)) {
282             Slog.d(TAG, "Lock screen wallpaper ineligible for backup");
283             logLockImageErrorIfNoLiveComponent(ERROR_INELIGIBLE);
284             return;
285         }
286 
287         final ParcelFileDescriptor lockWallpaperFd = mWallpaperManager.getWallpaperFile(
288                 FLAG_LOCK, /* getCropped= */ false);
289 
290         // If we get to this point, that means lockGeneration != -1 so there's a lock wallpaper
291         // set, but we can't find it.
292         if (lockWallpaperFd == null) {
293             Slog.w(TAG, "Lock wallpaper doesn't exist");
294             logLockImageErrorIfNoLiveComponent(ERROR_NO_WALLPAPER);
295             return;
296         }
297 
298         if (mQuotaExceeded) {
299             Slog.w(TAG, "Not backing up lock screen wallpaper. Quota was exceeded last time");
300             logLockImageErrorIfNoLiveComponent(ERROR_QUOTA_EXCEEDED);
301             return;
302         }
303 
304         if (lockChanged || !lockImageStage.exists()) {
305             if (DEBUG) Slog.v(TAG, "New lock wallpaper; copying");
306             copyFromPfdToFileAndClosePfd(lockWallpaperFd, lockImageStage);
307         }
308 
309         if (DEBUG) Slog.v(TAG, "Storing lock wallpaper image");
310         backupFile(lockImageStage, data);
311         sharedPrefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply();
312         mEventLogger.onLockImageWallpaperBackedUp();
313     }
314 
logLockImageErrorIfNoLiveComponent(@ackupRestoreError String error)315     private void logLockImageErrorIfNoLiveComponent(@BackupRestoreError String error) {
316         if (mLockHasLiveComponent) {
317             return;
318         }
319         mEventLogger.onLockImageWallpaperBackupFailed(error);
320     }
321 
322     /**
323      * Copies the contents of the given {@code pfd} to the given {@code file}.
324      *
325      * All resources used in the process including the {@code pfd} will be closed.
326      */
copyFromPfdToFileAndClosePfd(ParcelFileDescriptor pfd, File file)327     private static void copyFromPfdToFileAndClosePfd(ParcelFileDescriptor pfd, File file)
328             throws IOException {
329         try (ParcelFileDescriptor.AutoCloseInputStream inputStream =
330                      new ParcelFileDescriptor.AutoCloseInputStream(pfd);
331              FileOutputStream outputStream = new FileOutputStream(file)
332         ) {
333             FileUtils.copy(inputStream, outputStream);
334         }
335     }
336 
337     @VisibleForTesting
338     // fullBackupFile is final, so we intercept backups here in tests.
backupFile(File file, FullBackupDataOutput data)339     protected void backupFile(File file, FullBackupDataOutput data) {
340         fullBackupFile(file, data);
341     }
342 
343     @Override
onQuotaExceeded(long backupDataBytes, long quotaBytes)344     public void onQuotaExceeded(long backupDataBytes, long quotaBytes) {
345         Slog.i(TAG, "Quota exceeded (" + backupDataBytes + " vs " + quotaBytes + ')');
346         try (FileOutputStream f = new FileOutputStream(mQuotaFile)) {
347             f.write(0);
348         } catch (Exception e) {
349             Slog.w(TAG, "Unable to record quota-exceeded: " + e.getMessage());
350         }
351     }
352 
353     // We use the default onRestoreFile() implementation that will recreate our stage files,
354     // then post-process in onRestoreFinished() to apply the new wallpaper.
355     @Override
onRestoreFinished()356     public void onRestoreFinished() {
357         Slog.v(TAG, "onRestoreFinished()");
358         final File filesDir = getFilesDir();
359         final File infoStage = new File(filesDir, WALLPAPER_INFO_STAGE);
360         final File imageStage = new File(filesDir, SYSTEM_WALLPAPER_STAGE);
361         final File lockImageStage = new File(filesDir, LOCK_WALLPAPER_STAGE);
362         boolean lockImageStageExists = lockImageStage.exists();
363 
364         try {
365             // First parse the live component name so that we know for logging if we care about
366             // logging errors with the image restore.
367             ComponentName wpService = parseWallpaperComponent(infoStage, "wp");
368             mSystemHasLiveComponent = wpService != null;
369 
370             ComponentName kwpService = null;
371             boolean lockscreenLiveWallpaper = mWallpaperManager.isLockscreenLiveWallpaperEnabled();
372             if (lockscreenLiveWallpaper) {
373                 kwpService = parseWallpaperComponent(infoStage, "kwp");
374             }
375             mLockHasLiveComponent = kwpService != null;
376             boolean separateLockWallpaper = mLockHasLiveComponent || lockImageStage.exists();
377 
378             // if there's no separate lock wallpaper, apply the system wallpaper to both screens.
379             final int sysWhich = separateLockWallpaper ? FLAG_SYSTEM : FLAG_SYSTEM | FLAG_LOCK;
380 
381             // It is valid for the imagery to be absent; it means that we were not permitted
382             // to back up the original image on the source device, or there was no user-supplied
383             // wallpaper image present.
384             if (!lockscreenLiveWallpaper) restoreFromStage(imageStage, infoStage, "wp", sysWhich);
385             if (lockImageStageExists) {
386                 restoreFromStage(lockImageStage, infoStage, "kwp", FLAG_LOCK);
387             }
388             if (lockscreenLiveWallpaper) restoreFromStage(imageStage, infoStage, "wp", sysWhich);
389 
390             // And reset to the wallpaper service we should be using
391             if (lockscreenLiveWallpaper && mLockHasLiveComponent) {
392                 updateWallpaperComponent(kwpService, false, FLAG_LOCK);
393             }
394             updateWallpaperComponent(wpService, !lockImageStageExists, sysWhich);
395         } catch (Exception e) {
396             Slog.e(TAG, "Unable to restore wallpaper: " + e.getMessage());
397             mEventLogger.onRestoreException(e);
398         } finally {
399             Slog.v(TAG, "Restore finished; clearing backup bookkeeping");
400             infoStage.delete();
401             imageStage.delete();
402             lockImageStage.delete();
403 
404             SharedPreferences prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
405             prefs.edit()
406                     .putInt(SYSTEM_GENERATION, -1)
407                     .putInt(LOCK_GENERATION, -1)
408                     .commit();
409         }
410     }
411 
412     @VisibleForTesting
updateWallpaperComponent(ComponentName wpService, boolean applyToLock, int which)413     void updateWallpaperComponent(ComponentName wpService, boolean applyToLock, int which)
414             throws IOException {
415         boolean lockscreenLiveWallpaper = mWallpaperManager.isLockscreenLiveWallpaperEnabled();
416         if (servicePackageExists(wpService)) {
417             Slog.i(TAG, "Using wallpaper service " + wpService);
418             if (lockscreenLiveWallpaper) {
419                 mWallpaperManager.setWallpaperComponentWithFlags(wpService, which);
420                 if ((which & FLAG_LOCK) != 0) {
421                     mEventLogger.onLockLiveWallpaperRestored(wpService);
422                 }
423                 if ((which & FLAG_SYSTEM) != 0) {
424                     mEventLogger.onSystemLiveWallpaperRestored(wpService);
425                 }
426                 return;
427             }
428             mWallpaperManager.setWallpaperComponent(wpService);
429             if (applyToLock) {
430                 // We have a live wallpaper and no static lock image,
431                 // allow live wallpaper to show "through" on lock screen.
432                 mWallpaperManager.clear(FLAG_LOCK);
433                 mEventLogger.onLockLiveWallpaperRestored(wpService);
434             }
435             mEventLogger.onSystemLiveWallpaperRestored(wpService);
436         } else {
437             // If we've restored a live wallpaper, but the component doesn't exist,
438             // we should log it as an error so we can easily identify the problem
439             // in reports from users
440             if (wpService != null) {
441                 // TODO(b/268471749): Handle delayed case
442                 applyComponentAtInstall(wpService, applyToLock, which);
443                 Slog.w(TAG, "Wallpaper service " + wpService + " isn't available. "
444                         + " Will try to apply later");
445             }
446         }
447     }
448 
restoreFromStage(File stage, File info, String hintTag, int which)449     private void restoreFromStage(File stage, File info, String hintTag, int which)
450             throws IOException {
451         if (stage.exists()) {
452             // Parse the restored info file to find the crop hint.  Note that this currently
453             // relies on a priori knowledge of the wallpaper info file schema.
454             Rect cropHint = parseCropHint(info, hintTag);
455             if (cropHint != null) {
456                 Slog.i(TAG, "Got restored wallpaper; applying which=" + which
457                         + "; cropHint = " + cropHint);
458                 try (FileInputStream in = new FileInputStream(stage)) {
459                     mWallpaperManager.setStream(in, cropHint.isEmpty() ? null : cropHint, true,
460                             which);
461 
462                     // And log the success
463                     if ((which & FLAG_SYSTEM) > 0) {
464                         mEventLogger.onSystemImageWallpaperRestored();
465                     }
466                     if ((which & FLAG_LOCK) > 0) {
467                         mEventLogger.onLockImageWallpaperRestored();
468                     }
469                 }
470             } else {
471                 logRestoreError(which, ERROR_NO_METADATA);
472             }
473         } else {
474             Slog.d(TAG, "Restore data doesn't exist for file " + stage.getPath());
475             logRestoreErrorIfNoLiveComponent(which, ERROR_NO_WALLPAPER);
476         }
477     }
478 
logRestoreErrorIfNoLiveComponent(int which, String error)479     private void logRestoreErrorIfNoLiveComponent(int which, String error) {
480         if (mSystemHasLiveComponent) {
481             return;
482         }
483         logRestoreError(which, error);
484     }
485 
logRestoreError(int which, String error)486     private void logRestoreError(int which, String error) {
487         if ((which & FLAG_SYSTEM) == FLAG_SYSTEM) {
488             mEventLogger.onSystemImageWallpaperRestoreFailed(error);
489         }
490         if ((which & FLAG_LOCK) == FLAG_LOCK) {
491             mEventLogger.onLockImageWallpaperRestoreFailed(error);
492         }
493     }
parseCropHint(File wallpaperInfo, String sectionTag)494     private Rect parseCropHint(File wallpaperInfo, String sectionTag) {
495         Rect cropHint = new Rect();
496         try (FileInputStream stream = new FileInputStream(wallpaperInfo)) {
497             XmlPullParser parser = Xml.resolvePullParser(stream);
498 
499             int type;
500             do {
501                 type = parser.next();
502                 if (type == XmlPullParser.START_TAG) {
503                     String tag = parser.getName();
504                     if (sectionTag.equals(tag)) {
505                         cropHint.left = getAttributeInt(parser, "cropLeft", 0);
506                         cropHint.top = getAttributeInt(parser, "cropTop", 0);
507                         cropHint.right = getAttributeInt(parser, "cropRight", 0);
508                         cropHint.bottom = getAttributeInt(parser, "cropBottom", 0);
509                     }
510                 }
511             } while (type != XmlPullParser.END_DOCUMENT);
512         } catch (Exception e) {
513             // Whoops; can't process the info file at all.  Report failure.
514             Slog.w(TAG, "Failed to parse restored crop: " + e.getMessage());
515             return null;
516         }
517 
518         return cropHint;
519     }
520 
parseWallpaperComponent(File wallpaperInfo, String sectionTag)521     private ComponentName parseWallpaperComponent(File wallpaperInfo, String sectionTag) {
522         ComponentName name = null;
523         try (FileInputStream stream = new FileInputStream(wallpaperInfo)) {
524             final XmlPullParser parser = Xml.resolvePullParser(stream);
525 
526             int type;
527             do {
528                 type = parser.next();
529                 if (type == XmlPullParser.START_TAG) {
530                     String tag = parser.getName();
531                     if (sectionTag.equals(tag)) {
532                         final String parsedName = parser.getAttributeValue(null, "component");
533                         name = (parsedName != null)
534                                 ? ComponentName.unflattenFromString(parsedName)
535                                 : null;
536                         break;
537                     }
538                 }
539             } while (type != XmlPullParser.END_DOCUMENT);
540         } catch (Exception e) {
541             // Whoops; can't process the info file at all.  Report failure.
542             Slog.w(TAG, "Failed to parse restored component: " + e.getMessage());
543             return null;
544         }
545         return name;
546     }
547 
getAttributeInt(XmlPullParser parser, String name, int defValue)548     private int getAttributeInt(XmlPullParser parser, String name, int defValue) {
549         final String value = parser.getAttributeValue(null, name);
550         return (value == null) ? defValue : Integer.parseInt(value);
551     }
552 
553     @VisibleForTesting
servicePackageExists(ComponentName comp)554     boolean servicePackageExists(ComponentName comp) {
555         try {
556             if (comp != null) {
557                 final IPackageManager pm = AppGlobals.getPackageManager();
558                 final PackageInfo info = pm.getPackageInfo(comp.getPackageName(),
559                         0, getUserId());
560                 return (info != null);
561             }
562         } catch (RemoteException e) {
563             Slog.e(TAG, "Unable to contact package manager");
564         }
565         return false;
566     }
567 
568     /** Unused Key/Value API. */
569     @Override
onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState)570     public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
571             ParcelFileDescriptor newState) throws IOException {
572         // Intentionally blank
573     }
574 
575     /** Unused Key/Value API. */
576     @Override
onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)577     public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
578             throws IOException {
579         // Intentionally blank
580     }
581 
applyComponentAtInstall(ComponentName componentName, boolean applyToLock, int which)582     private void applyComponentAtInstall(ComponentName componentName, boolean applyToLock,
583             int which) {
584         PackageMonitor packageMonitor = getWallpaperPackageMonitor(
585                 componentName, applyToLock, which);
586         packageMonitor.register(getBaseContext(), null, UserHandle.ALL, true);
587     }
588 
589     @VisibleForTesting
getWallpaperPackageMonitor(ComponentName componentName, boolean applyToLock, int which)590     PackageMonitor getWallpaperPackageMonitor(ComponentName componentName, boolean applyToLock,
591             int which) {
592         return new PackageMonitor() {
593             @Override
594             public void onPackageAdded(String packageName, int uid) {
595                 boolean lockscreenLiveWallpaper =
596                         mWallpaperManager.isLockscreenLiveWallpaperEnabled();
597                 if (!isDeviceInRestore()) {
598                     // We don't want to reapply the wallpaper outside a restore.
599                     unregister();
600 
601                     // We have finished restore and not succeeded, so let's log that as an error.
602                     WallpaperEventLogger logger = new WallpaperEventLogger(
603                             mBackupManager.getDelayedRestoreLogger());
604                     logger.onSystemLiveWallpaperRestoreFailed(
605                             WallpaperEventLogger.ERROR_LIVE_PACKAGE_NOT_INSTALLED);
606                     if (applyToLock) {
607                         logger.onLockLiveWallpaperRestoreFailed(
608                                 WallpaperEventLogger.ERROR_LIVE_PACKAGE_NOT_INSTALLED);
609                     }
610                     mBackupManager.reportDelayedRestoreResult(logger.getBackupRestoreLogger());
611 
612                     return;
613                 }
614 
615                 if (componentName.getPackageName().equals(packageName)) {
616                     Slog.d(TAG, "Applying component " + componentName);
617                     boolean success = lockscreenLiveWallpaper
618                             ? mWallpaperManager.setWallpaperComponentWithFlags(componentName, which)
619                             : mWallpaperManager.setWallpaperComponent(componentName);
620                     WallpaperEventLogger logger = new WallpaperEventLogger(
621                             mBackupManager.getDelayedRestoreLogger());
622                     if (success) {
623                         if (!lockscreenLiveWallpaper || (which & FLAG_SYSTEM) != 0) {
624                             logger.onSystemLiveWallpaperRestored(componentName);
625                         }
626                         if (lockscreenLiveWallpaper && (which & FLAG_LOCK) != 0) {
627                             logger.onLockLiveWallpaperRestored(componentName);
628                         }
629                     } else {
630                         if (!lockscreenLiveWallpaper || (which & FLAG_SYSTEM) != 0) {
631                             logger.onSystemLiveWallpaperRestoreFailed(
632                                     WallpaperEventLogger.ERROR_SET_COMPONENT_EXCEPTION);
633                         }
634                         if (lockscreenLiveWallpaper && (which & FLAG_LOCK) != 0) {
635                             logger.onLockLiveWallpaperRestoreFailed(
636                                     WallpaperEventLogger.ERROR_SET_COMPONENT_EXCEPTION);
637                         }
638                     }
639                     if (applyToLock && !lockscreenLiveWallpaper) {
640                         try {
641                             mWallpaperManager.clear(FLAG_LOCK);
642                             logger.onLockLiveWallpaperRestored(componentName);
643                         } catch (IOException e) {
644                             Slog.w(TAG, "Failed to apply live wallpaper to lock screen: " + e);
645                             logger.onLockLiveWallpaperRestoreFailed(e.getClass().getName());
646                         }
647                     }
648                     // We're only expecting to restore the wallpaper component once.
649                     unregister();
650                     mBackupManager.reportDelayedRestoreResult(logger.getBackupRestoreLogger());
651                 }
652             }
653         };
654     }
655 
656     @VisibleForTesting
657     boolean isDeviceInRestore() {
658         try {
659             boolean isInSetup = Settings.Secure.getInt(getBaseContext().getContentResolver(),
660                     Settings.Secure.USER_SETUP_COMPLETE) == 0;
661             boolean isInDeferredSetup = Settings.Secure.getInt(getBaseContext()
662                             .getContentResolver(),
663                     Settings.Secure.USER_SETUP_PERSONALIZATION_STATE) ==
664                     Settings.Secure.USER_SETUP_PERSONALIZATION_STARTED;
665             return isInSetup || isInDeferredSetup;
666         } catch (Settings.SettingNotFoundException e) {
667             Slog.w(TAG, "Failed to check if the user is in restore: " + e);
668             return false;
669         }
670     }
671 
672     @VisibleForTesting
673     void setBackupManagerForTesting(BackupManager backupManager) {
674         mBackupManager = backupManager;
675     }
676 }