| package com.android.server.backup.fullbackup; |
| |
| import static com.android.server.backup.BackupManagerService.MORE_DEBUG; |
| import static com.android.server.backup.BackupManagerService.TAG; |
| import static com.android.server.backup.UserBackupManagerService.BACKUP_MANIFEST_VERSION; |
| import static com.android.server.backup.UserBackupManagerService.BACKUP_METADATA_VERSION; |
| import static com.android.server.backup.UserBackupManagerService.BACKUP_WIDGET_METADATA_TOKEN; |
| |
| import android.annotation.Nullable; |
| import android.annotation.UserIdInt; |
| import android.app.backup.FullBackup; |
| import android.app.backup.FullBackupDataOutput; |
| import android.content.pm.PackageInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.Signature; |
| import android.content.pm.SigningInfo; |
| import android.os.Build; |
| import android.os.Environment; |
| import android.util.Log; |
| import android.util.StringBuilderPrinter; |
| |
| import com.android.internal.util.Preconditions; |
| |
| import java.io.BufferedOutputStream; |
| import java.io.DataOutputStream; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| |
| /** |
| * Writes the backup of app-specific metadata to {@link FullBackupDataOutput}. This data is not |
| * backed up by the app's backup agent and is written before the agent writes its own data. This |
| * includes the app's: |
| * |
| * <ul> |
| * <li>manifest |
| * <li>widget data |
| * <li>apk |
| * <li>obb content |
| * </ul> |
| */ |
| // TODO(b/113807190): Fix or remove apk and obb implementation (only used for adb). |
| public class AppMetadataBackupWriter { |
| private final FullBackupDataOutput mOutput; |
| private final PackageManager mPackageManager; |
| |
| /** The destination of the backup is specified by {@code output}. */ |
| public AppMetadataBackupWriter(FullBackupDataOutput output, PackageManager packageManager) { |
| mOutput = output; |
| mPackageManager = packageManager; |
| } |
| |
| /** |
| * Back up the app's manifest without specifying a pseudo-directory for the TAR stream. |
| * |
| * @see #backupManifest(PackageInfo, File, File, String, String, boolean) |
| */ |
| public void backupManifest( |
| PackageInfo packageInfo, File manifestFile, File filesDir, boolean withApk) |
| throws IOException { |
| backupManifest( |
| packageInfo, |
| manifestFile, |
| filesDir, |
| /* domain */ null, |
| /* linkDomain */ null, |
| withApk); |
| } |
| |
| /** |
| * Back up the app's manifest. |
| * |
| * <ol> |
| * <li>Write the app's manifest data to the specified temporary file {@code manifestFile}. |
| * <li>Backup the file in TAR format to the backup destination {@link #mOutput}. |
| * </ol> |
| * |
| * <p>Note: {@code domain} and {@code linkDomain} are only used by adb to specify a |
| * pseudo-directory for the TAR stream. |
| */ |
| // TODO(b/113806991): Look into streaming the backup data directly. |
| public void backupManifest( |
| PackageInfo packageInfo, |
| File manifestFile, |
| File filesDir, |
| @Nullable String domain, |
| @Nullable String linkDomain, |
| boolean withApk) |
| throws IOException { |
| byte[] manifestBytes = getManifestBytes(packageInfo, withApk); |
| FileOutputStream outputStream = new FileOutputStream(manifestFile); |
| outputStream.write(manifestBytes); |
| outputStream.close(); |
| |
| // We want the manifest block in the archive stream to be constant each time we generate |
| // a backup stream for the app. However, the underlying TAR mechanism sees it as a file and |
| // will propagate its last modified time. We pin the last modified time to zero to prevent |
| // the TAR header from varying. |
| manifestFile.setLastModified(0); |
| |
| FullBackup.backupToTar( |
| packageInfo.packageName, |
| domain, |
| linkDomain, |
| filesDir.getAbsolutePath(), |
| manifestFile.getAbsolutePath(), |
| mOutput); |
| } |
| |
| /** |
| * Gets the app's manifest as a byte array. All data are strings ending in LF. |
| * |
| * <p>The manifest format is: |
| * |
| * <pre> |
| * BACKUP_MANIFEST_VERSION |
| * package name |
| * package version code |
| * platform version code |
| * installer package name (can be empty) |
| * boolean (1 if archive includes .apk, otherwise 0) |
| * # of signatures N |
| * N* (signature byte array in ascii format per Signature.toCharsString()) |
| * </pre> |
| */ |
| private byte[] getManifestBytes(PackageInfo packageInfo, boolean withApk) { |
| String packageName = packageInfo.packageName; |
| StringBuilder builder = new StringBuilder(4096); |
| StringBuilderPrinter printer = new StringBuilderPrinter(builder); |
| |
| printer.println(Integer.toString(BACKUP_MANIFEST_VERSION)); |
| printer.println(packageName); |
| printer.println(Long.toString(packageInfo.getLongVersionCode())); |
| printer.println(Integer.toString(Build.VERSION.SDK_INT)); |
| |
| String installerName = mPackageManager.getInstallerPackageName(packageName); |
| printer.println((installerName != null) ? installerName : ""); |
| |
| printer.println(withApk ? "1" : "0"); |
| |
| // Write the signature block. |
| SigningInfo signingInfo = packageInfo.signingInfo; |
| if (signingInfo == null) { |
| printer.println("0"); |
| } else { |
| // Retrieve the newest signatures to write. |
| // TODO (b/73988180) use entire signing history in case of rollbacks. |
| Signature[] signatures = signingInfo.getApkContentsSigners(); |
| printer.println(Integer.toString(signatures.length)); |
| for (Signature sig : signatures) { |
| printer.println(sig.toCharsString()); |
| } |
| } |
| return builder.toString().getBytes(); |
| } |
| |
| /** |
| * Backup specified widget data. The widget data is prefaced by a metadata header. |
| * |
| * <ol> |
| * <li>Write a metadata header to the specified temporary file {@code metadataFile}. |
| * <li>Write widget data bytes to the same file. |
| * <li>Backup the file in TAR format to the backup destination {@link #mOutput}. |
| * </ol> |
| * |
| * @throws IllegalArgumentException if the widget data provided is empty. |
| */ |
| // TODO(b/113806991): Look into streaming the backup data directly. |
| public void backupWidget( |
| PackageInfo packageInfo, File metadataFile, File filesDir, byte[] widgetData) |
| throws IOException { |
| Preconditions.checkArgument(widgetData.length > 0, "Can't backup widget with no data."); |
| |
| String packageName = packageInfo.packageName; |
| FileOutputStream fileOutputStream = new FileOutputStream(metadataFile); |
| BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream); |
| DataOutputStream dataOutputStream = new DataOutputStream(bufferedOutputStream); |
| |
| byte[] metadata = getMetadataBytes(packageName); |
| bufferedOutputStream.write(metadata); // bypassing DataOutputStream |
| writeWidgetData(dataOutputStream, widgetData); |
| bufferedOutputStream.flush(); |
| dataOutputStream.close(); |
| |
| // As with the manifest file, guarantee consistency of the archive metadata for the widget |
| // block by using a fixed last modified time on the metadata file. |
| metadataFile.setLastModified(0); |
| |
| FullBackup.backupToTar( |
| packageName, |
| /* domain */ null, |
| /* linkDomain */ null, |
| filesDir.getAbsolutePath(), |
| metadataFile.getAbsolutePath(), |
| mOutput); |
| } |
| |
| /** |
| * Gets the app's metadata as a byte array. All entries are strings ending in LF. |
| * |
| * <p>The metadata format is: |
| * |
| * <pre> |
| * BACKUP_METADATA_VERSION |
| * package name |
| * </pre> |
| */ |
| private byte[] getMetadataBytes(String packageName) { |
| StringBuilder builder = new StringBuilder(512); |
| StringBuilderPrinter printer = new StringBuilderPrinter(builder); |
| printer.println(Integer.toString(BACKUP_METADATA_VERSION)); |
| printer.println(packageName); |
| return builder.toString().getBytes(); |
| } |
| |
| /** |
| * Write a byte array of widget data to the specified output stream. All integers are binary in |
| * network byte order. |
| * |
| * <p>The widget data format: |
| * |
| * <pre> |
| * 4 : Integer token identifying the widget data blob. |
| * 4 : Integer size of the widget data. |
| * N : Raw bytes of the widget data. |
| * </pre> |
| */ |
| private void writeWidgetData(DataOutputStream out, byte[] widgetData) throws IOException { |
| out.writeInt(BACKUP_WIDGET_METADATA_TOKEN); |
| out.writeInt(widgetData.length); |
| out.write(widgetData); |
| } |
| |
| /** |
| * Backup the app's .apk to the backup destination {@link #mOutput}. Currently only used for |
| * 'adb backup'. |
| */ |
| // TODO(b/113807190): Investigate and potentially remove. |
| public void backupApk(PackageInfo packageInfo) { |
| // TODO: handle backing up split APKs |
| String appSourceDir = packageInfo.applicationInfo.getBaseCodePath(); |
| String apkDir = new File(appSourceDir).getParent(); |
| FullBackup.backupToTar( |
| packageInfo.packageName, |
| FullBackup.APK_TREE_TOKEN, |
| /* linkDomain */ null, |
| apkDir, |
| appSourceDir, |
| mOutput); |
| } |
| |
| /** |
| * Backup the app's .obb files to the backup destination {@link #mOutput}. Currently only used |
| * for 'adb backup'. |
| */ |
| // TODO(b/113807190): Investigate and potentially remove. |
| public void backupObb(@UserIdInt int userId, PackageInfo packageInfo) { |
| // TODO: migrate this to SharedStorageBackup, since AID_SYSTEM doesn't have access to |
| // external storage. |
| Environment.UserEnvironment userEnv = |
| new Environment.UserEnvironment(userId); |
| File obbDir = userEnv.buildExternalStorageAppObbDirs(packageInfo.packageName)[0]; |
| if (obbDir != null) { |
| if (MORE_DEBUG) { |
| Log.i(TAG, "obb dir: " + obbDir.getAbsolutePath()); |
| } |
| File[] obbFiles = obbDir.listFiles(); |
| if (obbFiles != null) { |
| String obbDirName = obbDir.getAbsolutePath(); |
| for (File obb : obbFiles) { |
| FullBackup.backupToTar( |
| packageInfo.packageName, |
| FullBackup.OBB_TREE_TOKEN, |
| /* linkDomain */ null, |
| obbDirName, |
| obb.getAbsolutePath(), |
| mOutput); |
| } |
| } |
| } |
| } |
| } |