1d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate/*
2d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate * Copyright (C) 2016 The Android Open Source Project
3d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate *
4d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate * Licensed under the Apache License, Version 2.0 (the "License");
5d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate * you may not use this file except in compliance with the License.
6d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate * You may obtain a copy of the License at
7d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate *
8d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate *      http://www.apache.org/licenses/LICENSE-2.0
9d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate *
10d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate * Unless required by applicable law or agreed to in writing, software
11d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate * distributed under the License is distributed on an "AS IS" BASIS,
12d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate * See the License for the specific language governing permissions and
14d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate * limitations under the License.
15d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate */
16d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate
17d7faf53605838487cace9979e577005cc7c8cabcChristopher Tatepackage com.android.wallpaperbackup;
18d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate
19ac482ebb00c8217d86227dde08b33a37b04ece68Christopher Tateimport static android.app.WallpaperManager.FLAG_LOCK;
20ac482ebb00c8217d86227dde08b33a37b04ece68Christopher Tateimport static android.app.WallpaperManager.FLAG_SYSTEM;
21ac482ebb00c8217d86227dde08b33a37b04ece68Christopher Tate
222471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tateimport android.app.AppGlobals;
23d7faf53605838487cace9979e577005cc7c8cabcChristopher Tateimport android.app.WallpaperManager;
24d7faf53605838487cace9979e577005cc7c8cabcChristopher Tateimport android.app.backup.BackupAgent;
25d7faf53605838487cace9979e577005cc7c8cabcChristopher Tateimport android.app.backup.BackupDataInput;
26d7faf53605838487cace9979e577005cc7c8cabcChristopher Tateimport android.app.backup.BackupDataOutput;
27d7faf53605838487cace9979e577005cc7c8cabcChristopher Tateimport android.app.backup.FullBackupDataOutput;
282471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tateimport android.content.ComponentName;
29d7faf53605838487cace9979e577005cc7c8cabcChristopher Tateimport android.content.Context;
30ac482ebb00c8217d86227dde08b33a37b04ece68Christopher Tateimport android.content.SharedPreferences;
312471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tateimport android.content.pm.IPackageManager;
322471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tateimport android.content.pm.PackageInfo;
3361f2d63889696252f10e7a3f2c999df5979a3021Christopher Tateimport android.graphics.Rect;
34d7faf53605838487cace9979e577005cc7c8cabcChristopher Tateimport android.os.Environment;
35ac482ebb00c8217d86227dde08b33a37b04ece68Christopher Tateimport android.os.FileUtils;
36d7faf53605838487cace9979e577005cc7c8cabcChristopher Tateimport android.os.ParcelFileDescriptor;
372471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tateimport android.os.RemoteException;
38d7faf53605838487cace9979e577005cc7c8cabcChristopher Tateimport android.os.UserHandle;
39d7faf53605838487cace9979e577005cc7c8cabcChristopher Tateimport android.util.Slog;
4061f2d63889696252f10e7a3f2c999df5979a3021Christopher Tateimport android.util.Xml;
4161f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate
42f47eff7343866d9cbcd7e9ba49d50b8f601743bdChristopher Tateimport libcore.io.IoUtils;
43f47eff7343866d9cbcd7e9ba49d50b8f601743bdChristopher Tate
4461f2d63889696252f10e7a3f2c999df5979a3021Christopher Tateimport org.xmlpull.v1.XmlPullParser;
45d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate
46d7faf53605838487cace9979e577005cc7c8cabcChristopher Tateimport java.io.File;
4761f2d63889696252f10e7a3f2c999df5979a3021Christopher Tateimport java.io.FileInputStream;
48d7faf53605838487cace9979e577005cc7c8cabcChristopher Tateimport java.io.FileOutputStream;
49d7faf53605838487cace9979e577005cc7c8cabcChristopher Tateimport java.io.IOException;
5061f2d63889696252f10e7a3f2c999df5979a3021Christopher Tateimport java.nio.charset.StandardCharsets;
51d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate
52d7faf53605838487cace9979e577005cc7c8cabcChristopher Tatepublic class WallpaperBackupAgent extends BackupAgent {
53d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate    private static final String TAG = "WallpaperBackup";
54ac482ebb00c8217d86227dde08b33a37b04ece68Christopher Tate    private static final boolean DEBUG = false;
55d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate
56d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate    // NB: must be kept in sync with WallpaperManagerService but has no
5761f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate    // compile-time visibility.
58d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate
59d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate    // Target filenames within the system's wallpaper directory
60d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate    static final String WALLPAPER = "wallpaper_orig";
61bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate    static final String WALLPAPER_LOCK = "wallpaper_lock_orig";
62d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate    static final String WALLPAPER_INFO = "wallpaper_info.xml";
63d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate
64d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate    // Names of our local-data stage files/links
65d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate    static final String IMAGE_STAGE = "wallpaper-stage";
66bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate    static final String LOCK_IMAGE_STAGE = "wallpaper-lock-stage";
67d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate    static final String INFO_STAGE = "wallpaper-info-stage";
68d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate    static final String EMPTY_SENTINEL = "empty";
69bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate    static final String QUOTA_SENTINEL = "quota";
70d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate
71ac482ebb00c8217d86227dde08b33a37b04ece68Christopher Tate    // Not-for-backup bookkeeping
72ac482ebb00c8217d86227dde08b33a37b04ece68Christopher Tate    static final String PREFS_NAME = "wbprefs.xml";
73ac482ebb00c8217d86227dde08b33a37b04ece68Christopher Tate    static final String SYSTEM_GENERATION = "system_gen";
74ac482ebb00c8217d86227dde08b33a37b04ece68Christopher Tate    static final String LOCK_GENERATION = "lock_gen";
75ac482ebb00c8217d86227dde08b33a37b04ece68Christopher Tate
76bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate    private File mWallpaperInfo;        // wallpaper metadata file
77bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate    private File mWallpaperFile;        // primary wallpaper image file
78bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate    private File mLockWallpaperFile;    // lock wallpaper image file
79bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate
80bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate    // If this file exists, it means we exceeded our quota last time
81bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate    private File mQuotaFile;
82bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate    private boolean mQuotaExceeded;
83d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate
84d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate    private WallpaperManager mWm;
85d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate
86d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate    @Override
87d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate    public void onCreate() {
88d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate        if (DEBUG) {
89d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate            Slog.v(TAG, "onCreate()");
90d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate        }
91d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate
92d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate        File wallpaperDir = Environment.getUserSystemDirectory(UserHandle.USER_SYSTEM);
93d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate        mWallpaperInfo = new File(wallpaperDir, WALLPAPER_INFO);
94d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate        mWallpaperFile = new File(wallpaperDir, WALLPAPER);
95bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate        mLockWallpaperFile = new File(wallpaperDir, WALLPAPER_LOCK);
96d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate        mWm = (WallpaperManager) getSystemService(Context.WALLPAPER_SERVICE);
97bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate
98bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate        mQuotaFile = new File(getFilesDir(), QUOTA_SENTINEL);
99bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate        mQuotaExceeded = mQuotaFile.exists();
100bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate        if (DEBUG) {
101bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate            Slog.v(TAG, "quota file " + mQuotaFile.getPath() + " exists=" + mQuotaExceeded);
102bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate        }
103d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate    }
104d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate
105d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate    @Override
106d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate    public void onFullBackup(FullBackupDataOutput data) throws IOException {
107d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate        // To avoid data duplication and disk churn, use links as the stage.
108d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate        final File filesDir = getFilesDir();
109d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate        final File infoStage = new File(filesDir, INFO_STAGE);
110d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate        final File imageStage = new File (filesDir, IMAGE_STAGE);
111bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate        final File lockImageStage = new File (filesDir, LOCK_IMAGE_STAGE);
112d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate        final File empty = new File (filesDir, EMPTY_SENTINEL);
113d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate
114d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate        try {
115d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate            // We always back up this 'empty' file to ensure that the absence of
116d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate            // storable wallpaper imagery still produces a non-empty backup data
117d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate            // stream, otherwise it'd simply be ignored in preflight.
118d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate            FileOutputStream touch = new FileOutputStream(empty);
119d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate            touch.close();
120d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate            fullBackupFile(empty, data);
121d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate
1226172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate            SharedPreferences prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
1236172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate            final int lastSysGeneration = prefs.getInt(SYSTEM_GENERATION, -1);
1246172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate            final int lastLockGeneration = prefs.getInt(LOCK_GENERATION, -1);
1255cb5e89d770f474031e4ab30fb7f24c66333590aChristopher Tate
1266172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate            final int sysGeneration =
1276172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate                    mWm.getWallpaperIdForUser(FLAG_SYSTEM, UserHandle.USER_SYSTEM);
1286172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate            final int lockGeneration =
1296172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate                    mWm.getWallpaperIdForUser(FLAG_LOCK, UserHandle.USER_SYSTEM);
1306172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate            final boolean sysChanged = (sysGeneration != lastSysGeneration);
1316172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate            final boolean lockChanged = (lockGeneration != lastLockGeneration);
1325cb5e89d770f474031e4ab30fb7f24c66333590aChristopher Tate
1336172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate            final boolean sysEligible = mWm.isWallpaperBackupEligible(FLAG_SYSTEM);
1346172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate            final boolean lockEligible = mWm.isWallpaperBackupEligible(FLAG_LOCK);
135ac482ebb00c8217d86227dde08b33a37b04ece68Christopher Tate
136f47eff7343866d9cbcd7e9ba49d50b8f601743bdChristopher Tate                // There might be a latent lock wallpaper file present but unused: don't
137f47eff7343866d9cbcd7e9ba49d50b8f601743bdChristopher Tate                // include it in the backup if that's the case.
138f47eff7343866d9cbcd7e9ba49d50b8f601743bdChristopher Tate                ParcelFileDescriptor lockFd = mWm.getWallpaperFile(FLAG_LOCK, UserHandle.USER_SYSTEM);
139f47eff7343866d9cbcd7e9ba49d50b8f601743bdChristopher Tate                final boolean hasLockWallpaper = (lockFd != null);
140f47eff7343866d9cbcd7e9ba49d50b8f601743bdChristopher Tate                IoUtils.closeQuietly(lockFd);
141f47eff7343866d9cbcd7e9ba49d50b8f601743bdChristopher Tate
1426172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate            if (DEBUG) {
1436172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate                Slog.v(TAG, "sysGen=" + sysGeneration + " : sysChanged=" + sysChanged);
1446172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate                Slog.v(TAG, "lockGen=" + lockGeneration + " : lockChanged=" + lockChanged);
1456172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate                Slog.v(TAG, "sysEligble=" + sysEligible);
1466172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate                Slog.v(TAG, "lockEligible=" + lockEligible);
1476172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate            }
1486172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate
1496172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate            // only back up the wallpapers if we've been told they're eligible
150a611fdc66b1c72c429cc2c8bf70afab9b5825c0bChristopher Tate            if (mWallpaperInfo.exists()) {
1516172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate                if (sysChanged || lockChanged || !infoStage.exists()) {
1526172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate                    if (DEBUG) Slog.v(TAG, "New wallpaper configuration; copying");
1536172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate                    FileUtils.copyFileOrThrow(mWallpaperInfo, infoStage);
1545f829d287f5c8cd72fee1522b8308fa459adc122Christopher Tate                }
155a611fdc66b1c72c429cc2c8bf70afab9b5825c0bChristopher Tate                if (DEBUG) Slog.v(TAG, "Storing wallpaper metadata");
1566172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate                fullBackupFile(infoStage, data);
1576172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate            }
1586172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate            if (sysEligible && mWallpaperFile.exists()) {
1596172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate                if (sysChanged || !imageStage.exists()) {
1606172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate                    if (DEBUG) Slog.v(TAG, "New system wallpaper; copying");
1616172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate                    FileUtils.copyFileOrThrow(mWallpaperFile, imageStage);
1625f829d287f5c8cd72fee1522b8308fa459adc122Christopher Tate                }
163a611fdc66b1c72c429cc2c8bf70afab9b5825c0bChristopher Tate                if (DEBUG) Slog.v(TAG, "Storing system wallpaper image");
1646172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate                fullBackupFile(imageStage, data);
1656172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate                prefs.edit().putInt(SYSTEM_GENERATION, sysGeneration).apply();
1666172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate            }
167bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate
1686172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate            // Don't try to store the lock image if we overran our quota last time
1696172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate            if (lockEligible && hasLockWallpaper && mLockWallpaperFile.exists() && !mQuotaExceeded) {
1706172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate                if (lockChanged || !lockImageStage.exists()) {
1716172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate                    if (DEBUG) Slog.v(TAG, "New lock wallpaper; copying");
1726172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate                    FileUtils.copyFileOrThrow(mLockWallpaperFile, lockImageStage);
173d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate                }
174a611fdc66b1c72c429cc2c8bf70afab9b5825c0bChristopher Tate                if (DEBUG) Slog.v(TAG, "Storing lock wallpaper image");
1756172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate                fullBackupFile(lockImageStage, data);
1766172266154e9071abba2c0aab9ffb31e0ec8c239Christopher Tate                prefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply();
177d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate            }
178d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate        } catch (Exception e) {
1795f829d287f5c8cd72fee1522b8308fa459adc122Christopher Tate            Slog.e(TAG, "Unable to back up wallpaper", e);
180d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate        } finally {
181bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate            // Even if this time we had to back off on attempting to store the lock image
182bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate            // due to exceeding the data quota, try again next time.  This will alternate
183bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate            // between "try both" and "only store the primary image" until either there
184bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate            // is no lock image to store, or the quota is raised, or both fit under the
185bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate            // quota.
186bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate            mQuotaFile.delete();
187bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate        }
188bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate    }
189bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate
190bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate    @Override
191bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate    public void onQuotaExceeded(long backupDataBytes, long quotaBytes) {
192bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate        if (DEBUG) {
193bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate            Slog.i(TAG, "Quota exceeded (" + backupDataBytes + " vs " + quotaBytes + ')');
194bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate        }
195bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate        try (FileOutputStream f = new FileOutputStream(mQuotaFile)) {
196bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate            f.write(0);
197bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate        } catch (Exception e) {
198bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate            Slog.w(TAG, "Unable to record quota-exceeded: " + e.getMessage());
199d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate        }
200d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate    }
201d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate
202d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate    // We use the default onRestoreFile() implementation that will recreate our stage files,
20361f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate    // then post-process in onRestoreFinished() to apply the new wallpaper.
204d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate    @Override
205d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate    public void onRestoreFinished() {
206d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate        if (DEBUG) {
207d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate            Slog.v(TAG, "onRestoreFinished()");
208d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate        }
209bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate        final File filesDir = getFilesDir();
210bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate        final File infoStage = new File(filesDir, INFO_STAGE);
211bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate        final File imageStage = new File (filesDir, IMAGE_STAGE);
212bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate        final File lockImageStage = new File (filesDir, LOCK_IMAGE_STAGE);
213d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate
214f47eff7343866d9cbcd7e9ba49d50b8f601743bdChristopher Tate        // If we restored separate lock imagery, the system wallpaper should be
215f47eff7343866d9cbcd7e9ba49d50b8f601743bdChristopher Tate        // applied as system-only; but if there's no separate lock image, make
216f47eff7343866d9cbcd7e9ba49d50b8f601743bdChristopher Tate        // sure to apply the restored system wallpaper as both.
217f47eff7343866d9cbcd7e9ba49d50b8f601743bdChristopher Tate        final int sysWhich = FLAG_SYSTEM | (lockImageStage.exists() ? 0 : FLAG_LOCK);
218f47eff7343866d9cbcd7e9ba49d50b8f601743bdChristopher Tate
219d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate        try {
220d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate            // It is valid for the imagery to be absent; it means that we were not permitted
221bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate            // to back up the original image on the source device, or there was no user-supplied
222bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate            // wallpaper image present.
223f47eff7343866d9cbcd7e9ba49d50b8f601743bdChristopher Tate            restoreFromStage(imageStage, infoStage, "wp", sysWhich);
224f47eff7343866d9cbcd7e9ba49d50b8f601743bdChristopher Tate            restoreFromStage(lockImageStage, infoStage, "kwp", FLAG_LOCK);
2252471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate
2262471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate            // And reset to the wallpaper service we should be using
2272471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate            ComponentName wpService = parseWallpaperComponent(infoStage, "wp");
2282471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate            if (servicePackageExists(wpService)) {
2292471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate                if (DEBUG) {
2302471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate                    Slog.i(TAG, "Using wallpaper service " + wpService);
2312471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate                }
2322471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate                mWm.setWallpaperComponent(wpService, UserHandle.USER_SYSTEM);
23396eb5b724bab31125e15eef2c926f6648567c3a1Bryan Mawhinney                if (!lockImageStage.exists()) {
23496eb5b724bab31125e15eef2c926f6648567c3a1Bryan Mawhinney                    // We have a live wallpaper and no static lock image,
23596eb5b724bab31125e15eef2c926f6648567c3a1Bryan Mawhinney                    // allow live wallpaper to show "through" on lock screen.
23696eb5b724bab31125e15eef2c926f6648567c3a1Bryan Mawhinney                    mWm.clear(FLAG_LOCK);
23796eb5b724bab31125e15eef2c926f6648567c3a1Bryan Mawhinney                }
2382471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate            } else {
2392471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate                if (DEBUG) {
2402471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate                    Slog.v(TAG, "Can't use wallpaper service " + wpService);
2412471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate                }
2422471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate            }
243d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate        } catch (Exception e) {
244d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate            Slog.e(TAG, "Unable to restore wallpaper: " + e.getMessage());
245d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate        } finally {
246d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate            if (DEBUG) {
247ac482ebb00c8217d86227dde08b33a37b04ece68Christopher Tate                Slog.v(TAG, "Restore finished; clearing backup bookkeeping");
248d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate            }
249d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate            infoStage.delete();
250d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate            imageStage.delete();
251bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate            lockImageStage.delete();
252ac482ebb00c8217d86227dde08b33a37b04ece68Christopher Tate
253ac482ebb00c8217d86227dde08b33a37b04ece68Christopher Tate            SharedPreferences prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
254ac482ebb00c8217d86227dde08b33a37b04ece68Christopher Tate            prefs.edit()
255ac482ebb00c8217d86227dde08b33a37b04ece68Christopher Tate                    .putInt(SYSTEM_GENERATION, -1)
256ac482ebb00c8217d86227dde08b33a37b04ece68Christopher Tate                    .putInt(LOCK_GENERATION, -1)
257ac482ebb00c8217d86227dde08b33a37b04ece68Christopher Tate                    .commit();
258bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate        }
259bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate    }
260bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate
261bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate    private void restoreFromStage(File stage, File info, String hintTag, int which)
262bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate            throws IOException {
263bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate        if (stage.exists()) {
264bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate            // Parse the restored info file to find the crop hint.  Note that this currently
265bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate            // relies on a priori knowledge of the wallpaper info file schema.
266bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate            Rect cropHint = parseCropHint(info, hintTag);
267bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate            if (cropHint != null) {
268ac482ebb00c8217d86227dde08b33a37b04ece68Christopher Tate                Slog.i(TAG, "Got restored wallpaper; applying which=" + which);
269bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate                if (DEBUG) {
270ac482ebb00c8217d86227dde08b33a37b04ece68Christopher Tate                    Slog.v(TAG, "Restored crop hint " + cropHint);
271bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate                }
272bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate                try (FileInputStream in = new FileInputStream(stage)) {
273dd7110db2b54c59ff758e25a26a06178b57c2469Christopher Tate                    mWm.setStream(in, cropHint.isEmpty() ? null : cropHint, true, which);
274bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate                } finally {} // auto-closes 'in'
275bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate            }
276d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate        }
277d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate    }
278d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate
279bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate    private Rect parseCropHint(File wallpaperInfo, String sectionTag) {
28061f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate        Rect cropHint = new Rect();
28161f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate        try (FileInputStream stream = new FileInputStream(wallpaperInfo)) {
28261f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate            XmlPullParser parser = Xml.newPullParser();
28361f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate            parser.setInput(stream, StandardCharsets.UTF_8.name());
28461f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate
28561f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate            int type;
28661f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate            do {
28761f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate                type = parser.next();
28861f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate                if (type == XmlPullParser.START_TAG) {
28961f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate                    String tag = parser.getName();
290bf13ccf4b7d7e50f24f6eefeeaa34b7a09e25ec8Christopher Tate                    if (sectionTag.equals(tag)) {
29161f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate                        cropHint.left = getAttributeInt(parser, "cropLeft", 0);
29261f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate                        cropHint.top = getAttributeInt(parser, "cropTop", 0);
29361f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate                        cropHint.right = getAttributeInt(parser, "cropRight", 0);
29461f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate                        cropHint.bottom = getAttributeInt(parser, "cropBottom", 0);
29561f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate                    }
29661f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate                }
29761f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate            } while (type != XmlPullParser.END_DOCUMENT);
29861f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate        } catch (Exception e) {
29961f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate            // Whoops; can't process the info file at all.  Report failure.
3002471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate            Slog.w(TAG, "Failed to parse restored crop: " + e.getMessage());
30161f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate            return null;
30261f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate        }
30361f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate
30461f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate        return cropHint;
30561f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate    }
30661f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate
3072471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate    private ComponentName parseWallpaperComponent(File wallpaperInfo, String sectionTag) {
3082471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate        ComponentName name = null;
3092471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate        try (FileInputStream stream = new FileInputStream(wallpaperInfo)) {
3102471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate            final XmlPullParser parser = Xml.newPullParser();
3112471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate            parser.setInput(stream, StandardCharsets.UTF_8.name());
3122471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate
3132471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate            int type;
3142471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate            do {
3152471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate                type = parser.next();
3162471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate                if (type == XmlPullParser.START_TAG) {
3172471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate                    String tag = parser.getName();
3182471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate                    if (sectionTag.equals(tag)) {
3192471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate                        final String parsedName = parser.getAttributeValue(null, "component");
3202471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate                        name = (parsedName != null)
3212471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate                                ? ComponentName.unflattenFromString(parsedName)
3222471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate                                : null;
3232471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate                        break;
3242471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate                    }
3252471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate                }
3262471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate            } while (type != XmlPullParser.END_DOCUMENT);
3272471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate        } catch (Exception e) {
3282471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate            // Whoops; can't process the info file at all.  Report failure.
3292471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate            Slog.w(TAG, "Failed to parse restored component: " + e.getMessage());
3302471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate            return null;
3312471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate        }
3322471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate        return name;
3332471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate    }
3342471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate
33561f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate    private int getAttributeInt(XmlPullParser parser, String name, int defValue) {
33661f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate        final String value = parser.getAttributeValue(null, name);
33761f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate        return (value == null) ? defValue : Integer.parseInt(value);
33861f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate    }
33961f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate
3402471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate    private boolean servicePackageExists(ComponentName comp) {
3412471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate        try {
3422471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate            if (comp != null) {
3432471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate                final IPackageManager pm = AppGlobals.getPackageManager();
3442471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate                final PackageInfo info = pm.getPackageInfo(comp.getPackageName(),
3452471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate                        0, UserHandle.USER_SYSTEM);
3462471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate                return (info != null);
3472471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate            }
3482471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate        } catch (RemoteException e) {
3492471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate            Slog.e(TAG, "Unable to contact package manager");
3502471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate        }
3512471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate        return false;
3522471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate    }
3532471a3724f5e95752bbd03ab1dac90de338a7f08Christopher Tate
354d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate    //
35561f2d63889696252f10e7a3f2c999df5979a3021Christopher Tate    // Key/value API: abstract, therefore required; but not used
356d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate    //
357d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate
358d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate    @Override
359d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate    public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
360d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate            ParcelFileDescriptor newState) throws IOException {
361d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate        // Intentionally blank
362d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate    }
363d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate
364d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate    @Override
365d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate    public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
366d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate            throws IOException {
367d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate        // Intentionally blank
368d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate    }
369d7faf53605838487cace9979e577005cc7c8cabcChristopher Tate}