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