1 package com.android.launcher3; 2 3 import static com.android.launcher3.LauncherPrefs.NO_DB_FILES_RESTORED; 4 5 import android.app.backup.BackupAgent; 6 import android.app.backup.BackupDataInput; 7 import android.app.backup.BackupDataOutput; 8 import android.os.ParcelFileDescriptor; 9 10 import com.android.launcher3.logging.FileLog; 11 import com.android.launcher3.provider.RestoreDbTask; 12 13 import java.io.File; 14 import java.io.IOException; 15 import java.util.Arrays; 16 import java.util.stream.Collectors; 17 18 public class LauncherBackupAgent extends BackupAgent { 19 private static final String TAG = "LauncherBackupAgent"; 20 private static final String DB_FILE_PREFIX = "launcher"; 21 private static final String DB_FILE_SUFFIX = ".db"; 22 23 @Override onCreate()24 public void onCreate() { 25 super.onCreate(); 26 // Set the log dir as LauncherAppState is not initialized during restore. 27 FileLog.setDir(getFilesDir()); 28 } 29 30 @Override onRestore( BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)31 public void onRestore( 32 BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) { 33 // Doesn't do incremental backup/restore 34 } 35 36 @Override onRestoreFile(ParcelFileDescriptor data, long size, File destination, int type, long mode, long mtime)37 public void onRestoreFile(ParcelFileDescriptor data, long size, File destination, int type, 38 long mode, long mtime) throws IOException { 39 // Remove old files which might contain obsolete attributes like idp_grid_name in shared 40 // preference that will obstruct backup's attribute from writing to shared preferences. 41 if (destination.delete()) { 42 FileLog.d(TAG, "onRestoreFile: Removed obsolete file " + destination); 43 } 44 super.onRestoreFile(data, size, destination, type, mode, mtime); 45 } 46 47 @Override onBackup( ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState)48 public void onBackup( 49 ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) { 50 // Doesn't do incremental backup/restore 51 } 52 53 @Override onRestoreFinished()54 public void onRestoreFinished() { 55 RestoreDbTask.setPending(this); 56 FileLog.d(TAG, "onRestoreFinished: set pending for RestoreDbTask"); 57 markIfFilesWereNotActuallyRestored(); 58 } 59 60 /** 61 * When restore is finished, we check to see if any db files were successfully restored. If not, 62 * our restore will fail later, but will report a different cause. This is important to split 63 * out the metric failures that are launcher's fault, and those that are due to bugs in the 64 * backup/restore code itself. 65 */ markIfFilesWereNotActuallyRestored()66 private void markIfFilesWereNotActuallyRestored() { 67 File directory = new File(getDatabasePath(InvariantDeviceProfile.INSTANCE.get(this).dbFile) 68 .getParent()); 69 if (!directory.exists()) { 70 FileLog.e(TAG, "restore failed as target database directory doesn't exist"); 71 } else { 72 // Check for any db file that was restored, and collect as list 73 String fileNames = Arrays.stream(directory.listFiles()) 74 .map(File::getName) 75 .filter(n -> n.startsWith(DB_FILE_PREFIX) && n.endsWith(DB_FILE_SUFFIX)) 76 .collect(Collectors.joining(", ")); 77 if (fileNames.isBlank()) { 78 FileLog.e(TAG, "no database files were successfully restored"); 79 LauncherPrefs.get(this).putSync(NO_DB_FILES_RESTORED.to(true)); 80 } else { 81 FileLog.d(TAG, "database files successfully restored: " + fileNames); 82 } 83 } 84 } 85 } 86