1 package com.google.android.libraries.backup.shadow; 2 3 import android.app.backup.FileBackupHelper; 4 import android.content.Context; 5 import android.util.Log; 6 import com.google.common.annotations.VisibleForTesting; 7 import com.google.common.base.Preconditions; 8 import com.google.common.collect.ImmutableMap; 9 import com.google.common.collect.ImmutableSet; 10 import com.google.common.io.Files; 11 import java.io.File; 12 import java.io.IOException; 13 import java.lang.reflect.Field; 14 import java.util.Arrays; 15 import java.util.Map; 16 import java.util.Set; 17 18 /** 19 * Representation of {@link FileBackupHelper} configuration used for testing. This class simulates 20 * backing up and restoring files by storing their contents in memory. 21 * 22 * <p>{@see BackupAgentHelperShadow} 23 */ 24 public class FileBackupHelperSimulator extends BackupHelperSimulator { 25 private static final String TAG = "FileBackupHelperSimulat"; 26 27 /** Filenames which should be backed up/restored. */ 28 private final Set<String> fileNames; 29 FileBackupHelperSimulator(String keyPrefix, Set<String> fileNames)30 private FileBackupHelperSimulator(String keyPrefix, Set<String> fileNames) { 31 super(keyPrefix); 32 this.fileNames = Preconditions.checkNotNull(fileNames); 33 } 34 fromFileNames(String keyPrefix, Set<String> fileNames)35 public static FileBackupHelperSimulator fromFileNames(String keyPrefix, Set<String> fileNames) { 36 return new FileBackupHelperSimulator(keyPrefix, fileNames); 37 } 38 fromHelper(String keyPrefix, FileBackupHelper helper)39 public static FileBackupHelperSimulator fromHelper(String keyPrefix, FileBackupHelper helper) { 40 return new FileBackupHelperSimulator(keyPrefix, extractFileNamesFromHelper(helper)); 41 } 42 43 @VisibleForTesting extractFileNamesFromHelper(FileBackupHelper helper)44 static Set<String> extractFileNamesFromHelper(FileBackupHelper helper) { 45 try { 46 Field filesField = FileBackupHelper.class.getDeclaredField("mFiles"); 47 filesField.setAccessible(true); 48 return ImmutableSet.copyOf((String[]) filesField.get(helper)); 49 } catch (ReflectiveOperationException e) { 50 throw new IllegalStateException(e); 51 } 52 } 53 54 /** Collection of backed up files. */ 55 public static class FilesBackupData { 56 /** Map from file names to their backed up contents. */ 57 private final Map<String, FileBackupContents> files; 58 FilesBackupData(Map<String, FileBackupContents> files)59 public FilesBackupData(Map<String, FileBackupContents> files) { 60 this.files = Preconditions.checkNotNull(files); 61 } 62 63 @Override equals(Object obj)64 public boolean equals(Object obj) { 65 return obj instanceof FilesBackupData && files.equals(((FilesBackupData) obj).files); 66 } 67 68 @Override hashCode()69 public int hashCode() { 70 return files.hashCode(); 71 } 72 getFiles()73 public Map<String, FileBackupContents> getFiles() { 74 return files; 75 } 76 } 77 78 /** Single backed up file. */ 79 public static class FileBackupContents { 80 private final byte[] bytes; 81 FileBackupContents(byte[] bytes)82 public FileBackupContents(byte[] bytes) { 83 this.bytes = Preconditions.checkNotNull(bytes); 84 } 85 86 @Override equals(Object obj)87 public boolean equals(Object obj) { 88 return obj instanceof FileBackupContents 89 && Arrays.equals(bytes, ((FileBackupContents) obj).bytes); 90 } 91 92 @Override hashCode()93 public int hashCode() { 94 return Arrays.hashCode(bytes); 95 } 96 readFromFile(File file)97 public static FileBackupContents readFromFile(File file) { 98 return new FileBackupContents(readBytesFromFile(file)); 99 } 100 writeToFile(File file)101 public void writeToFile(File file) { 102 writeBytesToFile(file, bytes); 103 } 104 readBytesFromFile(File file)105 public static byte[] readBytesFromFile(File file) { 106 try { 107 return Files.toByteArray(file); 108 } catch (IOException e) { 109 throw new IllegalStateException(e); 110 } 111 } 112 writeBytesToFile(File file, byte[] bytes)113 public static void writeBytesToFile(File file, byte[] bytes) { 114 try { 115 Files.write(bytes, file); 116 } catch (IOException e) { 117 throw new IllegalStateException(e); 118 } 119 } 120 } 121 122 @Override backup(Context context)123 public Object backup(Context context) { 124 File base = context.getFilesDir(); 125 ImmutableMap.Builder<String, FileBackupContents> dataToBackupBuilder = ImmutableMap.builder(); 126 for (String fileName : fileNames) { 127 File file = new File(base, fileName); 128 if (!file.exists()) { 129 Log.w(TAG, "File \"" + fileName + "\" not found by helper \"" + keyPrefix + "\"."); 130 continue; 131 } 132 dataToBackupBuilder.put(fileName, FileBackupContents.readFromFile(file)); 133 } 134 return new FilesBackupData(dataToBackupBuilder.build()); 135 } 136 137 @Override restore(Context context, Object data)138 public void restore(Context context, Object data) { 139 if (!(data instanceof FilesBackupData)) { 140 throw new IllegalArgumentException("Invalid type of files to restore in helper \"" 141 + keyPrefix + "\": " + data.getClass()); 142 } 143 144 File base = context.getFilesDir(); 145 Map<String, FileBackupContents> dataToRestore = ((FilesBackupData) data).getFiles(); 146 for (Map.Entry<String, FileBackupContents> restoreEntry : dataToRestore.entrySet()) { 147 String fileName = restoreEntry.getKey(); 148 if (!fileNames.contains(fileName)) { 149 Log.w(TAG, "File \"" + fileName + "\" ignored by helper \"" + keyPrefix + "\"."); 150 continue; 151 } 152 FileBackupContents contents = restoreEntry.getValue(); 153 File file = new File(base, fileName); 154 contents.writeToFile(file); 155 } 156 } 157 } 158