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

Commit 4e19d8fd authored by Songchun Fan's avatar Songchun Fan Committed by Android (Google) Code Review
Browse files

Merge "[incremental/installation] new API to add file to session"

parents b7c6d8d8 4e758697
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -46,4 +46,5 @@ interface IPackageInstallerSession {
    int getParentSessionId();

    boolean isStaged();
    void addFile(in String name, long size, in byte[] metadata);
}
+47 −0
Original line number Diff line number Diff line
@@ -50,6 +50,8 @@ import android.os.ParcelableException;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.incremental.IncrementalDataLoaderParams;
import android.os.incremental.IncrementalDataLoaderParamsParcel;
import android.system.ErrnoException;
import android.system.Os;
import android.util.ArraySet;
@@ -1211,6 +1213,27 @@ public class PackageInstaller {
            }
        }

        /**
         * Configure files for an installation session.
         *
         * Currently only for Incremental installation session. Once this method is called,
         * the files and their paths, as specified in the parameters, will be created and properly
         * configured in the Incremental File System.
         *
         * TODO(b/136132412): update this and InstallationFile class with latest API design.
         *
         * @throws IllegalStateException if {@link SessionParams#incrementalParams} is null.
         *
         * @hide
         */
        public void addFile(@NonNull String name, long size, @NonNull byte[] metadata) {
            try {
                mSession.addFile(name, size, metadata);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }

        /**
         * Release this session object. You can open the session again if it
         * hasn't been finalized.
@@ -1398,6 +1421,8 @@ public class PackageInstaller {
        public boolean isStaged;
        /** {@hide} */
        public long requiredInstalledVersionCode = PackageManager.VERSION_CODE_HIGHEST;
        /** {@hide} */
        public IncrementalDataLoaderParams incrementalParams;

        /**
         * Construct parameters for a new package install session.
@@ -1431,6 +1456,12 @@ public class PackageInstaller {
            isMultiPackage = source.readBoolean();
            isStaged = source.readBoolean();
            requiredInstalledVersionCode = source.readLong();
            IncrementalDataLoaderParamsParcel dataLoaderParamsParcel = source.readParcelable(
                    IncrementalDataLoaderParamsParcel.class.getClassLoader());
            if (dataLoaderParamsParcel != null) {
                incrementalParams = new IncrementalDataLoaderParams(
                        dataLoaderParamsParcel);
            }
        }

        /** {@hide} */
@@ -1454,6 +1485,7 @@ public class PackageInstaller {
            ret.isMultiPackage = isMultiPackage;
            ret.isStaged = isStaged;
            ret.requiredInstalledVersionCode = requiredInstalledVersionCode;
            ret.incrementalParams = incrementalParams;
            return ret;
        }

@@ -1782,6 +1814,16 @@ public class PackageInstaller {
            return (installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0;
        }

        /**
         * Set Incremental data loader params.
         *
         * {@hide}
         */
        @RequiresPermission(Manifest.permission.INSTALL_PACKAGES)
        public void setIncrementalParams(@NonNull IncrementalDataLoaderParams incrementalParams) {
            this.incrementalParams = incrementalParams;
        }

        /** {@hide} */
        public void dump(IndentingPrintWriter pw) {
            pw.printPair("mode", mode);
@@ -1831,6 +1873,11 @@ public class PackageInstaller {
            dest.writeBoolean(isMultiPackage);
            dest.writeBoolean(isStaged);
            dest.writeLong(requiredInstalledVersionCode);
            if (incrementalParams != null) {
                dest.writeParcelable(incrementalParams.getData(), flags);
            } else {
                dest.writeParcelable(null, flags);
            }
        }

        public static final Parcelable.Creator<SessionParams>
+280 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.os.incremental;

/**
 * Set up files and directories used in an installation session.
 * Currently only used by Incremental Installation.
 * For Incremental installation, the expected outcome of this function is:
 * 0) All the files are in defaultStorage
 * 1) All APK files are in the same directory, bound to mApkStorage, and bound to the
 * InstallerSession's stage dir. The files are linked from mApkStorage to defaultStorage.
 * 2) All lib files are in the sub directories as their names suggest, and in the same parent
 * directory as the APK files. The files are linked from mApkStorage to defaultStorage.
 * 3) OBB files are in another directory that is different from APK files and lib files, bound
 * to mObbStorage. The files are linked from mObbStorage to defaultStorage.
 *
 * @throws IllegalStateException the session is not an Incremental installation session.
 */

import static dalvik.system.VMRuntime.getInstructionSet;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.InstallationFile;
import android.os.IVold;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.ArraySet;
import android.util.Slog;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Random;

/**
 * This class manages storage instances used during a package installation session.
 * @hide
 */
public final class IncrementalFileStorages {
    private static final String TAG = "IncrementalFileStorages";
    private @Nullable IncrementalStorage mDefaultStorage;
    private @Nullable IncrementalStorage mApkStorage;
    private @Nullable IncrementalStorage mObbStorage;
    private @Nullable String mDefaultDir;
    private @Nullable String mObbDir;
    private @NonNull IncrementalManager mIncrementalManager;
    private @Nullable ArraySet<String> mLibDirs;
    private @NonNull String mPackageName;
    private @NonNull File mStageDir;

    /**
     * Set up files and directories used in an installation session.
     * Currently only used by Incremental Installation.
     * For Incremental installation, the expected outcome of this function is:
     * 0) All the files are in defaultStorage
     * 1) All APK files are in the same directory, bound to mApkStorage, and bound to the
     * InstallerSession's stage dir. The files are linked from mApkStorage to defaultStorage.
     * 2) All lib files are in the sub directories as their names suggest, and in the same parent
     * directory as the APK files. The files are linked from mApkStorage to defaultStorage.
     * 3) OBB files are in another directory that is different from APK files and lib files, bound
     * to mObbStorage. The files are linked from mObbStorage to defaultStorage.
     *
     * @throws IllegalStateException the session is not an Incremental installation session.
     */
    public IncrementalFileStorages(@NonNull String packageName,
            @NonNull File stageDir,
            @NonNull IncrementalManager incrementalManager,
            @NonNull IncrementalDataLoaderParams incrementalDataLoaderParams) {
        mPackageName = packageName;
        mStageDir = stageDir;
        mIncrementalManager = incrementalManager;
        if (incrementalDataLoaderParams.getPackageName().equals("local")) {
            final String incrementalPath = incrementalDataLoaderParams.getStaticArgs();
            mDefaultStorage = mIncrementalManager.openStorage(incrementalPath);
            mDefaultDir = incrementalPath;
            return;
        }
        mDefaultDir = getTempDir();
        if (mDefaultDir == null) {
            return;
        }
        mDefaultStorage = mIncrementalManager.createStorage(mDefaultDir,
                incrementalDataLoaderParams,
                IncrementalManager.CREATE_MODE_CREATE
                        | IncrementalManager.CREATE_MODE_TEMPORARY_BIND, false);
    }

    /**
     * Adds a file into the installation session. Makes sure it will be placed inside
     * a proper storage instance, based on its file type.
     */
    public void addFile(@NonNull InstallationFile file) throws IOException {
        if (mDefaultStorage == null) {
            throw new IOException("Cannot add file because default storage does not exist");
        }
        if (file.getFileType() == InstallationFile.FILE_TYPE_APK) {
            addApkFile(file);
        } else if (file.getFileType() == InstallationFile.FILE_TYPE_OBB) {
            addObbFile(file);
        } else if (file.getFileType() == InstallationFile.FILE_TYPE_LIB) {
            addLibFile(file);
        } else {
            throw new IOException("Unknown file type: " + file.getFileType());
        }
    }

    private void addApkFile(@NonNull InstallationFile apk) throws IOException {
        // Create a storage for APK files and lib files
        final String stageDirPath = mStageDir.getAbsolutePath();
        if (mApkStorage == null) {
            mApkStorage = mIncrementalManager.createStorage(stageDirPath, mDefaultStorage,
                    IncrementalManager.CREATE_MODE_CREATE
                            | IncrementalManager.CREATE_MODE_TEMPORARY_BIND);
            mApkStorage.bind(stageDirPath);
        }

        if (!new File(mDefaultDir, apk.getName()).exists()) {
            mDefaultStorage.makeFile(apk.getName(), apk.getSize(),
                    apk.getMetadata());
        }
        // Assuming APK files are already named properly, e.g., "base.apk"
        mDefaultStorage.makeLink(apk.getName(), mApkStorage, apk.getName());
    }

    private void addLibFile(@NonNull InstallationFile lib) throws IOException {
        // TODO(b/136132412): remove this after we have incfs support for lib file mapping
        if (mApkStorage == null) {
            throw new IOException("Cannot add lib file without adding an apk file first");
        }
        if (mLibDirs == null) {
            mLibDirs = new ArraySet<>();
        }
        String current = "";
        final Path libDirPath = Paths.get(lib.getName()).getParent();
        final int numDirComponents = libDirPath.getNameCount();
        for (int i = 0; i < numDirComponents; i++) {
            String dirName = libDirPath.getName(i).toString();
            try {
                dirName = getInstructionSet(dirName);
            } catch (IllegalArgumentException ignored) {
            }
            current += dirName;
            if (!mLibDirs.contains(current)) {
                mDefaultStorage.makeDirectory(current);
                mApkStorage.makeDirectory(current);
                mLibDirs.add(current);
            }
            current += '/';
        }
        String libFilePath = current + Paths.get(lib.getName()).getFileName();
        mDefaultStorage.makeFile(libFilePath, lib.getSize(), lib.getMetadata());
        mDefaultStorage.makeLink(libFilePath, mApkStorage, libFilePath);
    }

    private void addObbFile(@NonNull InstallationFile obb) throws IOException {
        if (mObbStorage == null) {
            // Create a storage for OBB files
            mObbDir = getTempDir();
            if (mObbDir == null) {
                throw new IOException("Failed to create obb storage directory.");
            }
            mObbStorage = mIncrementalManager.createStorage(
                    mObbDir, mDefaultStorage,
                    IncrementalManager.CREATE_MODE_CREATE
                            | IncrementalManager.CREATE_MODE_TEMPORARY_BIND);
        }
        mDefaultStorage.makeFile(obb.getName(), obb.getSize(), obb.getMetadata());
        mDefaultStorage.makeLink(obb.getName(), mObbStorage, obb.getName());
    }

    private boolean hasObb() {
        return (mObbStorage != null && mObbDir != null);
    }

    /**
     * Starts loading data for default storage.
     * TODO(b/136132412): update the implementation with latest API design.
     */
    public boolean startLoading() {
        if (mDefaultStorage == null) {
            return false;
        }
        return mDefaultStorage.startLoading();
    }

    /**
     * Sets up obb storage directory and create bindings.
     */
    public void finishSetUp() {
        if (!hasObb()) {
            return;
        }
        final String mainObbDir = String.format("/storage/emulated/0/Android/obb/%s", mPackageName);
        final String packageObbDirRoot =
                String.format("/mnt/runtime/%s/emulated/0/Android/obb/", mPackageName);
        final String[] obbDirs = {
                packageObbDirRoot + "read",
                packageObbDirRoot + "write",
                packageObbDirRoot + "full",
                packageObbDirRoot + "default",
                String.format("/data/media/0/Android/obb/%s", mPackageName),
                mainObbDir,
        };
        try {
            Slog.i(TAG, "Creating obb directory '" + mainObbDir + "'");
            final IVold vold = IVold.Stub.asInterface(ServiceManager.getServiceOrThrow("vold"));
            vold.mkdirs(mainObbDir);
            for (String d : obbDirs) {
                mObbStorage.bindPermanent(d);
            }
        } catch (ServiceManager.ServiceNotFoundException ex) {
            Slog.e(TAG, "vold service is not found.");
            cleanUp();
        } catch (IOException | RemoteException ex) {
            Slog.e(TAG, "Failed to create obb dir at: " + mainObbDir, ex);
            cleanUp();
        }
    }

    /**
     * Resets the states and unbinds storage instances for an installation session.
     * TODO(b/136132412): make sure unnecessary binds are removed but useful storages are kept
     */
    public void cleanUp() {
        if (mDefaultStorage != null && mDefaultDir != null) {
            try {
                mDefaultStorage.unBind(mDefaultDir);
            } catch (IOException ignored) {
            }
            mDefaultDir = null;
            mDefaultStorage = null;
        }
        if (mApkStorage != null && mStageDir != null) {
            try {
                mApkStorage.unBind(mStageDir.getAbsolutePath());
            } catch (IOException ignored) {
            }
            mApkStorage = null;
        }
        if (mObbStorage != null && mObbDir != null) {
            try {
                mObbStorage.unBind(mObbDir);
            } catch (IOException ignored) {
            }
            mObbDir = null;
            mObbStorage = null;
        }
    }

    private String getTempDir() {
        final String tmpDirRoot = "/data/tmp";
        final Random random = new Random();
        final Path tmpDir =
                Paths.get(tmpDirRoot, String.valueOf(random.nextInt(Integer.MAX_VALUE - 1)));
        try {
            Files.createDirectories(tmpDir);
        } catch (Exception ex) {
            Slog.e(TAG, "Failed to create dir", ex);
            return null;
        }
        return tmpDir.toAbsolutePath().toString();
    }
}
+46 −1
Original line number Diff line number Diff line
@@ -54,6 +54,7 @@ import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageInstallObserver2;
import android.content.pm.IPackageInstallerSession;
import android.content.pm.InstallationFile;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
@@ -81,6 +82,8 @@ import android.os.Process;
import android.os.RevocableFileDescriptor;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.incremental.IncrementalFileStorages;
import android.os.incremental.IncrementalManager;
import android.os.storage.StorageManager;
import android.stats.devicepolicy.DevicePolicyEnums;
import android.system.ErrnoException;
@@ -309,6 +312,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    @GuardedBy("mLock")
    private boolean mVerityFound;

    private IncrementalFileStorages mIncrementalFileStorages;

    private static final FileFilter sAddedFilter = new FileFilter() {
        @Override
        public boolean accept(File file) {
@@ -471,6 +476,18 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        mStagedSessionErrorCode = stagedSessionErrorCode;
        mStagedSessionErrorMessage =
                stagedSessionErrorMessage != null ? stagedSessionErrorMessage : "";

        // TODO(b/136132412): sanity check if session should not be incremental
        if (!params.isStaged && params.incrementalParams != null
                && !params.incrementalParams.getPackageName().isEmpty()) {
            IncrementalManager incrementalManager = (IncrementalManager) mContext.getSystemService(
                    Context.INCREMENTAL_SERVICE);
            if (incrementalManager != null) {
                mIncrementalFileStorages =
                        new IncrementalFileStorages(mPackageName, stageDir, incrementalManager,
                                params.incrementalParams);
            }
        }
    }

    public SessionInfo generateInfo() {
@@ -874,10 +891,18 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            }
        }

        if (mIncrementalFileStorages != null) {
            mIncrementalFileStorages.finishSetUp();
        }

        mHandler.obtainMessage(MSG_SEAL, statusReceiver).sendToTarget();
    }

    private void handleSeal(@NonNull IntentSender statusReceiver) {
        // TODO(b/136132412): update with new APIs
        if (mIncrementalFileStorages != null) {
            mIncrementalFileStorages.startLoading();
        }
        if (!markAsCommitted(statusReceiver)) {
            return;
        }
@@ -1492,6 +1517,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
                computeProgressLocked(true);

                // Unpack native libraries
                // TODO(b/136132412): skip for incremental installation
                extractNativeLibraries(stageDir, params.abiOverride, mayInheritNativeLibs());
            }

@@ -2182,7 +2208,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            }
            destroyInternal();
        }

        dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
    }

@@ -2268,6 +2293,20 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        return mParentSessionId;
    }

    @Override
    public void addFile(@NonNull String name, long size, @NonNull byte[] metadata) {
        if (mIncrementalFileStorages == null) {
            throw new IllegalStateException(
                    "Cannot add Incremental File to a non-Incremental session.");
        }
        try {
            mIncrementalFileStorages.addFile(new InstallationFile(name, size, metadata));
        } catch (IOException ex) {
            throw new IllegalStateException(
                    "Failed to add and configure Incremental File: " + name, ex);
        }
    }

    private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) {
        final IntentSender statusReceiver;
        final String packageName;
@@ -2390,6 +2429,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        // since these packages are supposed to be read on reboot.
        // Those dirs are deleted when the staged session has reached a final state.
        if (stageDir != null && !params.isStaged) {
            if (mIncrementalFileStorages != null) {
                mIncrementalFileStorages.cleanUp();
            }
            try {
                mPm.mInstaller.rmPackageDir(stageDir.getAbsolutePath());
            } catch (InstallerException ignored) {
@@ -2403,6 +2445,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
                mSessionProvider.getSession(childSessionId).cleanStageDir();
            }
        } else {
            if (mIncrementalFileStorages != null) {
                mIncrementalFileStorages.cleanUp();
            }
            try {
                mPm.mInstaller.rmPackageDir(stageDir.getAbsolutePath());
            } catch (InstallerException ignored) {