1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.wallpaperbackup; 18 19 import static android.app.Flags.liveWallpaperContentHandling; 20 import static android.app.WallpaperManager.FLAG_LOCK; 21 import static android.app.WallpaperManager.FLAG_SYSTEM; 22 import static android.app.WallpaperManager.ORIENTATION_UNKNOWN; 23 import static android.os.ParcelFileDescriptor.MODE_READ_ONLY; 24 25 import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_INELIGIBLE; 26 import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_METADATA; 27 import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_WALLPAPER; 28 import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_QUOTA_EXCEEDED; 29 import static com.android.window.flags.Flags.multiCrop; 30 31 import android.annotation.Nullable; 32 import android.app.AppGlobals; 33 import android.app.WallpaperManager; 34 import android.app.backup.BackupAgent; 35 import android.app.backup.BackupDataInput; 36 import android.app.backup.BackupDataOutput; 37 import android.app.backup.BackupManager; 38 import android.app.backup.BackupRestoreEventLogger.BackupRestoreError; 39 import android.app.backup.FullBackupDataOutput; 40 import android.app.wallpaper.WallpaperDescription; 41 import android.content.ComponentName; 42 import android.content.Context; 43 import android.content.SharedPreferences; 44 import android.content.pm.IPackageManager; 45 import android.content.pm.PackageInfo; 46 import android.graphics.BitmapFactory; 47 import android.graphics.Point; 48 import android.graphics.Rect; 49 import android.hardware.display.DisplayManager; 50 import android.os.FileUtils; 51 import android.os.ParcelFileDescriptor; 52 import android.os.RemoteException; 53 import android.os.UserHandle; 54 import android.provider.Settings; 55 import android.util.Pair; 56 import android.util.Slog; 57 import android.util.SparseArray; 58 import android.util.Xml; 59 import android.view.Display; 60 import android.view.DisplayInfo; 61 62 import com.android.internal.annotations.VisibleForTesting; 63 import com.android.internal.content.PackageMonitor; 64 import com.android.modules.utils.TypedXmlPullParser; 65 import com.android.modules.utils.TypedXmlSerializer; 66 67 import org.xmlpull.v1.XmlPullParser; 68 import org.xmlpull.v1.XmlPullParserException; 69 70 import java.io.File; 71 import java.io.FileInputStream; 72 import java.io.FileOutputStream; 73 import java.io.IOException; 74 import java.util.ArrayList; 75 import java.util.List; 76 77 /** 78 * Backs up and restores wallpaper and metadata related to it. 79 * 80 * This agent has its own package because it does full backup as opposed to SystemBackupAgent 81 * which does key/value backup. 82 * 83 * This class stages wallpaper files for backup by copying them into its own directory because of 84 * the following reasons: 85 * 86 * <ul> 87 * <li>Non-system users don't have permission to read the directory that the system stores 88 * the wallpaper files in</li> 89 * <li>{@link BackupAgent} enforces that backed up files must live inside the package's 90 * {@link Context#getFilesDir()}</li> 91 * </ul> 92 * 93 * There are 3 files to back up: 94 * <ul> 95 * <li>The "wallpaper info" file which contains metadata like the crop applied to the 96 * wallpaper or the live wallpaper component name.</li> 97 * <li>The "system" wallpaper file.</li> 98 * <li>An optional "lock" wallpaper, which is shown on the lockscreen instead of the system 99 * wallpaper if set.</li> 100 * </ul> 101 * 102 * On restore, the metadata file is parsed and {@link WallpaperManager} APIs are used to set the 103 * wallpaper. Note that if there's a live wallpaper, the live wallpaper package name will be 104 * part of the metadata file and the wallpaper will be applied when the package it's installed. 105 */ 106 public class WallpaperBackupAgent extends BackupAgent { 107 private static final String TAG = "WallpaperBackup"; 108 private static final boolean DEBUG = false; 109 110 // Names of our local-data stage files 111 @VisibleForTesting 112 static final String SYSTEM_WALLPAPER_STAGE = "wallpaper-stage"; 113 @VisibleForTesting 114 static final String LOCK_WALLPAPER_STAGE = "wallpaper-lock-stage"; 115 @VisibleForTesting 116 static final String WALLPAPER_INFO_STAGE = "wallpaper-info-stage"; 117 @VisibleForTesting 118 static final String WALLPAPER_BACKUP_DEVICE_INFO_STAGE = "wallpaper-backup-device-info-stage"; 119 static final String EMPTY_SENTINEL = "empty"; 120 static final String QUOTA_SENTINEL = "quota"; 121 // Shared preferences constants. 122 static final String PREFS_NAME = "wbprefs.xml"; 123 static final String SYSTEM_GENERATION = "system_gen"; 124 static final String LOCK_GENERATION = "lock_gen"; 125 126 static final String DEVICE_CONFIG_WIDTH = "device_config_width"; 127 128 static final String DEVICE_CONFIG_HEIGHT = "device_config_height"; 129 130 static final String DEVICE_CONFIG_SECONDARY_WIDTH = "device_config_secondary_width"; 131 132 static final String DEVICE_CONFIG_SECONDARY_HEIGHT = "device_config_secondary_height"; 133 134 static final float DEFAULT_ACCEPTABLE_PARALLAX = 0.2f; 135 136 // If this file exists, it means we exceeded our quota last time 137 private File mQuotaFile; 138 private boolean mQuotaExceeded; 139 140 private WallpaperManager mWallpaperManager; 141 private WallpaperEventLogger mEventLogger; 142 private BackupManager mBackupManager; 143 144 private boolean mSystemHasLiveComponent; 145 private boolean mLockHasLiveComponent; 146 147 private DisplayManager mDisplayManager; 148 149 @Override onCreate()150 public void onCreate() { 151 if (DEBUG) { 152 Slog.v(TAG, "onCreate()"); 153 } 154 155 mWallpaperManager = getSystemService(WallpaperManager.class); 156 157 mQuotaFile = new File(getFilesDir(), QUOTA_SENTINEL); 158 mQuotaExceeded = mQuotaFile.exists(); 159 if (DEBUG) { 160 Slog.v(TAG, "quota file " + mQuotaFile.getPath() + " exists=" + mQuotaExceeded); 161 } 162 163 mBackupManager = new BackupManager(getBaseContext()); 164 mEventLogger = new WallpaperEventLogger(mBackupManager, /* wallpaperAgent */ this); 165 166 mDisplayManager = getSystemService(DisplayManager.class); 167 } 168 169 @Override onFullBackup(FullBackupDataOutput data)170 public void onFullBackup(FullBackupDataOutput data) throws IOException { 171 try { 172 // We always back up this 'empty' file to ensure that the absence of 173 // storable wallpaper imagery still produces a non-empty backup data 174 // stream, otherwise it'd simply be ignored in preflight. 175 final File empty = new File(getFilesDir(), EMPTY_SENTINEL); 176 if (!empty.exists()) { 177 FileOutputStream touch = new FileOutputStream(empty); 178 touch.close(); 179 } 180 backupFile(empty, data); 181 182 SharedPreferences sharedPrefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); 183 184 // Check the IDs of the wallpapers that we backed up last time. If they haven't 185 // changed, we won't re-stage them for backup and use the old staged versions to avoid 186 // disk churn. 187 final int lastSysGeneration = sharedPrefs.getInt(SYSTEM_GENERATION, /* defValue= */ -1); 188 final int lastLockGeneration = sharedPrefs.getInt(LOCK_GENERATION, /* defValue= */ -1); 189 190 final int deviceConfigWidth = sharedPrefs.getInt( 191 DEVICE_CONFIG_WIDTH, /* defValue= */ -1); 192 final int deviceConfigHeight = sharedPrefs.getInt( 193 DEVICE_CONFIG_HEIGHT, /* defValue= */ -1); 194 final int deviceConfigSecondaryWidth = sharedPrefs.getInt( 195 DEVICE_CONFIG_SECONDARY_WIDTH, /* defValue= */ -1); 196 final int deviceConfigSecondaryHeight = sharedPrefs.getInt( 197 DEVICE_CONFIG_SECONDARY_HEIGHT, /* defValue= */ -1); 198 199 final int sysGeneration = mWallpaperManager.getWallpaperId(FLAG_SYSTEM); 200 final int lockGeneration = mWallpaperManager.getWallpaperId(FLAG_LOCK); 201 final boolean sysChanged = (sysGeneration != lastSysGeneration); 202 final boolean lockChanged = (lockGeneration != lastLockGeneration); 203 204 if (DEBUG) { 205 Slog.v(TAG, "sysGen=" + sysGeneration + " : sysChanged=" + sysChanged); 206 Slog.v(TAG, "lockGen=" + lockGeneration + " : lockChanged=" + lockChanged); 207 } 208 209 // Due to the way image vs live wallpaper backup logic is intermingled, for logging 210 // purposes first check if we have live components for each wallpaper to avoid 211 // over-reporting errors. 212 mSystemHasLiveComponent = mWallpaperManager.getWallpaperInfo(FLAG_SYSTEM) != null; 213 mLockHasLiveComponent = mWallpaperManager.getWallpaperInfo(FLAG_LOCK) != null; 214 215 // performing backup of each file based on order of importance 216 backupWallpaperInfoFile(/* sysOrLockChanged= */ sysChanged || lockChanged, data); 217 backupSystemWallpaperFile(sharedPrefs, sysChanged, sysGeneration, data); 218 backupLockWallpaperFileIfItExists(sharedPrefs, lockChanged, lockGeneration, data); 219 220 final boolean isDeviceConfigChanged = isDeviceConfigChanged(deviceConfigWidth, 221 deviceConfigHeight, deviceConfigSecondaryWidth, deviceConfigSecondaryHeight); 222 223 backupDeviceInfoFile(sharedPrefs, isDeviceConfigChanged, data); 224 } catch (Exception e) { 225 Slog.e(TAG, "Unable to back up wallpaper", e); 226 mEventLogger.onBackupException(e); 227 } finally { 228 // Even if this time we had to back off on attempting to store the lock image 229 // due to exceeding the data quota, try again next time. This will alternate 230 // between "try both" and "only store the primary image" until either there 231 // is no lock image to store, or the quota is raised, or both fit under the 232 // quota. 233 mQuotaFile.delete(); 234 } 235 } 236 isDeviceConfigChanged(int width, int height, int secondaryWidth, int secondaryHeight)237 private boolean isDeviceConfigChanged(int width, int height, int secondaryWidth, 238 int secondaryHeight) { 239 Point currentDimensions = getScreenDimensions(); 240 Display smallerDisplay = getSmallerDisplayIfExists(); 241 Point currentSecondaryDimensions = smallerDisplay != null ? getRealSize(smallerDisplay) : 242 new Point(0, 0); 243 244 return (currentDimensions.x != width 245 || currentDimensions.y != height 246 || currentSecondaryDimensions.x != secondaryWidth 247 || currentSecondaryDimensions.y != secondaryHeight); 248 } 249 250 /** 251 * This method backs up the device dimension information. The device data will always get 252 * overwritten when triggering a backup 253 */ backupDeviceInfoFile(SharedPreferences sharedPrefs, boolean isDeviceConfigChanged, FullBackupDataOutput data)254 private void backupDeviceInfoFile(SharedPreferences sharedPrefs, boolean isDeviceConfigChanged, 255 FullBackupDataOutput data) 256 throws IOException { 257 final File deviceInfoStage = new File(getFilesDir(), WALLPAPER_BACKUP_DEVICE_INFO_STAGE); 258 259 if (isDeviceConfigChanged) { 260 // save the dimensions of the device with xml formatting 261 Point dimensions = getScreenDimensions(); 262 Display smallerDisplay = getSmallerDisplayIfExists(); 263 Point secondaryDimensions = smallerDisplay != null ? getRealSize(smallerDisplay) : 264 new Point(0, 0); 265 266 deviceInfoStage.createNewFile(); 267 FileOutputStream fstream = new FileOutputStream(deviceInfoStage, false); 268 TypedXmlSerializer out = Xml.resolveSerializer(fstream); 269 out.startDocument(null, true); 270 out.startTag(null, "dimensions"); 271 272 out.startTag(null, "width"); 273 out.text(String.valueOf(dimensions.x)); 274 out.endTag(null, "width"); 275 276 out.startTag(null, "height"); 277 out.text(String.valueOf(dimensions.y)); 278 out.endTag(null, "height"); 279 280 if (smallerDisplay != null) { 281 out.startTag(null, "secondarywidth"); 282 out.text(String.valueOf(secondaryDimensions.x)); 283 out.endTag(null, "secondarywidth"); 284 285 out.startTag(null, "secondaryheight"); 286 out.text(String.valueOf(secondaryDimensions.y)); 287 out.endTag(null, "secondaryheight"); 288 } 289 290 out.endTag(null, "dimensions"); 291 out.endDocument(); 292 fstream.flush(); 293 FileUtils.sync(fstream); 294 fstream.close(); 295 296 SharedPreferences.Editor editor = sharedPrefs.edit(); 297 editor.putInt(DEVICE_CONFIG_WIDTH, dimensions.x); 298 editor.putInt(DEVICE_CONFIG_HEIGHT, dimensions.y); 299 editor.putInt(DEVICE_CONFIG_SECONDARY_WIDTH, secondaryDimensions.x); 300 editor.putInt(DEVICE_CONFIG_SECONDARY_HEIGHT, secondaryDimensions.y); 301 editor.apply(); 302 } 303 if (DEBUG) Slog.v(TAG, "Storing device dimension data"); 304 backupFile(deviceInfoStage, data); 305 } 306 backupWallpaperInfoFile(boolean sysOrLockChanged, FullBackupDataOutput data)307 private void backupWallpaperInfoFile(boolean sysOrLockChanged, FullBackupDataOutput data) 308 throws IOException { 309 final ParcelFileDescriptor wallpaperInfoFd = mWallpaperManager.getWallpaperInfoFile(); 310 311 if (wallpaperInfoFd == null) { 312 Slog.w(TAG, "Wallpaper metadata file doesn't exist"); 313 // If we have live components, getting the file to back up somehow failed, so log it 314 // as an error. 315 if (mSystemHasLiveComponent) { 316 mEventLogger.onSystemLiveWallpaperBackupFailed(ERROR_NO_METADATA); 317 } 318 if (mLockHasLiveComponent) { 319 mEventLogger.onLockLiveWallpaperBackupFailed(ERROR_NO_METADATA); 320 } 321 return; 322 } 323 324 final File infoStage = new File(getFilesDir(), WALLPAPER_INFO_STAGE); 325 326 if (sysOrLockChanged || !infoStage.exists()) { 327 if (DEBUG) Slog.v(TAG, "New wallpaper configuration; copying"); 328 copyFromPfdToFileAndClosePfd(wallpaperInfoFd, infoStage); 329 } 330 331 if (DEBUG) Slog.v(TAG, "Storing wallpaper metadata"); 332 backupFile(infoStage, data); 333 334 // We've backed up the info file which contains the live component, so log it as success 335 if (mSystemHasLiveComponent) { 336 mEventLogger.onSystemLiveWallpaperBackedUp( 337 mWallpaperManager.getWallpaperInfo(FLAG_SYSTEM)); 338 } 339 if (mLockHasLiveComponent) { 340 mEventLogger.onLockLiveWallpaperBackedUp(mWallpaperManager.getWallpaperInfo(FLAG_LOCK)); 341 } 342 } 343 backupSystemWallpaperFile(SharedPreferences sharedPrefs, boolean sysChanged, int sysGeneration, FullBackupDataOutput data)344 private void backupSystemWallpaperFile(SharedPreferences sharedPrefs, 345 boolean sysChanged, int sysGeneration, FullBackupDataOutput data) throws IOException { 346 if (!mWallpaperManager.isWallpaperBackupEligible(FLAG_SYSTEM)) { 347 Slog.d(TAG, "System wallpaper ineligible for backup"); 348 logSystemImageErrorIfNoLiveComponent(ERROR_INELIGIBLE); 349 return; 350 } 351 352 final ParcelFileDescriptor systemWallpaperImageFd = mWallpaperManager.getWallpaperFile( 353 FLAG_SYSTEM, 354 /* getCropped= */ false); 355 356 if (systemWallpaperImageFd == null) { 357 Slog.w(TAG, "System wallpaper doesn't exist"); 358 logSystemImageErrorIfNoLiveComponent(ERROR_NO_WALLPAPER); 359 return; 360 } 361 362 final File imageStage = new File(getFilesDir(), SYSTEM_WALLPAPER_STAGE); 363 364 if (sysChanged || !imageStage.exists()) { 365 if (DEBUG) Slog.v(TAG, "New system wallpaper; copying"); 366 copyFromPfdToFileAndClosePfd(systemWallpaperImageFd, imageStage); 367 } 368 369 if (DEBUG) Slog.v(TAG, "Storing system wallpaper image"); 370 backupFile(imageStage, data); 371 sharedPrefs.edit().putInt(SYSTEM_GENERATION, sysGeneration).apply(); 372 mEventLogger.onSystemImageWallpaperBackedUp(); 373 } 374 logSystemImageErrorIfNoLiveComponent(@ackupRestoreError String error)375 private void logSystemImageErrorIfNoLiveComponent(@BackupRestoreError String error) { 376 if (mSystemHasLiveComponent) { 377 return; 378 } 379 mEventLogger.onSystemImageWallpaperBackupFailed(error); 380 } 381 backupLockWallpaperFileIfItExists(SharedPreferences sharedPrefs, boolean lockChanged, int lockGeneration, FullBackupDataOutput data)382 private void backupLockWallpaperFileIfItExists(SharedPreferences sharedPrefs, 383 boolean lockChanged, int lockGeneration, FullBackupDataOutput data) throws IOException { 384 final File lockImageStage = new File(getFilesDir(), LOCK_WALLPAPER_STAGE); 385 386 // This means there's no lock wallpaper set by the user. 387 if (lockGeneration == -1) { 388 if (lockChanged && lockImageStage.exists()) { 389 if (DEBUG) Slog.v(TAG, "Removed lock wallpaper; deleting"); 390 lockImageStage.delete(); 391 } 392 Slog.d(TAG, "No lockscreen wallpaper set, add nothing to backup"); 393 sharedPrefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply(); 394 logLockImageErrorIfNoLiveComponent(ERROR_NO_WALLPAPER); 395 return; 396 } 397 398 if (!mWallpaperManager.isWallpaperBackupEligible(FLAG_LOCK)) { 399 Slog.d(TAG, "Lock screen wallpaper ineligible for backup"); 400 logLockImageErrorIfNoLiveComponent(ERROR_INELIGIBLE); 401 return; 402 } 403 404 final ParcelFileDescriptor lockWallpaperFd = mWallpaperManager.getWallpaperFile( 405 FLAG_LOCK, /* getCropped= */ false); 406 407 // If we get to this point, that means lockGeneration != -1 so there's a lock wallpaper 408 // set, but we can't find it. 409 if (lockWallpaperFd == null) { 410 Slog.w(TAG, "Lock wallpaper doesn't exist"); 411 logLockImageErrorIfNoLiveComponent(ERROR_NO_WALLPAPER); 412 return; 413 } 414 415 if (mQuotaExceeded) { 416 Slog.w(TAG, "Not backing up lock screen wallpaper. Quota was exceeded last time"); 417 logLockImageErrorIfNoLiveComponent(ERROR_QUOTA_EXCEEDED); 418 return; 419 } 420 421 if (lockChanged || !lockImageStage.exists()) { 422 if (DEBUG) Slog.v(TAG, "New lock wallpaper; copying"); 423 copyFromPfdToFileAndClosePfd(lockWallpaperFd, lockImageStage); 424 } 425 426 if (DEBUG) Slog.v(TAG, "Storing lock wallpaper image"); 427 backupFile(lockImageStage, data); 428 sharedPrefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply(); 429 mEventLogger.onLockImageWallpaperBackedUp(); 430 } 431 logLockImageErrorIfNoLiveComponent(@ackupRestoreError String error)432 private void logLockImageErrorIfNoLiveComponent(@BackupRestoreError String error) { 433 if (mLockHasLiveComponent) { 434 return; 435 } 436 mEventLogger.onLockImageWallpaperBackupFailed(error); 437 } 438 439 /** 440 * Copies the contents of the given {@code pfd} to the given {@code file}. 441 * 442 * All resources used in the process including the {@code pfd} will be closed. 443 */ copyFromPfdToFileAndClosePfd(ParcelFileDescriptor pfd, File file)444 private static void copyFromPfdToFileAndClosePfd(ParcelFileDescriptor pfd, File file) 445 throws IOException { 446 try (ParcelFileDescriptor.AutoCloseInputStream inputStream = 447 new ParcelFileDescriptor.AutoCloseInputStream(pfd); 448 FileOutputStream outputStream = new FileOutputStream(file) 449 ) { 450 FileUtils.copy(inputStream, outputStream); 451 } 452 } 453 readText(TypedXmlPullParser parser)454 private static String readText(TypedXmlPullParser parser) 455 throws IOException, XmlPullParserException { 456 String result = ""; 457 if (parser.next() == XmlPullParser.TEXT) { 458 result = parser.getText(); 459 parser.nextTag(); 460 } 461 return result; 462 } 463 464 @VisibleForTesting 465 // fullBackupFile is final, so we intercept backups here in tests. backupFile(File file, FullBackupDataOutput data)466 protected void backupFile(File file, FullBackupDataOutput data) { 467 fullBackupFile(file, data); 468 } 469 470 @Override onQuotaExceeded(long backupDataBytes, long quotaBytes)471 public void onQuotaExceeded(long backupDataBytes, long quotaBytes) { 472 Slog.i(TAG, "Quota exceeded (" + backupDataBytes + " vs " + quotaBytes + ')'); 473 try (FileOutputStream f = new FileOutputStream(mQuotaFile)) { 474 f.write(0); 475 } catch (Exception e) { 476 Slog.w(TAG, "Unable to record quota-exceeded: " + e.getMessage()); 477 } 478 } 479 480 // We use the default onRestoreFile() implementation that will recreate our stage files, 481 // then post-process in onRestoreFinished() to apply the new wallpaper. 482 @Override onRestoreFinished()483 public void onRestoreFinished() { 484 Slog.v(TAG, "onRestoreFinished()"); 485 final File filesDir = getFilesDir(); 486 final File infoStage = new File(filesDir, WALLPAPER_INFO_STAGE); 487 final File imageStage = new File(filesDir, SYSTEM_WALLPAPER_STAGE); 488 final File lockImageStage = new File(filesDir, LOCK_WALLPAPER_STAGE); 489 final File deviceDimensionsStage = new File(filesDir, WALLPAPER_BACKUP_DEVICE_INFO_STAGE); 490 boolean lockImageStageExists = lockImageStage.exists(); 491 492 try { 493 // Parse the device dimensions of the source device 494 Pair<Point, Point> sourceDeviceDimensions = parseDeviceDimensions( 495 deviceDimensionsStage); 496 497 // First parse the live component name so that we know for logging if we care about 498 // logging errors with the image restore. 499 Pair<ComponentName, WallpaperDescription> wpService = parseWallpaperComponent(infoStage, 500 "wp"); 501 mSystemHasLiveComponent = wpService.first != null; 502 503 Pair<ComponentName, WallpaperDescription> kwpService = parseWallpaperComponent( 504 infoStage, "kwp"); 505 mLockHasLiveComponent = kwpService.first != null; 506 boolean separateLockWallpaper = mLockHasLiveComponent || lockImageStage.exists(); 507 508 // if there's no separate lock wallpaper, apply the system wallpaper to both screens. 509 final int sysWhich = separateLockWallpaper ? FLAG_SYSTEM : FLAG_SYSTEM | FLAG_LOCK; 510 511 // It is valid for the imagery to be absent; it means that we were not permitted 512 // to back up the original image on the source device, or there was no user-supplied 513 // wallpaper image present. 514 if (lockImageStageExists) { 515 restoreFromStage(lockImageStage, infoStage, "kwp", FLAG_LOCK, 516 sourceDeviceDimensions); 517 } 518 restoreFromStage(imageStage, infoStage, "wp", sysWhich, sourceDeviceDimensions); 519 520 // And reset to the wallpaper service we should be using 521 if (mLockHasLiveComponent) { 522 updateWallpaperComponent(kwpService, FLAG_LOCK); 523 } 524 updateWallpaperComponent(wpService, sysWhich); 525 } catch (Exception e) { 526 Slog.e(TAG, "Unable to restore wallpaper: " + e.getMessage()); 527 mEventLogger.onRestoreException(e); 528 } finally { 529 Slog.v(TAG, "Restore finished; clearing backup bookkeeping"); 530 infoStage.delete(); 531 imageStage.delete(); 532 lockImageStage.delete(); 533 deviceDimensionsStage.delete(); 534 535 SharedPreferences prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); 536 prefs.edit() 537 .putInt(SYSTEM_GENERATION, -1) 538 .putInt(LOCK_GENERATION, -1) 539 .commit(); 540 } 541 } 542 543 /** 544 * This method parses the given file for the backed up device dimensions 545 * 546 * @param deviceDimensions the file which holds the device dimensions 547 * @return the backed up device dimensions 548 */ parseDeviceDimensions(File deviceDimensions)549 private Pair<Point, Point> parseDeviceDimensions(File deviceDimensions) { 550 int width = 0, height = 0, secondaryHeight = 0, secondaryWidth = 0; 551 try { 552 TypedXmlPullParser parser = Xml.resolvePullParser( 553 new FileInputStream(deviceDimensions)); 554 555 while (parser.next() != XmlPullParser.END_TAG) { 556 if (parser.getEventType() != XmlPullParser.START_TAG) { 557 continue; 558 } 559 560 String name = parser.getName(); 561 562 switch (name) { 563 case "width": 564 String widthText = readText(parser); 565 width = Integer.valueOf(widthText); 566 break; 567 568 case "height": 569 String textHeight = readText(parser); 570 height = Integer.valueOf(textHeight); 571 break; 572 573 case "secondarywidth": 574 String secondaryWidthText = readText(parser); 575 secondaryWidth = Integer.valueOf(secondaryWidthText); 576 break; 577 578 case "secondaryheight": 579 String secondaryHeightText = readText(parser); 580 secondaryHeight = Integer.valueOf(secondaryHeightText); 581 break; 582 default: 583 break; 584 } 585 } 586 return new Pair<>(new Point(width, height), new Point(secondaryWidth, secondaryHeight)); 587 588 } catch (Exception e) { 589 return null; 590 } 591 } 592 593 @VisibleForTesting updateWallpaperComponent(Pair<ComponentName, WallpaperDescription> wpService, int which)594 void updateWallpaperComponent(Pair<ComponentName, WallpaperDescription> wpService, int which) 595 throws IOException { 596 WallpaperDescription description = wpService.second; 597 boolean hasDescription = (liveWallpaperContentHandling() && description != null); 598 ComponentName component = hasDescription ? description.getComponent() : wpService.first; 599 if (servicePackageExists(component)) { 600 if (hasDescription) { 601 Slog.i(TAG, "Using wallpaper description " + description); 602 mWallpaperManager.setWallpaperComponentWithDescription(description, which); 603 if ((which & FLAG_LOCK) != 0) { 604 mEventLogger.onLockLiveWallpaperRestoredWithDescription(description); 605 } 606 if ((which & FLAG_SYSTEM) != 0) { 607 mEventLogger.onSystemLiveWallpaperRestoredWithDescription(description); 608 } 609 } else { 610 Slog.i(TAG, "Using wallpaper service " + component); 611 mWallpaperManager.setWallpaperComponentWithFlags(component, which); 612 if ((which & FLAG_LOCK) != 0) { 613 mEventLogger.onLockLiveWallpaperRestored(component); 614 } 615 if ((which & FLAG_SYSTEM) != 0) { 616 mEventLogger.onSystemLiveWallpaperRestored(component); 617 } 618 } 619 } else { 620 // If we've restored a live wallpaper, but the component doesn't exist, 621 // we should log it as an error so we can easily identify the problem 622 // in reports from users 623 if (component != null) { 624 // TODO(b/268471749): Handle delayed case 625 applyComponentAtInstall(component, description, which); 626 Slog.w(TAG, "Wallpaper service " + component + " isn't available. " 627 + " Will try to apply later"); 628 } 629 } 630 } 631 restoreFromStage(File stage, File info, String hintTag, int which, Pair<Point, Point> sourceDeviceDimensions)632 private void restoreFromStage(File stage, File info, String hintTag, int which, 633 Pair<Point, Point> sourceDeviceDimensions) 634 throws IOException { 635 if (stage.exists()) { 636 if (multiCrop()) { 637 // TODO(b/332937943): implement offset adjustment by manually adjusting crop to 638 // adhere to device aspect ratio 639 SparseArray<Rect> cropHints = parseCropHints(info, hintTag); 640 if (cropHints != null) { 641 Slog.i(TAG, "Got restored wallpaper; applying which=" + which 642 + "; cropHints = " + cropHints); 643 try (FileInputStream in = new FileInputStream(stage)) { 644 mWallpaperManager.setStreamWithCrops(in, cropHints, true, which); 645 } 646 // And log the success 647 if ((which & FLAG_SYSTEM) > 0) { 648 mEventLogger.onSystemImageWallpaperRestored(); 649 } 650 if ((which & FLAG_LOCK) > 0) { 651 mEventLogger.onLockImageWallpaperRestored(); 652 } 653 } else { 654 logRestoreError(which, ERROR_NO_METADATA); 655 } 656 return; 657 } 658 // Parse the restored info file to find the crop hint. Note that this currently 659 // relies on a priori knowledge of the wallpaper info file schema. 660 Rect cropHint = parseCropHint(info, hintTag); 661 if (cropHint != null) { 662 Slog.i(TAG, "Got restored wallpaper; applying which=" + which 663 + "; cropHint = " + cropHint); 664 try (FileInputStream in = new FileInputStream(stage)) { 665 666 if (sourceDeviceDimensions != null && sourceDeviceDimensions.first != null) { 667 BitmapFactory.Options options = new BitmapFactory.Options(); 668 options.inJustDecodeBounds = true; 669 ParcelFileDescriptor pdf = ParcelFileDescriptor.open(stage, MODE_READ_ONLY); 670 BitmapFactory.decodeFileDescriptor(pdf.getFileDescriptor(), 671 null, options); 672 Point bitmapSize = new Point(options.outWidth, options.outHeight); 673 Point sourceDeviceSize = new Point(sourceDeviceDimensions.first.x, 674 sourceDeviceDimensions.first.y); 675 Point targetDeviceDimensions = getScreenDimensions(); 676 677 // TODO: for now we handle only the case where the target device has smaller 678 // aspect ratio than the source device i.e. the target device is more narrow 679 // than the source device 680 if (isTargetMoreNarrowThanSource(targetDeviceDimensions, 681 sourceDeviceSize)) { 682 Rect adjustedCrop = findNewCropfromOldCrop(cropHint, 683 sourceDeviceDimensions.first, true, targetDeviceDimensions, 684 bitmapSize, true); 685 686 cropHint.set(adjustedCrop); 687 } 688 } 689 690 mWallpaperManager.setStream(in, cropHint.isEmpty() ? null : cropHint, 691 true, which); 692 693 // And log the success 694 if ((which & FLAG_SYSTEM) > 0) { 695 mEventLogger.onSystemImageWallpaperRestored(); 696 } 697 if ((which & FLAG_LOCK) > 0) { 698 mEventLogger.onLockImageWallpaperRestored(); 699 } 700 } 701 } else { 702 logRestoreError(which, ERROR_NO_METADATA); 703 } 704 } else { 705 Slog.d(TAG, "Restore data doesn't exist for file " + stage.getPath()); 706 logRestoreErrorIfNoLiveComponent(which, ERROR_NO_WALLPAPER); 707 } 708 } 709 710 /** 711 * This method computes the crop of the stored wallpaper to preserve its center point as the 712 * user had set it in the previous device. 713 * 714 * The algorithm involves first computing the original crop of the user (without parallax). Then 715 * manually adjusting the user's original crop to respect the current device's aspect ratio 716 * (thereby preserving the center point). Then finally, adding any leftover image real-estate 717 * (i.e. space left over on the horizontal axis) to add parallax effect. Parallax is only added 718 * if was present in the old device's settings. 719 */ findNewCropfromOldCrop(Rect oldCrop, Point oldDisplaySize, boolean oldRtl, Point newDisplaySize, Point bitmapSize, boolean newRtl)720 private Rect findNewCropfromOldCrop(Rect oldCrop, Point oldDisplaySize, boolean oldRtl, 721 Point newDisplaySize, Point bitmapSize, boolean newRtl) { 722 Rect cropWithoutParallax = withoutParallax(oldCrop, oldDisplaySize, oldRtl, bitmapSize); 723 oldCrop = oldCrop.isEmpty() ? new Rect(0, 0, bitmapSize.x, bitmapSize.y) : oldCrop; 724 float oldParallaxAmount = ((float) oldCrop.width() / cropWithoutParallax.width()) - 1; 725 726 Rect newCropWithSameCenterWithoutParallax = sameCenter(newDisplaySize, bitmapSize, 727 cropWithoutParallax); 728 729 Rect newCrop = newCropWithSameCenterWithoutParallax; 730 731 // calculate the amount of left-over space there is in the image after adjusting the crop 732 // from the above operation i.e. in a rtl configuration, this is the remaining space in the 733 // image after subtracting the new crop's right edge coordinate from the image itself, and 734 // for ltr, its just the new crop's left edge coordinate (as it's the distance from the 735 // beginning of the image) 736 int widthAvailableForParallaxOnTheNewDevice = 737 (newRtl) ? newCrop.left : bitmapSize.x - newCrop.right; 738 739 // calculate relatively how much this available space is as a fraction of the total cropped 740 // image 741 float availableParallaxAmount = 742 (float) widthAvailableForParallaxOnTheNewDevice / newCrop.width(); 743 744 float minAcceptableParallax = Math.min(DEFAULT_ACCEPTABLE_PARALLAX, oldParallaxAmount); 745 746 if (DEBUG) { 747 Slog.d(TAG, "- cropWithoutParallax: " + cropWithoutParallax); 748 Slog.d(TAG, "- oldParallaxAmount: " + oldParallaxAmount); 749 Slog.d(TAG, "- newCropWithSameCenterWithoutParallax: " 750 + newCropWithSameCenterWithoutParallax); 751 Slog.d(TAG, "- widthAvailableForParallaxOnTheNewDevice: " 752 + widthAvailableForParallaxOnTheNewDevice); 753 Slog.d(TAG, "- availableParallaxAmount: " + availableParallaxAmount); 754 Slog.d(TAG, "- minAcceptableParallax: " + minAcceptableParallax); 755 Slog.d(TAG, "- oldCrop: " + oldCrop); 756 Slog.d(TAG, "- oldDisplaySize: " + oldDisplaySize); 757 Slog.d(TAG, "- oldRtl: " + oldRtl); 758 Slog.d(TAG, "- newDisplaySize: " + newDisplaySize); 759 Slog.d(TAG, "- bitmapSize: " + bitmapSize); 760 Slog.d(TAG, "- newRtl: " + newRtl); 761 } 762 if (availableParallaxAmount >= minAcceptableParallax) { 763 // but in any case, don't put more parallax than the amount of the old device 764 float parallaxToAdd = Math.min(availableParallaxAmount, oldParallaxAmount); 765 766 int widthToAddForParallax = (int) (newCrop.width() * parallaxToAdd); 767 if (DEBUG) { 768 Slog.d(TAG, "- parallaxToAdd: " + parallaxToAdd); 769 Slog.d(TAG, "- widthToAddForParallax: " + widthToAddForParallax); 770 } 771 if (newRtl) { 772 newCrop.left -= widthToAddForParallax; 773 } else { 774 newCrop.right += widthToAddForParallax; 775 } 776 } 777 return newCrop; 778 } 779 780 /** 781 * This method computes the original crop of the user without parallax. 782 * 783 * NOTE: When the user sets the wallpaper with a specific crop, there may additional image added 784 * to the crop to support parallax. In order to determine the user's actual crop the parallax 785 * must be removed if it exists. 786 */ withoutParallax(Rect crop, Point displaySize, boolean rtl, Point bitmapSize)787 Rect withoutParallax(Rect crop, Point displaySize, boolean rtl, Point bitmapSize) { 788 // in the case an image's crop is not set, we assume the image itself is cropped 789 if (crop.isEmpty()) { 790 crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y); 791 } 792 793 if (DEBUG) { 794 Slog.w(TAG, "- crop: " + crop); 795 } 796 797 Rect adjustedCrop = new Rect(crop); 798 float suggestedDisplayRatio = (float) displaySize.x / displaySize.y; 799 800 // here we calculate the width of the wallpaper image such that it has the same aspect ratio 801 // as the given display i.e. the width of the image on a single page of the device without 802 // parallax (i.e. displaySize will correspond to the display the crop was originally set on) 803 int wallpaperWidthWithoutParallax = (int) (0.5f + (float) displaySize.x * crop.height() 804 / displaySize.y); 805 // subtracting wallpaperWidthWithoutParallax from the wallpaper crop gives the amount of 806 // parallax added 807 int widthToRemove = Math.max(0, crop.width() - wallpaperWidthWithoutParallax); 808 809 if (DEBUG) { 810 Slog.d(TAG, "- adjustedCrop: " + adjustedCrop); 811 Slog.d(TAG, "- suggestedDisplayRatio: " + suggestedDisplayRatio); 812 Slog.d(TAG, "- wallpaperWidthWithoutParallax: " + wallpaperWidthWithoutParallax); 813 Slog.d(TAG, "- widthToRemove: " + widthToRemove); 814 } 815 if (rtl) { 816 adjustedCrop.left += widthToRemove; 817 } else { 818 adjustedCrop.right -= widthToRemove; 819 } 820 821 if (DEBUG) { 822 Slog.d(TAG, "- adjustedCrop: " + crop); 823 } 824 return adjustedCrop; 825 } 826 827 /** 828 * This method computes a new crop based on the given crop in order to preserve the center point 829 * of the given crop on the provided displaySize. This is only for the case where the device 830 * displaySize has a smaller aspect ratio than the cropped image. 831 * 832 * NOTE: If the width to height ratio is less in the device display than cropped image 833 * this means the aspect ratios are off and there will be distortions in the image 834 * if the image is applied to the current display (i.e. the image will be skewed -> 835 * pixels in the image will not align correctly with the same pixels in the image that are 836 * above them) 837 */ sameCenter(Point displaySize, Point bitmapSize, Rect crop)838 Rect sameCenter(Point displaySize, Point bitmapSize, Rect crop) { 839 840 // in the case an image's crop is not set, we assume the image itself is cropped 841 if (crop.isEmpty()) { 842 crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y); 843 } 844 845 float screenRatio = (float) displaySize.x / displaySize.y; 846 float cropRatio = (float) crop.width() / crop.height(); 847 848 Rect adjustedCrop = new Rect(crop); 849 850 if (screenRatio < cropRatio) { 851 // the screen is more narrow than the image, and as such, the image will need to be 852 // zoomed in till it fits in the vertical axis. Due to this, we need to manually adjust 853 // the image's crop in order for it to fit into the screen without having the framework 854 // do it (since the framework left aligns the image after zooming) 855 856 // Calculate the height of the adjusted wallpaper crop so it respects the aspect ratio 857 // of the device. To calculate the height, we will use the width of the current crop. 858 // This is so we find the largest height possible which also respects the device aspect 859 // ratio. 860 int heightToAdd = (int) (0.5f + crop.width() / screenRatio - crop.height()); 861 862 // Calculate how much extra image space available that can be used to adjust 863 // the crop. If this amount is less than heightToAdd, from above, then that means we 864 // can't use heightToAdd. Instead we will need to use the maximum possible height, which 865 // is the height of the original bitmap. NOTE: the bitmap height may be different than 866 // the crop. 867 // since there is no guarantee to have height available on both sides 868 // (e.g. the available height might be fully at the bottom), grab the minimum 869 int availableHeight = 2 * Math.min(crop.top, bitmapSize.y - crop.bottom); 870 int actualHeightToAdd = Math.min(heightToAdd, availableHeight); 871 872 // half of the additional height is added to the top and bottom of the crop 873 adjustedCrop.top -= actualHeightToAdd / 2 + actualHeightToAdd % 2; 874 adjustedCrop.bottom += actualHeightToAdd / 2; 875 876 // Calculate the width of the adjusted crop. Initially we used the fixed width of the 877 // crop to calculate the heightToAdd, but since this height may be invalid (based on 878 // the calculation above) we calculate the width again instead of using the fixed width, 879 // using the adjustedCrop's updated height. 880 int widthToRemove = (int) (0.5f + crop.width() - adjustedCrop.height() * screenRatio); 881 882 // half of the additional width is subtracted from the left and right side of the crop 883 int widthToRemoveLeft = widthToRemove / 2; 884 int widthToRemoveRight = widthToRemove / 2 + widthToRemove % 2; 885 886 adjustedCrop.left += widthToRemoveLeft; 887 adjustedCrop.right -= widthToRemoveRight; 888 889 if (DEBUG) { 890 Slog.d(TAG, "cropRatio: " + cropRatio); 891 Slog.d(TAG, "screenRatio: " + screenRatio); 892 Slog.d(TAG, "heightToAdd: " + heightToAdd); 893 Slog.d(TAG, "actualHeightToAdd: " + actualHeightToAdd); 894 Slog.d(TAG, "availableHeight: " + availableHeight); 895 Slog.d(TAG, "widthToRemove: " + widthToRemove); 896 Slog.d(TAG, "adjustedCrop: " + adjustedCrop); 897 } 898 899 return adjustedCrop; 900 } 901 902 return adjustedCrop; 903 } 904 isTargetMoreNarrowThanSource(Point targetDisplaySize, Point srcDisplaySize)905 private boolean isTargetMoreNarrowThanSource(Point targetDisplaySize, Point srcDisplaySize) { 906 float targetScreenRatio = (float) targetDisplaySize.x / targetDisplaySize.y; 907 float srcScreenRatio = (float) srcDisplaySize.x / srcDisplaySize.y; 908 909 return (targetScreenRatio < srcScreenRatio); 910 } 911 logRestoreErrorIfNoLiveComponent(int which, String error)912 private void logRestoreErrorIfNoLiveComponent(int which, String error) { 913 if (mSystemHasLiveComponent) { 914 return; 915 } 916 logRestoreError(which, error); 917 } 918 logRestoreError(int which, String error)919 private void logRestoreError(int which, String error) { 920 if ((which & FLAG_SYSTEM) == FLAG_SYSTEM) { 921 mEventLogger.onSystemImageWallpaperRestoreFailed(error); 922 } 923 if ((which & FLAG_LOCK) == FLAG_LOCK) { 924 mEventLogger.onLockImageWallpaperRestoreFailed(error); 925 } 926 } 927 parseCropHint(File wallpaperInfo, String sectionTag)928 private Rect parseCropHint(File wallpaperInfo, String sectionTag) { 929 Rect cropHint = new Rect(); 930 try (FileInputStream stream = new FileInputStream(wallpaperInfo)) { 931 XmlPullParser parser = Xml.resolvePullParser(stream); 932 933 int type; 934 do { 935 type = parser.next(); 936 if (type == XmlPullParser.START_TAG) { 937 String tag = parser.getName(); 938 if (sectionTag.equals(tag)) { 939 cropHint.left = getAttributeInt(parser, "cropLeft", 0); 940 cropHint.top = getAttributeInt(parser, "cropTop", 0); 941 cropHint.right = getAttributeInt(parser, "cropRight", 0); 942 cropHint.bottom = getAttributeInt(parser, "cropBottom", 0); 943 } 944 } 945 } while (type != XmlPullParser.END_DOCUMENT); 946 } catch (Exception e) { 947 // Whoops; can't process the info file at all. Report failure. 948 Slog.w(TAG, "Failed to parse restored crop: " + e.getMessage()); 949 return null; 950 } 951 952 return cropHint; 953 } 954 parseCropHints(File wallpaperInfo, String sectionTag)955 private SparseArray<Rect> parseCropHints(File wallpaperInfo, String sectionTag) { 956 SparseArray<Rect> cropHints = new SparseArray<>(); 957 try (FileInputStream stream = new FileInputStream(wallpaperInfo)) { 958 XmlPullParser parser = Xml.resolvePullParser(stream); 959 int type; 960 do { 961 type = parser.next(); 962 if (type != XmlPullParser.START_TAG) continue; 963 String tag = parser.getName(); 964 if (!sectionTag.equals(tag)) continue; 965 for (Pair<Integer, String> pair : List.of( 966 new Pair<>(WallpaperManager.ORIENTATION_PORTRAIT, "Portrait"), 967 new Pair<>(WallpaperManager.ORIENTATION_LANDSCAPE, "Landscape"), 968 new Pair<>(WallpaperManager.ORIENTATION_SQUARE_PORTRAIT, "SquarePortrait"), 969 new Pair<>(WallpaperManager.ORIENTATION_SQUARE_LANDSCAPE, 970 "SquareLandscape"))) { 971 Rect cropHint = new Rect( 972 getAttributeInt(parser, "cropLeft" + pair.second, 0), 973 getAttributeInt(parser, "cropTop" + pair.second, 0), 974 getAttributeInt(parser, "cropRight" + pair.second, 0), 975 getAttributeInt(parser, "cropBottom" + pair.second, 0)); 976 if (!cropHint.isEmpty()) cropHints.put(pair.first, cropHint); 977 } 978 if (cropHints.size() == 0) { 979 // migration case: the crops per screen orientation are not specified. 980 // use the old attributes to restore the crop for one screen orientation. 981 Rect cropHint = new Rect( 982 getAttributeInt(parser, "cropLeft", 0), 983 getAttributeInt(parser, "cropTop", 0), 984 getAttributeInt(parser, "cropRight", 0), 985 getAttributeInt(parser, "cropBottom", 0)); 986 if (!cropHint.isEmpty()) cropHints.put(ORIENTATION_UNKNOWN, cropHint); 987 } 988 } while (type != XmlPullParser.END_DOCUMENT); 989 } catch (Exception e) { 990 // Whoops; can't process the info file at all. Report failure. 991 Slog.w(TAG, "Failed to parse restored crops: " + e.getMessage()); 992 return null; 993 } 994 return cropHints; 995 } 996 parseWallpaperComponent(File wallpaperInfo, String sectionTag)997 private Pair<ComponentName, WallpaperDescription> parseWallpaperComponent(File wallpaperInfo, 998 String sectionTag) { 999 ComponentName name = null; 1000 WallpaperDescription description = null; 1001 try (FileInputStream stream = new FileInputStream(wallpaperInfo)) { 1002 final TypedXmlPullParser parser = Xml.resolvePullParser(stream); 1003 1004 int type; 1005 do { 1006 type = parser.next(); 1007 if (type == XmlPullParser.START_TAG) { 1008 String tag = parser.getName(); 1009 if (sectionTag.equals(tag)) { 1010 final String parsedName = parser.getAttributeValue(null, "component"); 1011 name = (parsedName != null) 1012 ? ComponentName.unflattenFromString(parsedName) 1013 : null; 1014 description = parseWallpaperDescription(parser, name); 1015 break; 1016 } 1017 } 1018 } while (type != XmlPullParser.END_DOCUMENT); 1019 } catch (Exception e) { 1020 // Whoops; can't process the info file at all. Report failure. 1021 Slog.w(TAG, "Failed to parse restored component: " + e.getMessage()); 1022 return new Pair<>(null, null); 1023 } 1024 return new Pair<>(name, description); 1025 } 1026 1027 // Copied from com.android.server.wallpaper.WallpaperDataParser parseWallpaperDescription(TypedXmlPullParser parser, ComponentName component)1028 private WallpaperDescription parseWallpaperDescription(TypedXmlPullParser parser, 1029 ComponentName component) throws XmlPullParserException, IOException { 1030 1031 WallpaperDescription description = null; 1032 int type = parser.next(); 1033 if (type == XmlPullParser.START_TAG && "description".equals(parser.getName())) { 1034 // Always read the description if it's there - there may be one from a previous save 1035 // with content handling enabled even if it's enabled now 1036 description = WallpaperDescription.restoreFromXml(parser); 1037 if (liveWallpaperContentHandling()) { 1038 // null component means that wallpaper was last saved without content handling, so 1039 // populate description from saved component 1040 if (description.getComponent() == null) { 1041 description = description.toBuilder().setComponent(component).build(); 1042 } 1043 } 1044 } 1045 return description; 1046 } 1047 getAttributeInt(XmlPullParser parser, String name, int defValue)1048 private int getAttributeInt(XmlPullParser parser, String name, int defValue) { 1049 final String value = parser.getAttributeValue(null, name); 1050 return (value == null) ? defValue : Integer.parseInt(value); 1051 } 1052 1053 @VisibleForTesting servicePackageExists(ComponentName comp)1054 boolean servicePackageExists(ComponentName comp) { 1055 try { 1056 if (comp != null) { 1057 final IPackageManager pm = AppGlobals.getPackageManager(); 1058 final PackageInfo info = pm.getPackageInfo(comp.getPackageName(), 1059 0, getUserId()); 1060 return (info != null); 1061 } 1062 } catch (RemoteException e) { 1063 Slog.e(TAG, "Unable to contact package manager"); 1064 } 1065 return false; 1066 } 1067 1068 /** Unused Key/Value API. */ 1069 @Override onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState)1070 public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, 1071 ParcelFileDescriptor newState) throws IOException { 1072 // Intentionally blank 1073 } 1074 1075 /** Unused Key/Value API. */ 1076 @Override onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)1077 public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) 1078 throws IOException { 1079 // Intentionally blank 1080 } 1081 applyComponentAtInstall(ComponentName componentName, @Nullable WallpaperDescription description, int which)1082 private void applyComponentAtInstall(ComponentName componentName, 1083 @Nullable WallpaperDescription description, int which) { 1084 PackageMonitor packageMonitor = getWallpaperPackageMonitor(componentName, description, 1085 which); 1086 packageMonitor.register(getBaseContext(), null, UserHandle.ALL, true); 1087 } 1088 1089 @VisibleForTesting getWallpaperPackageMonitor(ComponentName componentName, @Nullable WallpaperDescription description, int which)1090 PackageMonitor getWallpaperPackageMonitor(ComponentName componentName, 1091 @Nullable WallpaperDescription description, int which) { 1092 return new PackageMonitor() { 1093 @Override 1094 public void onPackageAdded(String packageName, int uid) { 1095 if (!isDeviceInRestore()) { 1096 // We don't want to reapply the wallpaper outside a restore. 1097 unregister(); 1098 1099 // We have finished restore and not succeeded, so let's log that as an error. 1100 WallpaperEventLogger logger = new WallpaperEventLogger( 1101 mBackupManager.getDelayedRestoreLogger()); 1102 if ((which & FLAG_SYSTEM) != 0) { 1103 logger.onSystemLiveWallpaperRestoreFailed( 1104 WallpaperEventLogger.ERROR_LIVE_PACKAGE_NOT_INSTALLED); 1105 } 1106 if ((which & FLAG_LOCK) != 0) { 1107 logger.onLockLiveWallpaperRestoreFailed( 1108 WallpaperEventLogger.ERROR_LIVE_PACKAGE_NOT_INSTALLED); 1109 } 1110 mBackupManager.reportDelayedRestoreResult(logger.getBackupRestoreLogger()); 1111 1112 return; 1113 } 1114 1115 boolean useDescription = (liveWallpaperContentHandling() && description != null 1116 && description.getComponent() != null); 1117 if (useDescription && description.getComponent().getPackageName().equals( 1118 packageName)) { 1119 Slog.d(TAG, "Applying description " + description); 1120 boolean success = mWallpaperManager.setWallpaperComponentWithDescription( 1121 description, which); 1122 WallpaperEventLogger logger = new WallpaperEventLogger( 1123 mBackupManager.getDelayedRestoreLogger()); 1124 if (success) { 1125 if ((which & FLAG_SYSTEM) != 0) { 1126 logger.onSystemLiveWallpaperRestoredWithDescription(description); 1127 } 1128 if ((which & FLAG_LOCK) != 0) { 1129 logger.onLockLiveWallpaperRestoredWithDescription(description); 1130 } 1131 } else { 1132 if ((which & FLAG_SYSTEM) != 0) { 1133 logger.onSystemLiveWallpaperRestoreFailed( 1134 WallpaperEventLogger.ERROR_SET_DESCRIPTION_EXCEPTION); 1135 } 1136 if ((which & FLAG_LOCK) != 0) { 1137 logger.onLockLiveWallpaperRestoreFailed( 1138 WallpaperEventLogger.ERROR_SET_DESCRIPTION_EXCEPTION); 1139 } 1140 } 1141 } else if (componentName.getPackageName().equals(packageName)) { 1142 Slog.d(TAG, "Applying component " + componentName); 1143 boolean success = mWallpaperManager.setWallpaperComponentWithFlags( 1144 componentName, which); 1145 WallpaperEventLogger logger = new WallpaperEventLogger( 1146 mBackupManager.getDelayedRestoreLogger()); 1147 if (success) { 1148 if ((which & FLAG_SYSTEM) != 0) { 1149 logger.onSystemLiveWallpaperRestored(componentName); 1150 } 1151 if ((which & FLAG_LOCK) != 0) { 1152 logger.onLockLiveWallpaperRestored(componentName); 1153 } 1154 } else { 1155 if ((which & FLAG_SYSTEM) != 0) { 1156 logger.onSystemLiveWallpaperRestoreFailed( 1157 WallpaperEventLogger.ERROR_SET_COMPONENT_EXCEPTION); 1158 } 1159 if ((which & FLAG_LOCK) != 0) { 1160 logger.onLockLiveWallpaperRestoreFailed( 1161 WallpaperEventLogger.ERROR_SET_COMPONENT_EXCEPTION); 1162 } 1163 } 1164 // We're only expecting to restore the wallpaper component once. 1165 unregister(); 1166 mBackupManager.reportDelayedRestoreResult(logger.getBackupRestoreLogger()); 1167 } 1168 } 1169 }; 1170 } 1171 1172 /** 1173 * This method retrieves the dimensions of the largest display of the device 1174 * 1175 * @return a @{Point} object that contains the dimensions of the largest display on the device 1176 */ 1177 private Point getScreenDimensions() { 1178 Point largetDimensions = null; 1179 int maxArea = 0; 1180 1181 for (Display display : getInternalDisplays()) { 1182 Point displaySize = getRealSize(display); 1183 1184 int width = displaySize.x; 1185 int height = displaySize.y; 1186 int area = width * height; 1187 1188 if (area > maxArea) { 1189 maxArea = area; 1190 largetDimensions = displaySize; 1191 } 1192 } 1193 1194 return largetDimensions; 1195 } 1196 1197 private Point getRealSize(Display display) { 1198 DisplayInfo displayInfo = new DisplayInfo(); 1199 display.getDisplayInfo(displayInfo); 1200 return new Point(displayInfo.logicalWidth, displayInfo.logicalHeight); 1201 } 1202 1203 /** 1204 * This method returns the smaller display on a multi-display device 1205 * 1206 * @return Display that corresponds to the smaller display on a device or null if ther is only 1207 * one Display on a device 1208 */ 1209 private Display getSmallerDisplayIfExists() { 1210 List<Display> internalDisplays = getInternalDisplays(); 1211 Point largestDisplaySize = getScreenDimensions(); 1212 1213 // Find the first non-matching internal display 1214 for (Display display : internalDisplays) { 1215 Point displaySize = getRealSize(display); 1216 if (displaySize.x != largestDisplaySize.x || displaySize.y != largestDisplaySize.y) { 1217 return display; 1218 } 1219 } 1220 1221 // If no smaller display found, return null, as there is only a single display 1222 return null; 1223 } 1224 1225 /** 1226 * This method retrieves the collection of Display objects available in the device. 1227 * i.e. non-external displays are ignored 1228 * 1229 * @return list of displays corresponding to each display in the device 1230 */ 1231 private List<Display> getInternalDisplays() { 1232 Display[] allDisplays = mDisplayManager.getDisplays( 1233 DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED); 1234 1235 List<Display> internalDisplays = new ArrayList<>(); 1236 for (Display display : allDisplays) { 1237 if (display.getType() == Display.TYPE_INTERNAL) { 1238 internalDisplays.add(display); 1239 } 1240 } 1241 return internalDisplays; 1242 } 1243 1244 @VisibleForTesting 1245 boolean isDeviceInRestore() { 1246 try { 1247 boolean isInSetup = Settings.Secure.getInt(getBaseContext().getContentResolver(), 1248 Settings.Secure.USER_SETUP_COMPLETE) == 0; 1249 boolean isInDeferredSetup = Settings.Secure.getInt(getBaseContext() 1250 .getContentResolver(), 1251 Settings.Secure.USER_SETUP_PERSONALIZATION_STATE) == 1252 Settings.Secure.USER_SETUP_PERSONALIZATION_STARTED; 1253 return isInSetup || isInDeferredSetup; 1254 } catch (Settings.SettingNotFoundException e) { 1255 Slog.w(TAG, "Failed to check if the user is in restore: " + e); 1256 return false; 1257 } 1258 } 1259 1260 @VisibleForTesting 1261 void setBackupManagerForTesting(BackupManager backupManager) { 1262 mBackupManager = backupManager; 1263 } 1264 } 1265