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

Commit cd4d3875 authored by Alex Buynytskyy's avatar Alex Buynytskyy
Browse files

Incremental install via adb.

- passing V4 signature to IncFS,
- cleanup and use InstallationFile everywhere,
- pass params to DataLoader creation,
- minor refactor for PackageManagerShellCommandDataLoader to prepare for
Incremental data loading.

Test: atest PackageManagerShellCommandTest
Bug: b/136132412 b/133435829
Change-Id: Iacc3e4c51c0fa3410b076147ce153a1303246189
parent 6e406c8a
Loading
Loading
Loading
Loading
+5 −8
Original line number Diff line number Diff line
@@ -2007,18 +2007,15 @@ package android.content.pm {
  }
  public final class InstallationFile implements android.os.Parcelable {
    ctor public InstallationFile(@NonNull String, long, @Nullable byte[]);
    ctor public InstallationFile(int, @NonNull String, long, @Nullable byte[], @Nullable byte[]);
    method public int describeContents();
    method public int getFileType();
    method public long getLengthBytes();
    method public int getLocation();
    method @Nullable public byte[] getMetadata();
    method @NonNull public String getName();
    method public long getSize();
    method @Nullable public byte[] getSignature();
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.InstallationFile> CREATOR;
    field public static final int FILE_TYPE_APK = 0; // 0x0
    field public static final int FILE_TYPE_LIB = 1; // 0x1
    field public static final int FILE_TYPE_OBB = 2; // 0x2
    field public static final int FILE_TYPE_UNKNOWN = -1; // 0xffffffff
  }
  public final class InstantAppInfo implements android.os.Parcelable {
@@ -10317,7 +10314,7 @@ package android.service.dataloader {
  public abstract class DataLoaderService extends android.app.Service {
    ctor public DataLoaderService();
    method @Nullable public android.service.dataloader.DataLoaderService.DataLoader onCreateDataLoader();
    method @Nullable public android.service.dataloader.DataLoaderService.DataLoader onCreateDataLoader(@NonNull android.content.pm.DataLoaderParams);
  }
  public static interface DataLoaderService.DataLoader {
+29 −51
Original line number Diff line number Diff line
@@ -16,82 +16,59 @@

package android.content.pm;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * Defines the properties of a file in an installation session.
 * TODO(b/136132412): update with new APIs.
 *
 * @hide
 */
@SystemApi
public final class InstallationFile implements Parcelable {
    public static final int FILE_TYPE_UNKNOWN = -1;
    public static final int FILE_TYPE_APK = 0;
    public static final int FILE_TYPE_LIB = 1;
    public static final int FILE_TYPE_OBB = 2;

    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(prefix = {"FILE_TYPE_"}, value = {
            FILE_TYPE_APK,
            FILE_TYPE_LIB,
            FILE_TYPE_OBB,
    })
    public @interface FileType {
    }

    private String mFileName;
    private @FileType int mFileType;
    private long mFileSize;
    private byte[] mMetadata;

    public InstallationFile(@NonNull String fileName, long fileSize,
            @Nullable byte[] metadata) {
        mFileName = fileName;
        mFileSize = fileSize;
    private final @PackageInstaller.FileLocation int mLocation;
    private final @NonNull String mName;
    private final long mLengthBytes;
    private final @Nullable byte[] mMetadata;
    private final @Nullable byte[] mSignature;

    public InstallationFile(@PackageInstaller.FileLocation int location, @NonNull String name,
            long lengthBytes, @Nullable byte[] metadata, @Nullable byte[] signature) {
        mLocation = location;
        mName = name;
        mLengthBytes = lengthBytes;
        mMetadata = metadata;
        if (fileName.toLowerCase().endsWith(".apk")) {
            mFileType = FILE_TYPE_APK;
        } else if (fileName.toLowerCase().endsWith(".obb")) {
            mFileType = FILE_TYPE_OBB;
        } else if (fileName.toLowerCase().endsWith(".so") && fileName.toLowerCase().startsWith(
                "lib/")) {
            mFileType = FILE_TYPE_LIB;
        } else {
            mFileType = FILE_TYPE_UNKNOWN;
        }
        mSignature = signature;
    }

    public @FileType int getFileType() {
        return mFileType;
    public @PackageInstaller.FileLocation int getLocation() {
        return mLocation;
    }

    public @NonNull String getName() {
        return mFileName;
        return mName;
    }

    public long getSize() {
        return mFileSize;
    public long getLengthBytes() {
        return mLengthBytes;
    }

    public @Nullable byte[] getMetadata() {
        return mMetadata;
    }

    public @Nullable byte[] getSignature() {
        return mSignature;
    }

    private InstallationFile(Parcel source) {
        mFileName = source.readString();
        mFileType = source.readInt();
        mFileSize = source.readLong();
        mLocation = source.readInt();
        mName = source.readString();
        mLengthBytes = source.readLong();
        mMetadata = source.createByteArray();
        mSignature = source.createByteArray();
    }

    @Override
@@ -101,10 +78,11 @@ public final class InstallationFile implements Parcelable {

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeString(mFileName);
        dest.writeInt(mFileType);
        dest.writeLong(mFileSize);
        dest.writeInt(mLocation);
        dest.writeString(mName);
        dest.writeLong(mLengthBytes);
        dest.writeByteArray(mMetadata);
        dest.writeByteArray(mSignature);
    }

    public static final @NonNull Creator<InstallationFile> CREATOR =
+6 −4
Original line number Diff line number Diff line
@@ -29,6 +29,8 @@ package android.os.incremental;
 * @throws IllegalStateException the session is not an Incremental installation session.
 */

import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -85,7 +87,7 @@ public final class IncrementalFileStorages {
        try {
            result = new IncrementalFileStorages(stageDir, incrementalManager, dataLoaderParams);
            for (InstallationFile file : addedFiles) {
                if (file.getFileType() == InstallationFile.FILE_TYPE_APK) {
                if (file.getLocation() == LOCATION_DATA_APP) {
                    try {
                        result.addApkFile(file);
                    } catch (IOException e) {
@@ -95,7 +97,7 @@ public final class IncrementalFileStorages {
                                e);
                    }
                } else {
                    throw new IOException("Unknown file type: " + file.getFileType());
                    throw new IOException("Unknown file location: " + file.getLocation());
                }
            }

@@ -147,8 +149,8 @@ public final class IncrementalFileStorages {
        String apkName = apk.getName();
        File targetFile = Paths.get(stageDirPath, apkName).toFile();
        if (!targetFile.exists()) {
            mDefaultStorage.makeFile(apkName, apk.getSize(), null,
                    apk.getMetadata(), 0, null, null, null);
            mDefaultStorage.makeFile(apkName, apk.getLengthBytes(), null, apk.getMetadata(),
                    apk.getSignature());
        }
    }

+9 −0
Original line number Diff line number Diff line
@@ -299,7 +299,16 @@ public final class IncrementalManager {
        return nativeIsIncrementalPath(path);
    }

    /**
     * Returns raw signature for file if it's on Incremental File System.
     * Unsafe, use only if you are sure what you are doing.
     */
    public static @Nullable byte[] unsafeGetFileSignature(@NonNull String path) {
        return nativeUnsafeGetFileSignature(path);
    }

    /* Native methods */
    private static native boolean nativeIsEnabled();
    private static native boolean nativeIsIncrementalPath(@NonNull String path);
    private static native byte[] nativeUnsafeGetFileSignature(@NonNull String path);
}
+70 −32
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.RemoteException;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -169,10 +171,11 @@ public final class IncrementalStorage {
     * @param path             Relative path of the new file.
     * @param size             Size of the new file in bytes.
     * @param metadata         Metadata bytes.
     * @param v4signatureBytes Serialized V4SignatureProto.
     */
    public void makeFile(@NonNull String path, long size, @Nullable UUID id,
            @Nullable byte[] metadata, int hashAlgorithm, @Nullable byte[] rootHash,
            @Nullable byte[] additionalData, @Nullable byte[] signature) throws IOException {
            @Nullable byte[] metadata, @Nullable byte[] v4signatureBytes)
            throws IOException {
        try {
            if (id == null && metadata == null) {
                throw new IOException("File ID and metadata cannot both be null");
@@ -181,13 +184,7 @@ public final class IncrementalStorage {
            params.size = size;
            params.metadata = (metadata == null ? new byte[0] : metadata);
            params.fileId = idToBytes(id);
            if (hashAlgorithm != 0 || signature != null) {
                params.signature = new IncrementalSignature();
                params.signature.hashAlgorithm = hashAlgorithm;
                params.signature.rootHash = rootHash;
                params.signature.additionalData = additionalData;
                params.signature.signature = signature;
            }
            params.signature = parseV4Signature(v4signatureBytes);
            int res = mService.makeFile(mId, path, params);
            if (res != 0) {
                throw new IOException("makeFile() failed with errno " + -res);
@@ -197,6 +194,7 @@ public final class IncrementalStorage {
        }
    }


    /**
     * Creates a file in Incremental storage. The content of the file is mapped from a range inside
     * a source file in the same storage.
@@ -349,6 +347,37 @@ public final class IncrementalStorage {
        }
    }

    /**
     * Returns the metadata object of an IncFs File.
     *
     * @param id The file id.
     * @return Byte array that contains metadata bytes.
     */
    @Nullable
    public byte[] getFileMetadata(@NonNull UUID id) {
        try {
            final byte[] rawId = idToBytes(id);
            return mService.getMetadataById(mId, rawId);
        } catch (RemoteException e) {
            e.rethrowFromSystemServer();
            return null;
        }
    }

    /**
     * Informs the data loader service associated with the current storage to start data loader
     *
     * @return True if data loader is successfully started.
     */
    public boolean startLoading() {
        try {
            return mService.startLoading(mId);
        } catch (RemoteException e) {
            e.rethrowFromSystemServer();
            return false;
        }
    }

    private static final int UUID_BYTE_SIZE = 16;

    /**
@@ -386,35 +415,44 @@ public final class IncrementalStorage {
        return new UUID(msb, lsb);
    }

    private static final int INCFS_HASH_SHA256 = 1;
    private static final int INCFS_MAX_HASH_SIZE = 32; // SHA256
    private static final int INCFS_MAX_ADD_DATA_SIZE = 128;

    /**
     * Returns the metadata object of an IncFs File.
     *
     * @param id The file id.
     * @return Byte array that contains metadata bytes.
     * Deserialize and validate v4 signature bytes.
     */
    @Nullable
    public byte[] getFileMetadata(@NonNull UUID id) {
        try {
            final byte[] rawId = idToBytes(id);
            return mService.getMetadataById(mId, rawId);
        } catch (RemoteException e) {
            e.rethrowFromSystemServer();
    private static IncrementalSignature parseV4Signature(@Nullable byte[] v4signatureBytes)
            throws IOException {
        if (v4signatureBytes == null) {
            return null;
        }

        final V4Signature signature;
        try (DataInputStream input = new DataInputStream(
                new ByteArrayInputStream(v4signatureBytes))) {
            signature = V4Signature.readFrom(input);
        }

    /**
     * Informs the data loader service associated with the current storage to start data loader
     *
     * @return True if data loader is successfully started.
     */
    public boolean startLoading() {
        try {
            return mService.startLoading(mId);
        } catch (RemoteException e) {
            e.rethrowFromSystemServer();
            return false;
        final byte[] rootHash = signature.verityRootHash;
        final byte[] additionalData = signature.v3Digest;
        final byte[] pkcs7Signature = signature.pkcs7SignatureBlock;

        if (rootHash.length != INCFS_MAX_HASH_SIZE) {
            throw new IOException("rootHash has to be " + INCFS_MAX_HASH_SIZE + " bytes");
        }
        if (additionalData.length > INCFS_MAX_ADD_DATA_SIZE) {
            throw new IOException(
                    "additionalData has to be at most " + INCFS_MAX_ADD_DATA_SIZE + " bytes");
        }

        IncrementalSignature result = new IncrementalSignature();
        result.hashAlgorithm = INCFS_HASH_SHA256;
        result.rootHash = rootHash;
        result.additionalData = additionalData;
        result.signature = pkcs7Signature;

        return result;
    }

    /**
Loading