1 package com.android.server.backup.fullbackup; 2 3 import static com.android.server.backup.BackupManagerService.MORE_DEBUG; 4 import static com.android.server.backup.BackupManagerService.TAG; 5 import static com.android.server.backup.UserBackupManagerService.BACKUP_MANIFEST_VERSION; 6 import static com.android.server.backup.UserBackupManagerService.BACKUP_METADATA_VERSION; 7 import static com.android.server.backup.UserBackupManagerService.BACKUP_WIDGET_METADATA_TOKEN; 8 9 import android.annotation.Nullable; 10 import android.annotation.UserIdInt; 11 import android.app.backup.FullBackup; 12 import android.app.backup.FullBackupDataOutput; 13 import android.content.pm.PackageInfo; 14 import android.content.pm.PackageManager; 15 import android.content.pm.Signature; 16 import android.content.pm.SigningInfo; 17 import android.os.Build; 18 import android.os.Environment; 19 import android.util.Log; 20 import android.util.StringBuilderPrinter; 21 22 import com.android.internal.util.Preconditions; 23 24 import java.io.BufferedOutputStream; 25 import java.io.DataOutputStream; 26 import java.io.File; 27 import java.io.FileOutputStream; 28 import java.io.IOException; 29 30 /** 31 * Writes the backup of app-specific metadata to {@link FullBackupDataOutput}. This data is not 32 * backed up by the app's backup agent and is written before the agent writes its own data. This 33 * includes the app's: 34 * 35 * <ul> 36 * <li>manifest 37 * <li>widget data 38 * <li>apk 39 * <li>obb content 40 * </ul> 41 */ 42 // TODO(b/113807190): Fix or remove apk and obb implementation (only used for adb). 43 public class AppMetadataBackupWriter { 44 private final FullBackupDataOutput mOutput; 45 private final PackageManager mPackageManager; 46 47 /** The destination of the backup is specified by {@code output}. */ AppMetadataBackupWriter(FullBackupDataOutput output, PackageManager packageManager)48 public AppMetadataBackupWriter(FullBackupDataOutput output, PackageManager packageManager) { 49 mOutput = output; 50 mPackageManager = packageManager; 51 } 52 53 /** 54 * Back up the app's manifest without specifying a pseudo-directory for the TAR stream. 55 * 56 * @see #backupManifest(PackageInfo, File, File, String, String, boolean) 57 */ backupManifest( PackageInfo packageInfo, File manifestFile, File filesDir, boolean withApk)58 public void backupManifest( 59 PackageInfo packageInfo, File manifestFile, File filesDir, boolean withApk) 60 throws IOException { 61 backupManifest( 62 packageInfo, 63 manifestFile, 64 filesDir, 65 /* domain */ null, 66 /* linkDomain */ null, 67 withApk); 68 } 69 70 /** 71 * Back up the app's manifest. 72 * 73 * <ol> 74 * <li>Write the app's manifest data to the specified temporary file {@code manifestFile}. 75 * <li>Backup the file in TAR format to the backup destination {@link #mOutput}. 76 * </ol> 77 * 78 * <p>Note: {@code domain} and {@code linkDomain} are only used by adb to specify a 79 * pseudo-directory for the TAR stream. 80 */ 81 // TODO(b/113806991): Look into streaming the backup data directly. backupManifest( PackageInfo packageInfo, File manifestFile, File filesDir, @Nullable String domain, @Nullable String linkDomain, boolean withApk)82 public void backupManifest( 83 PackageInfo packageInfo, 84 File manifestFile, 85 File filesDir, 86 @Nullable String domain, 87 @Nullable String linkDomain, 88 boolean withApk) 89 throws IOException { 90 byte[] manifestBytes = getManifestBytes(packageInfo, withApk); 91 FileOutputStream outputStream = new FileOutputStream(manifestFile); 92 outputStream.write(manifestBytes); 93 outputStream.close(); 94 95 // We want the manifest block in the archive stream to be constant each time we generate 96 // a backup stream for the app. However, the underlying TAR mechanism sees it as a file and 97 // will propagate its last modified time. We pin the last modified time to zero to prevent 98 // the TAR header from varying. 99 manifestFile.setLastModified(0); 100 101 FullBackup.backupToTar( 102 packageInfo.packageName, 103 domain, 104 linkDomain, 105 filesDir.getAbsolutePath(), 106 manifestFile.getAbsolutePath(), 107 mOutput); 108 } 109 110 /** 111 * Gets the app's manifest as a byte array. All data are strings ending in LF. 112 * 113 * <p>The manifest format is: 114 * 115 * <pre> 116 * BACKUP_MANIFEST_VERSION 117 * package name 118 * package version code 119 * platform version code 120 * installer package name (can be empty) 121 * boolean (1 if archive includes .apk, otherwise 0) 122 * # of signatures N 123 * N* (signature byte array in ascii format per Signature.toCharsString()) 124 * </pre> 125 */ getManifestBytes(PackageInfo packageInfo, boolean withApk)126 private byte[] getManifestBytes(PackageInfo packageInfo, boolean withApk) { 127 String packageName = packageInfo.packageName; 128 StringBuilder builder = new StringBuilder(4096); 129 StringBuilderPrinter printer = new StringBuilderPrinter(builder); 130 131 printer.println(Integer.toString(BACKUP_MANIFEST_VERSION)); 132 printer.println(packageName); 133 printer.println(Long.toString(packageInfo.getLongVersionCode())); 134 printer.println(Integer.toString(Build.VERSION.SDK_INT)); 135 136 String installerName = mPackageManager.getInstallerPackageName(packageName); 137 printer.println((installerName != null) ? installerName : ""); 138 139 printer.println(withApk ? "1" : "0"); 140 141 // Write the signature block. 142 SigningInfo signingInfo = packageInfo.signingInfo; 143 if (signingInfo == null) { 144 printer.println("0"); 145 } else { 146 // Retrieve the newest signatures to write. 147 // TODO (b/73988180) use entire signing history in case of rollbacks. 148 Signature[] signatures = signingInfo.getApkContentsSigners(); 149 printer.println(Integer.toString(signatures.length)); 150 for (Signature sig : signatures) { 151 printer.println(sig.toCharsString()); 152 } 153 } 154 return builder.toString().getBytes(); 155 } 156 157 /** 158 * Backup specified widget data. The widget data is prefaced by a metadata header. 159 * 160 * <ol> 161 * <li>Write a metadata header to the specified temporary file {@code metadataFile}. 162 * <li>Write widget data bytes to the same file. 163 * <li>Backup the file in TAR format to the backup destination {@link #mOutput}. 164 * </ol> 165 * 166 * @throws IllegalArgumentException if the widget data provided is empty. 167 */ 168 // TODO(b/113806991): Look into streaming the backup data directly. backupWidget( PackageInfo packageInfo, File metadataFile, File filesDir, byte[] widgetData)169 public void backupWidget( 170 PackageInfo packageInfo, File metadataFile, File filesDir, byte[] widgetData) 171 throws IOException { 172 Preconditions.checkArgument(widgetData.length > 0, "Can't backup widget with no data."); 173 174 String packageName = packageInfo.packageName; 175 FileOutputStream fileOutputStream = new FileOutputStream(metadataFile); 176 BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream); 177 DataOutputStream dataOutputStream = new DataOutputStream(bufferedOutputStream); 178 179 byte[] metadata = getMetadataBytes(packageName); 180 bufferedOutputStream.write(metadata); // bypassing DataOutputStream 181 writeWidgetData(dataOutputStream, widgetData); 182 bufferedOutputStream.flush(); 183 dataOutputStream.close(); 184 185 // As with the manifest file, guarantee consistency of the archive metadata for the widget 186 // block by using a fixed last modified time on the metadata file. 187 metadataFile.setLastModified(0); 188 189 FullBackup.backupToTar( 190 packageName, 191 /* domain */ null, 192 /* linkDomain */ null, 193 filesDir.getAbsolutePath(), 194 metadataFile.getAbsolutePath(), 195 mOutput); 196 } 197 198 /** 199 * Gets the app's metadata as a byte array. All entries are strings ending in LF. 200 * 201 * <p>The metadata format is: 202 * 203 * <pre> 204 * BACKUP_METADATA_VERSION 205 * package name 206 * </pre> 207 */ getMetadataBytes(String packageName)208 private byte[] getMetadataBytes(String packageName) { 209 StringBuilder builder = new StringBuilder(512); 210 StringBuilderPrinter printer = new StringBuilderPrinter(builder); 211 printer.println(Integer.toString(BACKUP_METADATA_VERSION)); 212 printer.println(packageName); 213 return builder.toString().getBytes(); 214 } 215 216 /** 217 * Write a byte array of widget data to the specified output stream. All integers are binary in 218 * network byte order. 219 * 220 * <p>The widget data format: 221 * 222 * <pre> 223 * 4 : Integer token identifying the widget data blob. 224 * 4 : Integer size of the widget data. 225 * N : Raw bytes of the widget data. 226 * </pre> 227 */ writeWidgetData(DataOutputStream out, byte[] widgetData)228 private void writeWidgetData(DataOutputStream out, byte[] widgetData) throws IOException { 229 out.writeInt(BACKUP_WIDGET_METADATA_TOKEN); 230 out.writeInt(widgetData.length); 231 out.write(widgetData); 232 } 233 234 /** 235 * Backup the app's .apk to the backup destination {@link #mOutput}. Currently only used for 236 * 'adb backup'. 237 */ 238 // TODO(b/113807190): Investigate and potentially remove. backupApk(PackageInfo packageInfo)239 public void backupApk(PackageInfo packageInfo) { 240 // TODO: handle backing up split APKs 241 String appSourceDir = packageInfo.applicationInfo.getBaseCodePath(); 242 String apkDir = new File(appSourceDir).getParent(); 243 FullBackup.backupToTar( 244 packageInfo.packageName, 245 FullBackup.APK_TREE_TOKEN, 246 /* linkDomain */ null, 247 apkDir, 248 appSourceDir, 249 mOutput); 250 } 251 252 /** 253 * Backup the app's .obb files to the backup destination {@link #mOutput}. Currently only used 254 * for 'adb backup'. 255 */ 256 // TODO(b/113807190): Investigate and potentially remove. backupObb(@serIdInt int userId, PackageInfo packageInfo)257 public void backupObb(@UserIdInt int userId, PackageInfo packageInfo) { 258 // TODO: migrate this to SharedStorageBackup, since AID_SYSTEM doesn't have access to 259 // external storage. 260 Environment.UserEnvironment userEnv = 261 new Environment.UserEnvironment(userId); 262 File obbDir = userEnv.buildExternalStorageAppObbDirs(packageInfo.packageName)[0]; 263 if (obbDir != null) { 264 if (MORE_DEBUG) { 265 Log.i(TAG, "obb dir: " + obbDir.getAbsolutePath()); 266 } 267 File[] obbFiles = obbDir.listFiles(); 268 if (obbFiles != null) { 269 String obbDirName = obbDir.getAbsolutePath(); 270 for (File obb : obbFiles) { 271 FullBackup.backupToTar( 272 packageInfo.packageName, 273 FullBackup.OBB_TREE_TOKEN, 274 /* linkDomain */ null, 275 obbDirName, 276 obb.getAbsolutePath(), 277 mOutput); 278 } 279 } 280 } 281 } 282 } 283