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