blob: d13f71116c8108fc748d87faaf1c2fb8e9fa7faf [file] [log] [blame]
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);
}
}
}
}
}