1 /*
2  * Copyright (C) 2023 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.server.wallpaper;
18 
19 import static android.app.WallpaperManager.FLAG_LOCK;
20 import static android.app.WallpaperManager.FLAG_SYSTEM;
21 import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
22 import static android.view.Display.DEFAULT_DISPLAY;
23 
24 import static com.android.server.wallpaper.WallpaperDisplayHelper.DisplayData;
25 import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER;
26 import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_CROP;
27 import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_INFO;
28 import static com.android.server.wallpaper.WallpaperUtils.getWallpaperDir;
29 import static com.android.server.wallpaper.WallpaperUtils.makeWallpaperIdLocked;
30 import static com.android.window.flags.Flags.multiCrop;
31 
32 import android.annotation.Nullable;
33 import android.app.WallpaperColors;
34 import android.app.WallpaperManager;
35 import android.app.WallpaperManager.SetWallpaperFlags;
36 import android.app.backup.WallpaperBackupHelper;
37 import android.content.ComponentName;
38 import android.content.Context;
39 import android.content.pm.PackageManager;
40 import android.content.res.Resources;
41 import android.graphics.Color;
42 import android.graphics.Rect;
43 import android.os.FileUtils;
44 import android.util.Pair;
45 import android.util.Slog;
46 import android.util.SparseArray;
47 import android.util.Xml;
48 
49 import com.android.internal.R;
50 import com.android.internal.annotations.VisibleForTesting;
51 import com.android.internal.util.JournaledFile;
52 import com.android.modules.utils.TypedXmlPullParser;
53 import com.android.modules.utils.TypedXmlSerializer;
54 import com.android.server.wallpaper.WallpaperData.BindSource;
55 
56 import libcore.io.IoUtils;
57 
58 import org.xmlpull.v1.XmlPullParser;
59 import org.xmlpull.v1.XmlPullParserException;
60 
61 import java.io.File;
62 import java.io.FileInputStream;
63 import java.io.FileNotFoundException;
64 import java.io.FileOutputStream;
65 import java.io.IOException;
66 import java.io.InputStream;
67 import java.util.HashMap;
68 import java.util.List;
69 import java.util.Map;
70 
71 /**
72  * Helper for the wallpaper loading / saving / xml parsing
73  * Only meant to be used lock held by WallpaperManagerService
74  * Only meant to be instantiated once by WallpaperManagerService
75  * @hide
76  */
77 public class WallpaperDataParser {
78 
79     private static final String TAG = WallpaperDataParser.class.getSimpleName();
80     private static final boolean DEBUG = false;
81     private final ComponentName mImageWallpaper;
82     private final WallpaperDisplayHelper mWallpaperDisplayHelper;
83     private final WallpaperCropper mWallpaperCropper;
84     private final Context mContext;
85 
WallpaperDataParser(Context context, WallpaperDisplayHelper wallpaperDisplayHelper, WallpaperCropper wallpaperCropper)86     WallpaperDataParser(Context context, WallpaperDisplayHelper wallpaperDisplayHelper,
87             WallpaperCropper wallpaperCropper) {
88         mContext = context;
89         mWallpaperDisplayHelper = wallpaperDisplayHelper;
90         mWallpaperCropper = wallpaperCropper;
91         mImageWallpaper = ComponentName.unflattenFromString(
92                 context.getResources().getString(R.string.image_wallpaper_component));
93     }
94 
makeJournaledFile(int userId)95     private JournaledFile makeJournaledFile(int userId) {
96         final String base = new File(getWallpaperDir(userId), WALLPAPER_INFO).getAbsolutePath();
97         return new JournaledFile(new File(base), new File(base + ".tmp"));
98     }
99 
100     static class WallpaperLoadingResult {
101 
102         private final WallpaperData mSystemWallpaperData;
103 
104         @Nullable
105         private final WallpaperData mLockWallpaperData;
106 
107         private final boolean mSuccess;
108 
WallpaperLoadingResult( WallpaperData systemWallpaperData, WallpaperData lockWallpaperData, boolean success)109         private WallpaperLoadingResult(
110                 WallpaperData systemWallpaperData,
111                 WallpaperData lockWallpaperData,
112                 boolean success) {
113             mSystemWallpaperData = systemWallpaperData;
114             mLockWallpaperData = lockWallpaperData;
115             mSuccess = success;
116         }
117 
getSystemWallpaperData()118         public WallpaperData getSystemWallpaperData() {
119             return mSystemWallpaperData;
120         }
121 
getLockWallpaperData()122         public WallpaperData getLockWallpaperData() {
123             return mLockWallpaperData;
124         }
125 
success()126         public boolean success() {
127             return mSuccess;
128         }
129     }
130 
131     /**
132      * Load the system wallpaper (and the lock wallpaper, if it exists) from disk
133      * @param userId the id of the user for which the wallpaper should be loaded
134      * @param keepDimensionHints if false, parse and set the
135      *                      {@link DisplayData} width and height for the specified userId
136      * @param migrateFromOld whether the current wallpaper is pre-N and needs migration
137      * @param which The wallpaper(s) to load.
138      * @return a {@link WallpaperLoadingResult} object containing the wallpaper data.
139      */
loadSettingsLocked(int userId, boolean keepDimensionHints, boolean migrateFromOld, @SetWallpaperFlags int which)140     public WallpaperLoadingResult loadSettingsLocked(int userId, boolean keepDimensionHints,
141             boolean migrateFromOld, @SetWallpaperFlags int which) {
142         // TODO(b/270726737) remove the "keepDimensionHints" arg when removing the multi crop flag
143         JournaledFile journal = makeJournaledFile(userId);
144         FileInputStream stream = null;
145         File file = journal.chooseForRead();
146 
147         boolean loadSystem = (which & FLAG_SYSTEM) != 0;
148         boolean loadLock = (which & FLAG_LOCK) != 0;
149         WallpaperData wallpaper = null;
150         WallpaperData lockWallpaper = null;
151 
152         if (loadSystem) {
153             // Do this once per boot
154             if (migrateFromOld) migrateFromOld();
155             wallpaper = new WallpaperData(userId, FLAG_SYSTEM);
156             wallpaper.allowBackup = true;
157             if (!wallpaper.cropExists()) {
158                 if (wallpaper.sourceExists()) {
159                     mWallpaperCropper.generateCrop(wallpaper);
160                 } else {
161                     Slog.i(TAG, "No static wallpaper imagery; defaults will be shown");
162                 }
163             }
164         }
165 
166         final DisplayData wpdData = mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
167         boolean success = false;
168 
169         try {
170             stream = new FileInputStream(file);
171             TypedXmlPullParser parser = Xml.resolvePullParser(stream);
172 
173             int type;
174             do {
175                 type = parser.next();
176                 if (type == XmlPullParser.START_TAG) {
177                     String tag = parser.getName();
178                     if (("wp".equals(tag) && loadSystem) || ("kwp".equals(tag) && loadLock)) {
179                         if ("kwp".equals(tag)) {
180                             lockWallpaper = new WallpaperData(userId, FLAG_LOCK);
181                         }
182                         WallpaperData wallpaperToParse =
183                                 "wp".equals(tag) ? wallpaper : lockWallpaper;
184 
185                         if (!multiCrop()) {
186                             parseWallpaperAttributes(parser, wallpaperToParse, keepDimensionHints);
187                         }
188 
189                         String comp = parser.getAttributeValue(null, "component");
190                         wallpaperToParse.nextWallpaperComponent = comp != null
191                                 ? ComponentName.unflattenFromString(comp)
192                                 : null;
193                         if (wallpaperToParse.nextWallpaperComponent == null
194                                 || "android".equals(wallpaperToParse.nextWallpaperComponent
195                                 .getPackageName())) {
196                             wallpaperToParse.nextWallpaperComponent = mImageWallpaper;
197                         }
198 
199                         if (multiCrop()) {
200                             parseWallpaperAttributes(parser, wallpaperToParse, keepDimensionHints);
201                         }
202 
203                         if (DEBUG) {
204                             Slog.v(TAG, "mWidth:" + wpdData.mWidth);
205                             Slog.v(TAG, "mHeight:" + wpdData.mHeight);
206                             Slog.v(TAG, "cropRect:" + wallpaper.cropHint);
207                             Slog.v(TAG, "primaryColors:" + wallpaper.primaryColors);
208                             Slog.v(TAG, "mName:" + wallpaper.name);
209                             Slog.v(TAG, "mNextWallpaperComponent:"
210                                     + wallpaper.nextWallpaperComponent);
211                         }
212                     }
213                 }
214             } while (type != XmlPullParser.END_DOCUMENT);
215             success = true;
216         } catch (FileNotFoundException e) {
217             Slog.w(TAG, "no current wallpaper -- first boot?");
218         } catch (NullPointerException e) {
219             Slog.w(TAG, "failed parsing " + file + " " + e);
220         } catch (NumberFormatException e) {
221             Slog.w(TAG, "failed parsing " + file + " " + e);
222         } catch (XmlPullParserException e) {
223             Slog.w(TAG, "failed parsing " + file + " " + e);
224         } catch (IOException e) {
225             Slog.w(TAG, "failed parsing " + file + " " + e);
226         } catch (IndexOutOfBoundsException e) {
227             Slog.w(TAG, "failed parsing " + file + " " + e);
228         }
229         IoUtils.closeQuietly(stream);
230 
231         mWallpaperDisplayHelper.ensureSaneWallpaperDisplaySize(wpdData, DEFAULT_DISPLAY);
232 
233         if (loadSystem) {
234             if (!success) {
235                 wallpaper.cropHint.set(0, 0, 0, 0);
236                 wpdData.mPadding.set(0, 0, 0, 0);
237                 wallpaper.name = "";
238             } else {
239                 if (wallpaper.wallpaperId <= 0) {
240                     wallpaper.wallpaperId = makeWallpaperIdLocked();
241                     if (DEBUG) {
242                         Slog.w(TAG, "Didn't set wallpaper id in loadSettingsLocked(" + userId
243                                 + "); now " + wallpaper.wallpaperId);
244                     }
245                 }
246             }
247             ensureSaneWallpaperData(wallpaper);
248             wallpaper.mWhich = lockWallpaper != null ? FLAG_SYSTEM : FLAG_SYSTEM | FLAG_LOCK;
249         }
250 
251         if (loadLock) {
252             if (!success) lockWallpaper = null;
253             if (lockWallpaper != null) {
254                 ensureSaneWallpaperData(lockWallpaper);
255                 lockWallpaper.mWhich = FLAG_LOCK;
256             }
257         }
258 
259         return new WallpaperLoadingResult(wallpaper, lockWallpaper, success);
260     }
261 
ensureSaneWallpaperData(WallpaperData wallpaper)262     private void ensureSaneWallpaperData(WallpaperData wallpaper) {
263         // Only overwrite cropHint if the rectangle is invalid.
264         if (wallpaper.cropHint.width() < 0
265                 || wallpaper.cropHint.height() < 0) {
266             wallpaper.cropHint.set(0, 0, 0, 0);
267         }
268     }
269 
270 
migrateFromOld()271     private void migrateFromOld() {
272         // Pre-N, what existed is the one we're now using as the display crop
273         File preNWallpaper = new File(getWallpaperDir(0), WALLPAPER_CROP);
274         // In the very-long-ago, imagery lived with the settings app
275         File originalWallpaper = new File(WallpaperBackupHelper.WALLPAPER_IMAGE_KEY);
276         File newWallpaper = new File(getWallpaperDir(0), WALLPAPER);
277 
278         // Migrations from earlier wallpaper image storage schemas
279         if (preNWallpaper.exists()) {
280             if (!newWallpaper.exists()) {
281                 // we've got the 'wallpaper' crop file but not the nominal source image,
282                 // so do the simple "just take everything" straight copy of legacy data
283                 if (DEBUG) {
284                     Slog.i(TAG, "Migrating wallpaper schema");
285                 }
286                 FileUtils.copyFile(preNWallpaper, newWallpaper);
287             } // else we're in the usual modern case: both source & crop exist
288         } else if (originalWallpaper.exists()) {
289             // VERY old schema; make sure things exist and are in the right place
290             if (DEBUG) {
291                 Slog.i(TAG, "Migrating antique wallpaper schema");
292             }
293             File oldInfo = new File(WallpaperBackupHelper.WALLPAPER_INFO_KEY);
294             if (oldInfo.exists()) {
295                 File newInfo = new File(getWallpaperDir(0), WALLPAPER_INFO);
296                 oldInfo.renameTo(newInfo);
297             }
298 
299             FileUtils.copyFile(originalWallpaper, preNWallpaper);
300             originalWallpaper.renameTo(newWallpaper);
301         }
302     }
303 
304     @VisibleForTesting
parseWallpaperAttributes(TypedXmlPullParser parser, WallpaperData wallpaper, boolean keepDimensionHints)305     void parseWallpaperAttributes(TypedXmlPullParser parser, WallpaperData wallpaper,
306             boolean keepDimensionHints) throws XmlPullParserException {
307         final int id = parser.getAttributeInt(null, "id", -1);
308         if (id != -1) {
309             wallpaper.wallpaperId = id;
310             if (id > WallpaperUtils.getCurrentWallpaperId()) {
311                 WallpaperUtils.setCurrentWallpaperId(id);
312             }
313         } else {
314             wallpaper.wallpaperId = makeWallpaperIdLocked();
315         }
316 
317         Rect legacyCropHint = new Rect(
318                 getAttributeInt(parser, "cropLeft", 0),
319                 getAttributeInt(parser, "cropTop", 0),
320                 getAttributeInt(parser, "cropRight", 0),
321                 getAttributeInt(parser, "cropBottom", 0));
322         Rect totalCropHint = new Rect(
323                 getAttributeInt(parser, "totalCropLeft", 0),
324                 getAttributeInt(parser, "totalCropTop", 0),
325                 getAttributeInt(parser, "totalCropRight", 0),
326                 getAttributeInt(parser, "totalCropBottom", 0));
327         if (multiCrop() && mImageWallpaper.equals(wallpaper.nextWallpaperComponent)) {
328             wallpaper.mCropHints = new SparseArray<>();
329             for (Pair<Integer, String> pair: screenDimensionPairs()) {
330                 Rect cropHint = new Rect(
331                         parser.getAttributeInt(null, "cropLeft" + pair.second, 0),
332                         parser.getAttributeInt(null, "cropTop" + pair.second, 0),
333                         parser.getAttributeInt(null, "cropRight" + pair.second, 0),
334                         parser.getAttributeInt(null, "cropBottom" + pair.second, 0));
335                 if (!cropHint.isEmpty()) wallpaper.mCropHints.put(pair.first, cropHint);
336                 if (!cropHint.isEmpty() && cropHint.equals(legacyCropHint)) {
337                     wallpaper.mOrientationWhenSet = pair.first;
338                 }
339             }
340             if (wallpaper.mCropHints.size() == 0 && totalCropHint.isEmpty()) {
341                 // migration case: the crops per screen orientation are not specified.
342                 if (!legacyCropHint.isEmpty()) {
343                     wallpaper.cropHint.set(legacyCropHint);
344                 }
345             } else {
346                 wallpaper.cropHint.set(totalCropHint);
347             }
348             wallpaper.mSampleSize = parser.getAttributeFloat(null, "sampleSize", 1f);
349         } else if (!multiCrop()) {
350             wallpaper.cropHint.set(legacyCropHint);
351         }
352         final DisplayData wpData = mWallpaperDisplayHelper
353                 .getDisplayDataOrCreate(DEFAULT_DISPLAY);
354         if (!keepDimensionHints && !multiCrop()) {
355             wpData.mWidth = parser.getAttributeInt(null, "width", 0);
356             wpData.mHeight = parser.getAttributeInt(null, "height", 0);
357         }
358         if (!multiCrop()) {
359             wpData.mPadding.left = getAttributeInt(parser, "paddingLeft", 0);
360             wpData.mPadding.top = getAttributeInt(parser, "paddingTop", 0);
361             wpData.mPadding.right = getAttributeInt(parser, "paddingRight", 0);
362             wpData.mPadding.bottom = getAttributeInt(parser, "paddingBottom", 0);
363         }
364         wallpaper.mWallpaperDimAmount = getAttributeFloat(parser, "dimAmount", 0f);
365         BindSource bindSource;
366         try {
367             bindSource = Enum.valueOf(BindSource.class,
368                     getAttributeString(parser, "bindSource", BindSource.UNKNOWN.name()));
369         } catch (IllegalArgumentException | NullPointerException e) {
370             bindSource = BindSource.UNKNOWN;
371         }
372         wallpaper.mBindSource = bindSource;
373         int dimAmountsCount = getAttributeInt(parser, "dimAmountsCount", 0);
374         if (dimAmountsCount > 0) {
375             SparseArray<Float> allDimAmounts = new SparseArray<>(dimAmountsCount);
376             for (int i = 0; i < dimAmountsCount; i++) {
377                 int uid = getAttributeInt(parser, "dimUID" + i, 0);
378                 float dimValue = getAttributeFloat(parser, "dimValue" + i, 0f);
379                 allDimAmounts.put(uid, dimValue);
380             }
381             wallpaper.mUidToDimAmount = allDimAmounts;
382         }
383         int colorsCount = getAttributeInt(parser, "colorsCount", 0);
384         int allColorsCount =  getAttributeInt(parser, "allColorsCount", 0);
385         if (allColorsCount > 0) {
386             Map<Integer, Integer> allColors = new HashMap<>(allColorsCount);
387             for (int i = 0; i < allColorsCount; i++) {
388                 int colorInt = getAttributeInt(parser, "allColorsValue" + i, 0);
389                 int population = getAttributeInt(parser, "allColorsPopulation" + i, 0);
390                 allColors.put(colorInt, population);
391             }
392             int colorHints = getAttributeInt(parser, "colorHints", 0);
393             wallpaper.primaryColors = new WallpaperColors(allColors, colorHints);
394         } else if (colorsCount > 0) {
395             Color primary = null, secondary = null, tertiary = null;
396             for (int i = 0; i < colorsCount; i++) {
397                 Color color = Color.valueOf(getAttributeInt(parser, "colorValue" + i, 0));
398                 if (i == 0) {
399                     primary = color;
400                 } else if (i == 1) {
401                     secondary = color;
402                 } else if (i == 2) {
403                     tertiary = color;
404                 } else {
405                     break;
406                 }
407             }
408             int colorHints = getAttributeInt(parser, "colorHints", 0);
409             wallpaper.primaryColors = new WallpaperColors(primary, secondary, tertiary, colorHints);
410         }
411         wallpaper.name = parser.getAttributeValue(null, "name");
412         wallpaper.allowBackup = parser.getAttributeBoolean(null, "backup", false);
413     }
414 
getAttributeInt(TypedXmlPullParser parser, String name, int defValue)415     private static int getAttributeInt(TypedXmlPullParser parser, String name, int defValue) {
416         return parser.getAttributeInt(null, name, defValue);
417     }
418 
getAttributeFloat(TypedXmlPullParser parser, String name, float defValue)419     private static float getAttributeFloat(TypedXmlPullParser parser, String name, float defValue) {
420         return parser.getAttributeFloat(null, name, defValue);
421     }
422 
getAttributeString(XmlPullParser parser, String name, String defValue)423     private String getAttributeString(XmlPullParser parser, String name, String defValue) {
424         String s = parser.getAttributeValue(null, name);
425         return (s != null) ? s : defValue;
426     }
427 
saveSettingsLocked(int userId, WallpaperData wallpaper, WallpaperData lockWallpaper)428     void saveSettingsLocked(int userId, WallpaperData wallpaper, WallpaperData lockWallpaper) {
429         JournaledFile journal = makeJournaledFile(userId);
430         FileOutputStream fstream = null;
431         try {
432             fstream = new FileOutputStream(journal.chooseForWrite(), false);
433             TypedXmlSerializer out = Xml.resolveSerializer(fstream);
434             out.startDocument(null, true);
435 
436             if (wallpaper != null) {
437                 writeWallpaperAttributes(out, "wp", wallpaper);
438             }
439 
440             if (lockWallpaper != null) {
441                 writeWallpaperAttributes(out, "kwp", lockWallpaper);
442             }
443 
444             out.endDocument();
445 
446             fstream.flush();
447             FileUtils.sync(fstream);
448             fstream.close();
449             journal.commit();
450         } catch (IOException e) {
451             IoUtils.closeQuietly(fstream);
452             journal.rollback();
453         }
454     }
455 
456     @VisibleForTesting
writeWallpaperAttributes(TypedXmlSerializer out, String tag, WallpaperData wallpaper)457     void writeWallpaperAttributes(TypedXmlSerializer out, String tag, WallpaperData wallpaper)
458             throws IllegalArgumentException, IllegalStateException, IOException {
459         if (DEBUG) {
460             Slog.v(TAG, "writeWallpaperAttributes id=" + wallpaper.wallpaperId);
461         }
462         out.startTag(null, tag);
463         out.attributeInt(null, "id", wallpaper.wallpaperId);
464 
465         if (multiCrop() && mImageWallpaper.equals(wallpaper.wallpaperComponent)) {
466             if (wallpaper.mCropHints == null) {
467                 Slog.e(TAG, "cropHints should not be null when saved");
468                 wallpaper.mCropHints = new SparseArray<>();
469             }
470             Rect rectToPutInLegacyCrop = new Rect(wallpaper.cropHint);
471             for (Pair<Integer, String> pair : screenDimensionPairs()) {
472                 Rect cropHint = wallpaper.mCropHints.get(pair.first);
473                 if (cropHint == null) continue;
474                 out.attributeInt(null, "cropLeft" + pair.second, cropHint.left);
475                 out.attributeInt(null, "cropTop" + pair.second, cropHint.top);
476                 out.attributeInt(null, "cropRight" + pair.second, cropHint.right);
477                 out.attributeInt(null, "cropBottom" + pair.second, cropHint.bottom);
478 
479                 // to support back compatibility in B&R, save the crops for one orientation in the
480                 // legacy "cropLeft", "cropTop", "cropRight", "cropBottom" entries
481                 int orientationToPutInLegacyCrop = wallpaper.mOrientationWhenSet;
482                 if (mWallpaperDisplayHelper.isFoldable()) {
483                     int unfoldedOrientation = mWallpaperDisplayHelper
484                             .getUnfoldedOrientation(orientationToPutInLegacyCrop);
485                     if (unfoldedOrientation != ORIENTATION_UNKNOWN) {
486                         orientationToPutInLegacyCrop = unfoldedOrientation;
487                     }
488                 }
489                 if (pair.first == orientationToPutInLegacyCrop) {
490                     rectToPutInLegacyCrop.set(cropHint);
491                 }
492             }
493             out.attributeInt(null, "cropLeft", rectToPutInLegacyCrop.left);
494             out.attributeInt(null, "cropTop", rectToPutInLegacyCrop.top);
495             out.attributeInt(null, "cropRight", rectToPutInLegacyCrop.right);
496             out.attributeInt(null, "cropBottom", rectToPutInLegacyCrop.bottom);
497 
498             out.attributeInt(null, "totalCropLeft", wallpaper.cropHint.left);
499             out.attributeInt(null, "totalCropTop", wallpaper.cropHint.top);
500             out.attributeInt(null, "totalCropRight", wallpaper.cropHint.right);
501             out.attributeInt(null, "totalCropBottom", wallpaper.cropHint.bottom);
502             out.attributeFloat(null, "sampleSize", wallpaper.mSampleSize);
503         } else if (!multiCrop()) {
504             final DisplayData wpdData =
505                     mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
506             out.attributeInt(null, "width", wpdData.mWidth);
507             out.attributeInt(null, "height", wpdData.mHeight);
508             out.attributeInt(null, "cropLeft", wallpaper.cropHint.left);
509             out.attributeInt(null, "cropTop", wallpaper.cropHint.top);
510             out.attributeInt(null, "cropRight", wallpaper.cropHint.right);
511             out.attributeInt(null, "cropBottom", wallpaper.cropHint.bottom);
512             if (wpdData.mPadding.left != 0) {
513                 out.attributeInt(null, "paddingLeft", wpdData.mPadding.left);
514             }
515             if (wpdData.mPadding.top != 0) {
516                 out.attributeInt(null, "paddingTop", wpdData.mPadding.top);
517             }
518             if (wpdData.mPadding.right != 0) {
519                 out.attributeInt(null, "paddingRight", wpdData.mPadding.right);
520             }
521             if (wpdData.mPadding.bottom != 0) {
522                 out.attributeInt(null, "paddingBottom", wpdData.mPadding.bottom);
523             }
524         }
525 
526         out.attributeFloat(null, "dimAmount", wallpaper.mWallpaperDimAmount);
527         out.attribute(null, "bindSource", wallpaper.mBindSource.name());
528         int dimAmountsCount = wallpaper.mUidToDimAmount.size();
529         out.attributeInt(null, "dimAmountsCount", dimAmountsCount);
530         if (dimAmountsCount > 0) {
531             int index = 0;
532             for (int i = 0; i < wallpaper.mUidToDimAmount.size(); i++) {
533                 out.attributeInt(null, "dimUID" + index, wallpaper.mUidToDimAmount.keyAt(i));
534                 out.attributeFloat(null, "dimValue" + index, wallpaper.mUidToDimAmount.valueAt(i));
535                 index++;
536             }
537         }
538 
539         if (wallpaper.primaryColors != null) {
540             int colorsCount = wallpaper.primaryColors.getMainColors().size();
541             out.attributeInt(null, "colorsCount", colorsCount);
542             if (colorsCount > 0) {
543                 for (int i = 0; i < colorsCount; i++) {
544                     final Color wc = wallpaper.primaryColors.getMainColors().get(i);
545                     out.attributeInt(null, "colorValue" + i, wc.toArgb());
546                 }
547             }
548 
549             int allColorsCount = wallpaper.primaryColors.getAllColors().size();
550             out.attributeInt(null, "allColorsCount", allColorsCount);
551             if (allColorsCount > 0) {
552                 int index = 0;
553                 for (Map.Entry<Integer, Integer> entry : wallpaper.primaryColors.getAllColors()
554                         .entrySet()) {
555                     out.attributeInt(null, "allColorsValue" + index, entry.getKey());
556                     out.attributeInt(null, "allColorsPopulation" + index, entry.getValue());
557                     index++;
558                 }
559             }
560 
561             out.attributeInt(null, "colorHints", wallpaper.primaryColors.getColorHints());
562         }
563 
564         out.attribute(null, "name", wallpaper.name);
565         if (wallpaper.wallpaperComponent != null
566                 && !wallpaper.wallpaperComponent.equals(mImageWallpaper)) {
567             out.attribute(null, "component",
568                     wallpaper.wallpaperComponent.flattenToShortString());
569         }
570 
571         if (wallpaper.allowBackup) {
572             out.attributeBoolean(null, "backup", true);
573         }
574 
575         out.endTag(null, tag);
576     }
577 
578     // Restore the named resource bitmap to both source + crop files
restoreNamedResourceLocked(WallpaperData wallpaper)579     boolean restoreNamedResourceLocked(WallpaperData wallpaper) {
580         if (wallpaper.name.length() > 4 && "res:".equals(wallpaper.name.substring(0, 4))) {
581             String resName = wallpaper.name.substring(4);
582 
583             String pkg = null;
584             int colon = resName.indexOf(':');
585             if (colon > 0) {
586                 pkg = resName.substring(0, colon);
587             }
588 
589             String ident = null;
590             int slash = resName.lastIndexOf('/');
591             if (slash > 0) {
592                 ident = resName.substring(slash + 1);
593             }
594 
595             String type = null;
596             if (colon > 0 && slash > 0 && (slash - colon) > 1) {
597                 type = resName.substring(colon + 1, slash);
598             }
599 
600             if (pkg != null && ident != null && type != null) {
601                 int resId = -1;
602                 InputStream res = null;
603                 FileOutputStream fos = null;
604                 FileOutputStream cos = null;
605                 try {
606                     Context c = mContext.createPackageContext(pkg, Context.CONTEXT_RESTRICTED);
607                     Resources r = c.getResources();
608                     resId = r.getIdentifier(resName, null, null);
609                     if (resId == 0) {
610                         Slog.e(TAG, "couldn't resolve identifier pkg=" + pkg + " type=" + type
611                                 + " ident=" + ident);
612                         return false;
613                     }
614 
615                     res = r.openRawResource(resId);
616                     if (wallpaper.getWallpaperFile().exists()) {
617                         wallpaper.getWallpaperFile().delete();
618                         wallpaper.getCropFile().delete();
619                     }
620                     fos = new FileOutputStream(wallpaper.getWallpaperFile());
621                     cos = new FileOutputStream(wallpaper.getCropFile());
622 
623                     byte[] buffer = new byte[32768];
624                     int amt;
625                     while ((amt = res.read(buffer)) > 0) {
626                         fos.write(buffer, 0, amt);
627                         cos.write(buffer, 0, amt);
628                     }
629                     // mWallpaperObserver will notice the close and send the change broadcast
630 
631                     Slog.v(TAG, "Restored wallpaper: " + resName);
632                     return true;
633                 } catch (PackageManager.NameNotFoundException e) {
634                     Slog.e(TAG, "Package name " + pkg + " not found");
635                 } catch (Resources.NotFoundException e) {
636                     Slog.e(TAG, "Resource not found: " + resId);
637                 } catch (IOException e) {
638                     Slog.e(TAG, "IOException while restoring wallpaper ", e);
639                 } finally {
640                     IoUtils.closeQuietly(res);
641                     if (fos != null) {
642                         FileUtils.sync(fos);
643                     }
644                     if (cos != null) {
645                         FileUtils.sync(cos);
646                     }
647                     IoUtils.closeQuietly(fos);
648                     IoUtils.closeQuietly(cos);
649                 }
650             }
651         }
652         return false;
653     }
654 
screenDimensionPairs()655     private static List<Pair<Integer, String>> screenDimensionPairs() {
656         return List.of(
657                 new Pair<>(WallpaperManager.PORTRAIT, "Portrait"),
658                 new Pair<>(WallpaperManager.LANDSCAPE, "Landscape"),
659                 new Pair<>(WallpaperManager.SQUARE_PORTRAIT, "SquarePortrait"),
660                 new Pair<>(WallpaperManager.SQUARE_LANDSCAPE, "SquareLandscape"));
661     }
662 }
663