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