Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1dbac1a5 authored by Annie Meng's avatar Annie Meng Committed by Android (Google) Code Review
Browse files

Merge "[FBR] Extract app metadata backup to helper"

parents b2fa27cc 13746906
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.server;

import android.annotation.Nullable;

import java.util.List;

/**
@@ -37,6 +39,8 @@ public class AppWidgetBackupBridge {
                : null;
    }

    /** Returns a byte array of widget data for the specified package or {@code null}. */
    @Nullable
    public static byte[] getWidgetState(String packageName, int userId) {
        return (sAppWidgetService != null)
                ? sAppWidgetService.getWidgetState(packageName, userId)
+12 −7
Original line number Diff line number Diff line
@@ -9,9 +9,9 @@ import static com.android.server.backup.BackupManagerService.OP_TYPE_BACKUP_WAIT

import android.app.ApplicationThreadConstants;
import android.app.IBackupAgent;
import android.app.backup.IBackupCallback;
import android.app.backup.FullBackup;
import android.app.backup.FullBackupDataOutput;
import android.app.backup.IBackupCallback;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -21,6 +21,7 @@ import android.os.SELinux;
import android.util.Slog;

import com.android.internal.util.Preconditions;
import com.android.server.backup.fullbackup.AppMetadataBackupWriter;
import com.android.server.backup.remote.ServiceBackupCallback;
import com.android.server.backup.utils.FullBackupUtils;

@@ -202,16 +203,20 @@ public class KeyValueAdbBackupEngine {
        public void run() {
            try {
                FullBackupDataOutput output = new FullBackupDataOutput(mPipe);
                AppMetadataBackupWriter writer =
                        new AppMetadataBackupWriter(output, mPackageManager);

                if (DEBUG) {
                    Slog.d(TAG, "Writing manifest for " + mPackage.packageName);
                }
                FullBackupUtils.writeAppManifest(
                        mPackage, mPackageManager, mManifestFile, false, false);
                FullBackup.backupToTar(mPackage.packageName, FullBackup.KEY_VALUE_DATA_TOKEN, null,
                        mDataDir.getAbsolutePath(),
                        mManifestFile.getAbsolutePath(),
                        output);

                writer.backupManifest(
                        mPackage,
                        mManifestFile,
                        mDataDir,
                        FullBackup.KEY_VALUE_DATA_TOKEN,
                        /* linkDomain */ null,
                        /* withApk */ false);
                mManifestFile.delete();

                if (DEBUG) {
+283 −0
Original line number Diff line number Diff line
package com.android.server.backup.fullbackup;

import static com.android.server.backup.BackupManagerService.BACKUP_MANIFEST_VERSION;
import static com.android.server.backup.BackupManagerService.BACKUP_METADATA_VERSION;
import static com.android.server.backup.BackupManagerService.BACKUP_WIDGET_METADATA_TOKEN;
import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
import static com.android.server.backup.BackupManagerService.TAG;

import android.annotation.Nullable;
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.os.UserHandle;
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(PackageInfo packageInfo) {
        // TODO: migrate this to SharedStorageBackup, since AID_SYSTEM doesn't have access to
        // external storage.
        // TODO: http://b/22388012
        Environment.UserEnvironment userEnv =
                new Environment.UserEnvironment(UserHandle.USER_SYSTEM);
        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);
                }
            }
        }
    }
}
+102 −168

File changed.

Preview size limit exceeded, changes collapsed.

+0 −72
Original line number Diff line number Diff line
@@ -16,23 +16,14 @@

package com.android.server.backup.utils;

import static com.android.server.backup.BackupManagerService.BACKUP_MANIFEST_VERSION;
import static com.android.server.backup.BackupManagerService.TAG;

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.ParcelFileDescriptor;
import android.util.Slog;
import android.util.StringBuilderPrinter;

import java.io.DataInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

@@ -68,67 +59,4 @@ public class FullBackupUtils {
            }
        }
    }

    /**
     * Writes app manifest to the given manifest file.
     *
     * @param pkg - app package, which manifest to write.
     * @param packageManager - {@link PackageManager} instance.
     * @param manifestFile - target manifest file.
     * @param withApk - whether include apk or not.
     * @param withWidgets - whether to write widgets data.
     * @throws IOException - in case of an error.
     */
    // TODO: withWidgets is not used, decide whether it is needed.
    public static void writeAppManifest(PackageInfo pkg, PackageManager packageManager,
            File manifestFile, boolean withApk, boolean withWidgets) throws IOException {
        // Manifest format. All data are strings ending in LF:
        //     BACKUP_MANIFEST_VERSION, currently 1
        //
        // Version 1:
        //     package name
        //     package's versionCode
        //     platform versionCode
        //     getInstallerPackageName() for this package (maybe empty)
        //     boolean: "1" if archive includes .apk; any other string means not
        //     number of signatures == N
        // N*:    signature byte array in ascii format per Signature.toCharsString()
        StringBuilder builder = new StringBuilder(4096);
        StringBuilderPrinter printer = new StringBuilderPrinter(builder);

        printer.println(Integer.toString(BACKUP_MANIFEST_VERSION));
        printer.println(pkg.packageName);
        printer.println(Long.toString(pkg.getLongVersionCode()));
        printer.println(Integer.toString(Build.VERSION.SDK_INT));

        String installerName = packageManager.getInstallerPackageName(pkg.packageName);
        printer.println((installerName != null) ? installerName : "");

        printer.println(withApk ? "1" : "0");

        // write the signature block
        SigningInfo signingInfo = pkg.signingInfo;
        if (signingInfo == null) {
            printer.println("0");
        } else {
            // retrieve the newest sigs 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());
            }
        }

        FileOutputStream outstream = new FileOutputStream(manifestFile);
        outstream.write(builder.toString().getBytes());
        outstream.close();

        // We want the manifest block in the archive stream to be idempotent:
        // each time we generate a backup stream for the app, we want the manifest
        // block to be identical.  The underlying tar mechanism sees it as a file,
        // though, and will propagate its mtime, causing the tar header to vary.
        // Avoid this problem by pinning the mtime to zero.
        manifestFile.setLastModified(0);
    }
}
Loading