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

Commit 9d703935 authored by Joël Stemmer's avatar Joël Stemmer
Browse files

Add `onRestoreFile` overload that takes a `FullRestoreDataInput` param

The `BackupAgent#onRestoreFile` method takes a number of parameters
related to the file being restored. We need to add some additional
parameters, for example the app version code (which is also passed to
`onRestore`) or transport flags. Rather than adding another overload
with all these additional parameters, we're taking the same approach as
we did with `onFullBackup` and define a new class that contains all
these parameters. This should also help us avoid adding more overloads
in case we want to add another parameter in the future.

This change only introduces the necessary API changes, but the new
parameters are all set to their default values. A follow up change will
modify the IBackupAgent.aidl and pass through the parameters from the
restore engine, which also requires updating non-backup owned code.

Bug: 403956528
Test: atest BackupAgentTest.java
Flag: com.android.server.backup.enable_cross_platform_transfer
API-Coverage-Bug: 430585746

Change-Id: Ic6e6235e782a47d1a6a722e99bc9976fce15fdc4
parent 4f90555c
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -9287,6 +9287,7 @@ package android.app.backup {
    method public void onQuotaExceeded(long, long);
    method public abstract void onRestore(android.app.backup.BackupDataInput, int, android.os.ParcelFileDescriptor) throws java.io.IOException;
    method public void onRestore(android.app.backup.BackupDataInput, long, android.os.ParcelFileDescriptor) throws java.io.IOException;
    method @FlaggedApi("com.android.server.backup.enable_cross_platform_transfer") public void onRestoreFile(@NonNull android.app.backup.FullRestoreDataInput) throws java.io.IOException;
    method public void onRestoreFile(android.os.ParcelFileDescriptor, long, java.io.File, int, long, long) throws java.io.IOException;
    method public void onRestoreFinished();
    field public static final int FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED = 1; // 0x1
@@ -9350,6 +9351,18 @@ package android.app.backup {
    method public int getTransportFlags();
  }
  @FlaggedApi("com.android.server.backup.enable_cross_platform_transfer") public class FullRestoreDataInput {
    method public long getAppVersionCode();
    method @NonNull public String getContentVersion();
    method @NonNull public android.os.ParcelFileDescriptor getData();
    method @NonNull public java.io.File getDestination();
    method public long getMode();
    method public long getModificationTimeSeconds();
    method public long getSize();
    method public int getTransportFlags();
    method public int getType();
  }
  public abstract class RestoreObserver {
    ctor public RestoreObserver();
    method public void onUpdate(int, String);
+58 −18
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.app.backup;

import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.IBackupAgent;
import android.app.QueuedWork;
@@ -159,23 +160,25 @@ public abstract class BackupAgent extends ContextWrapper {
    /** @hide */
    public static final int TYPE_SYMLINK = 3;

    /** @hide */
    @IntDef(
            prefix = {"TYPE_"},
            value = {TYPE_EOF, TYPE_FILE, TYPE_DIRECTORY, TYPE_SYMLINK})
    @Retention(RetentionPolicy.SOURCE)
    public @interface BackupFileSystemObjectType {}

    /**
     * Flag for {@link BackupDataOutput#getTransportFlags()}, {@link
     * FullBackupDataOutput#getTransportFlags()} and {@link #onMeasureFullBackup(long, int)} only.
     *
     * <p>The transport has client-side encryption enabled. i.e., the user's backup has been
     * encrypted with a key known only to the device, and not to the remote storage solution. Even
     * if an attacker had root access to the remote storage provider they should not be able to
     * decrypt the user's backup data.
     * Transport flag indicating that the transport has client-side encryption enabled. i.e., the
     * user's backup has been encrypted with a key known only to the device, and not to the remote
     * storage solution. Even if an attacker had root access to the remote storage provider they
     * should not be able to decrypt the user's backup data.
     */
    public static final int FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED = 1;

    /**
     * Flag for {@link BackupDataOutput#getTransportFlags()}, {@link
     * FullBackupDataOutput#getTransportFlags()} and {@link #onMeasureFullBackup(long, int)} only.
     *
     * <p>The transport is for a device-to-device transfer. There is no third party or intermediate
     * storage. The user's backup data is sent directly to another device over e.g., USB or WiFi.
     * Transport flag indicating that the transport is used for a device-to-device transfer. There
     * is no third party or intermediate storage. The user's backup data is sent directly to another
     * device over e.g., USB or WiFi.
     */
    public static final int FLAG_DEVICE_TO_DEVICE_TRANSFER = 2;

@@ -188,11 +191,8 @@ public abstract class BackupAgent extends ContextWrapper {
    public static final int FLAG_SKIP_RESTORE_FOR_LAUNCHED_APPS = 1 << 2;

    /**
     * Flag for {@link BackupDataOutput#getTransportFlags()} and {@link
     * FullBackupDataOutput#getTransportFlags()} only.
     *
     * <p>Indicates this is a cross-platform transfer to or from iOS. The user's backup data is sent
     * directly to another device over e.g. USB or WiFi.
     * Transport flag indicating that the transport is used for a cross-platform transfer to or from
     * iOS. The user's backup data is sent directly to another device over e.g. USB or WiFi.
     */
    @FlaggedApi(Flags.FLAG_ENABLE_CROSS_PLATFORM_TRANSFER)
    public static final int FLAG_CROSS_PLATFORM_DATA_TRANSFER_IOS = 1 << 3;
@@ -970,6 +970,32 @@ public abstract class BackupAgent extends ContextWrapper {
        return false;
    }

    /**
     * Handle the data delivered via the given file descriptor during a full restore operation. The
     * agent is given the path to the file's original location as well as its size and metadata.
     *
     * <p>The file descriptor can only be read for {@code size} bytes; attempting to read more data
     * has undefined behavior.
     *
     * <p>The default implementation creates the destination file/directory and populates it with
     * the data from the file descriptor, then sets the file's access mode and modification time to
     * match the restore arguments.
     *
     * @param data A structured wrapper containing the details of the file that's being restored and
     *     additional metadata from the backup.
     * @throws IOException
     */
    @FlaggedApi(Flags.FLAG_ENABLE_CROSS_PLATFORM_TRANSFER)
    public void onRestoreFile(@NonNull FullRestoreDataInput data) throws IOException {
        onRestoreFile(
                data.getData(),
                data.getSize(),
                data.getDestination(),
                data.getType(),
                data.getMode(),
                data.getModificationTimeSeconds());
    }

    /**
     * Handle the data delivered via the given file descriptor during a full restore operation. The
     * agent is given the path to the file's original location as well as its size and metadata.
@@ -1121,7 +1147,21 @@ public abstract class BackupAgent extends ContextWrapper {
            String outPath = outFile.getCanonicalPath();
            if (outPath.startsWith(basePath + File.separatorChar)) {
                if (DEBUG) Log.i(TAG, "[" + domain + " : " + path + "] mapped to " + outPath);
                if (Flags.enableCrossPlatformTransfer()) {
                    onRestoreFile(
                            new FullRestoreDataInput(
                                    data,
                                    size,
                                    outFile,
                                    type,
                                    mode,
                                    mtime,
                                    /* appVersionCode= */ 0,
                                    /* transportFlags= */ 0,
                                    /* contentVersion= */ ""));
                } else {
                    onRestoreFile(data, size, outFile, type, mode, mtime);
                }
                return;
            } else {
                // Attempt to restore to a path outside the file's nominal domain.
+133 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.app.backup;

import android.annotation.CurrentTimeSecondsLong;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.app.backup.BackupAgent.BackupFileSystemObjectType;
import android.app.backup.BackupAgent.BackupTransportFlags;
import android.os.ParcelFileDescriptor;

import com.android.server.backup.Flags;

import java.io.File;

/**
 * Provides the interface through which a {@link BackupAgent} reads entire files from a full backup
 * data set, via its {@link BackupAgent#onRestoreFile(FullRestoreDataInput)} method.
 */
@FlaggedApi(Flags.FLAG_ENABLE_CROSS_PLATFORM_TRANSFER)
public class FullRestoreDataInput {
    private final ParcelFileDescriptor mData;
    private final long mSize;
    private final File mDestination;
    private final int mType;
    private final long mMode;
    private final long mModificationTime;
    private final long mAppVersionCode;
    private final int mTransportFlags;
    private final String mContentVersion;

    /** @hide */
    public FullRestoreDataInput(
            ParcelFileDescriptor data,
            long size,
            File destination,
            @BackupFileSystemObjectType int type,
            long mode,
            long mtime,
            long appVersionCode,
            @BackupTransportFlags int transportFlags,
            String contentVersion) {
        this.mData = data;
        this.mSize = size;
        this.mDestination = destination;
        this.mType = type;
        this.mMode = mode;
        this.mModificationTime = mtime;
        this.mAppVersionCode = appVersionCode;
        this.mTransportFlags = transportFlags;
        this.mContentVersion = contentVersion;
    }

    /** Read-only file descriptor containing the file data. */
    @NonNull
    public ParcelFileDescriptor getData() {
        return mData;
    }

    /** Size of the file in bytes. */
    public long getSize() {
        return mSize;
    }

    /** The file on disk to be restored with the given data. */
    @NonNull
    public File getDestination() {
        return mDestination;
    }

    /**
     * The kind of file system object being restored. This will be either {@link
     * BackupAgent#TYPE_FILE} or {@link BackupAgent#TYPE_DIRECTORY}.
     */
    @BackupFileSystemObjectType
    public int getType() {
        return mType;
    }

    /**
     * The access mode to be assigned to the destination after its data is written. This is in the
     * standard format used by {@code chmod()}.
     */
    public long getMode() {
        return mMode;
    }

    /**
     * A timestamp in the standard Unix epoch that represents the last modification time of the file
     * when it was backed up, suitable to be assigned to the file after its data is written.
     */
    @CurrentTimeSecondsLong
    public long getModificationTimeSeconds() {
        return mModificationTime;
    }

    /** The version code of the app that created the backup. */
    public long getAppVersionCode() {
        return mAppVersionCode;
    }

    /**
     * Flags with additional information about the transport. The transport flags that can be set
     * are defined in {@link BackupAgent}.
     */
    @BackupTransportFlags
    public int getTransportFlags() {
        return mTransportFlags;
    }

    /**
     * Content version set by the source device during a cross-platform transfer. Empty string if
     * the source device did not provide a content version.
     */
    @NonNull
    public String getContentVersion() {
        return mContentVersion;
    }
}