FileDocCategorySizeDatePackage
PackageInstallerSession.javaAPI DocAndroid 5.1 API37427Thu Mar 12 22:22:42 GMT 2015com.android.server.pm

PackageInstallerSession

public class PackageInstallerSession extends IPackageInstallerSession.Stub

Fields Summary
private static final String
TAG
private static final boolean
LOGD
private static final int
MSG_COMMIT
private final PackageInstallerService.InternalCallback
mCallback
private final android.content.Context
mContext
private final PackageManagerService
mPm
private final android.os.Handler
mHandler
final int
sessionId
final int
userId
final String
installerPackageName
final int
installerUid
final android.content.pm.PackageInstaller.SessionParams
params
final long
createdMillis
final File
stageDir
Staging location where client data is written.
final String
stageCid
private final AtomicInteger
mActiveCount
private final Object
mLock
private float
mClientProgress
private float
mInternalProgress
private float
mProgress
private float
mReportedProgress
private boolean
mPrepared
private boolean
mSealed
private boolean
mPermissionsAccepted
private boolean
mDestroyed
private int
mFinalStatus
private String
mFinalMessage
private ArrayList
mBridges
private android.content.pm.IPackageInstallObserver2
mRemoteObserver
private String
mPackageName
Fields derived from commit parsing
private int
mVersionCode
private android.content.pm.Signature[]
mSignatures
private File
mResolvedBaseFile
Path to the validated base APK for this session, which may point at an APK inside the session (when the session defines the base), or it may point at the existing base APK (when adding splits to an existing app).

This is used when confirming permissions, since we can't fully stage the session inside an ASEC before confirming with user.

private File
mResolvedStageDir
private final List
mResolvedStagedFiles
private final List
mResolvedInheritedFiles
private final Handler.Callback
mHandlerCallback
Constructors Summary
public PackageInstallerSession(PackageInstallerService.InternalCallback callback, android.content.Context context, PackageManagerService pm, android.os.Looper looper, int sessionId, int userId, String installerPackageName, int installerUid, android.content.pm.PackageInstaller.SessionParams params, long createdMillis, File stageDir, String stageCid, boolean prepared, boolean sealed)


      
                     
                   
                    
        mCallback = callback;
        mContext = context;
        mPm = pm;
        mHandler = new Handler(looper, mHandlerCallback);

        this.sessionId = sessionId;
        this.userId = userId;
        this.installerPackageName = installerPackageName;
        this.installerUid = installerUid;
        this.params = params;
        this.createdMillis = createdMillis;
        this.stageDir = stageDir;
        this.stageCid = stageCid;

        if ((stageDir == null) == (stageCid == null)) {
            throw new IllegalArgumentException(
                    "Exactly one of stageDir or stageCid stage must be set");
        }

        mPrepared = prepared;
        mSealed = sealed;

        if ((mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES, installerUid)
                == PackageManager.PERMISSION_GRANTED) || (installerUid == Process.ROOT_UID)) {
            mPermissionsAccepted = true;
        } else {
            mPermissionsAccepted = false;
        }
    
Methods Summary
public voidabandon()

        destroyInternal();
        dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
    
public voidaddClientProgress(float progress)

        synchronized (mLock) {
            setClientProgress(mClientProgress + progress);
        }
    
private voidassertApkConsistent(java.lang.String tag, android.content.pm.PackageParser.ApkLite apk)

        if (!mPackageName.equals(apk.packageName)) {
            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " package "
                    + apk.packageName + " inconsistent with " + mPackageName);
        }
        if (mVersionCode != apk.versionCode) {
            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag
                    + " version code " + apk.versionCode + " inconsistent with "
                    + mVersionCode);
        }
        if (!Signature.areExactMatch(mSignatures, apk.signatures)) {
            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                    tag + " signatures are inconsistent");
        }
    
private voidassertPreparedAndNotSealed(java.lang.String cookie)

        synchronized (mLock) {
            if (!mPrepared) {
                throw new IllegalStateException(cookie + " before prepared");
            }
            if (mSealed) {
                throw new SecurityException(cookie + " not allowed after commit");
            }
        }
    
private longcalculateInstalledSize()
Calculate the final install footprint size, combining both staged and existing APKs together and including unpacked native code from both.

        Preconditions.checkNotNull(mResolvedBaseFile);

        final ApkLite baseApk;
        try {
            baseApk = PackageParser.parseApkLite(mResolvedBaseFile, 0);
        } catch (PackageParserException e) {
            throw PackageManagerException.from(e);
        }

        final List<String> splitPaths = new ArrayList<>();
        for (File file : mResolvedStagedFiles) {
            if (mResolvedBaseFile.equals(file)) continue;
            splitPaths.add(file.getAbsolutePath());
        }
        for (File file : mResolvedInheritedFiles) {
            if (mResolvedBaseFile.equals(file)) continue;
            splitPaths.add(file.getAbsolutePath());
        }

        // This is kind of hacky; we're creating a half-parsed package that is
        // straddled between the inherited and staged APKs.
        final PackageLite pkg = new PackageLite(null, baseApk, null,
                splitPaths.toArray(new String[splitPaths.size()]), null);
        final boolean isForwardLocked =
                (params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0;

        try {
            return PackageHelper.calculateInstalledSize(pkg, isForwardLocked, params.abiOverride);
        } catch (IOException e) {
            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                    "Failed to calculate install size", e);
        }
    
public voidclose()

        if (mActiveCount.decrementAndGet() == 0) {
            mCallback.onSessionActiveChanged(this, false);
        }
    
public voidcommit(android.content.IntentSender statusReceiver)

        Preconditions.checkNotNull(statusReceiver);

        final boolean wasSealed;
        synchronized (mLock) {
            wasSealed = mSealed;
            if (!mSealed) {
                // Verify that all writers are hands-off
                for (FileBridge bridge : mBridges) {
                    if (!bridge.isClosed()) {
                        throw new SecurityException("Files still open");
                    }
                }
                mSealed = true;
            }

            // Client staging is fully done at this point
            mClientProgress = 1f;
            computeProgressLocked(true);
        }

        if (!wasSealed) {
            // Persist the fact that we've sealed ourselves to prevent
            // mutations of any hard links we create. We do this without holding
            // the session lock, since otherwise it's a lock inversion.
            mCallback.onSessionSealedBlocking(this);
        }

        // This ongoing commit should keep session active, even though client
        // will probably close their end.
        mActiveCount.incrementAndGet();

        final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(mContext,
                statusReceiver, sessionId);
        mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget();
    
private voidcommitLocked()

        if (mDestroyed) {
            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed");
        }
        if (!mSealed) {
            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session not sealed");
        }

        try {
            resolveStageDir();
        } catch (IOException e) {
            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
                    "Failed to resolve stage location", e);
        }

        // Verify that stage looks sane with respect to existing application.
        // This currently only ensures packageName, versionCode, and certificate
        // consistency.
        validateInstallLocked();

        Preconditions.checkNotNull(mPackageName);
        Preconditions.checkNotNull(mSignatures);
        Preconditions.checkNotNull(mResolvedBaseFile);

        if (!mPermissionsAccepted) {
            // User needs to accept permissions; give installer an intent they
            // can use to involve user.
            final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_PERMISSIONS);
            intent.setPackage("com.android.packageinstaller");
            intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
            try {
                mRemoteObserver.onUserActionRequired(intent);
            } catch (RemoteException ignored) {
            }

            // Commit was keeping session marked as active until now; release
            // that extra refcount so session appears idle.
            close();
            return;
        }

        if (stageCid != null) {
            // Figure out the final installed size and resize the container once
            // and for all. Internally the parser handles straddling between two
            // locations when inheriting.
            final long finalSize = calculateInstalledSize();
            resizeContainer(stageCid, finalSize);
        }

        // Inherit any packages and native libraries from existing install that
        // haven't been overridden.
        if (params.mode == SessionParams.MODE_INHERIT_EXISTING) {
            try {
                final List<File> fromFiles = mResolvedInheritedFiles;
                final File toDir = resolveStageDir();

                if (isLinkPossible(fromFiles, toDir)) {
                    linkFiles(fromFiles, toDir);
                } else {
                    // TODO: this should delegate to DCS so the system process
                    // avoids holding open FDs into containers.
                    copyFiles(fromFiles, toDir);
                }
            } catch (IOException e) {
                throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
                        "Failed to inherit existing install", e);
            }
        }

        // TODO: surface more granular state from dexopt
        mInternalProgress = 0.5f;
        computeProgressLocked(true);

        // Unpack native libraries
        extractNativeLibraries(mResolvedStageDir, params.abiOverride);

        // Container is ready to go, let's seal it up!
        if (stageCid != null) {
            finalizeAndFixContainer(stageCid);
        }

        // We've reached point of no return; call into PMS to install the stage.
        // Regardless of success or failure we always destroy session.
        final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
            @Override
            public void onUserActionRequired(Intent intent) {
                throw new IllegalStateException();
            }

            @Override
            public void onPackageInstalled(String basePackageName, int returnCode, String msg,
                    Bundle extras) {
                destroyInternal();
                dispatchSessionFinished(returnCode, msg, extras);
            }
        };

        final UserHandle user;
        if ((params.installFlags & PackageManager.INSTALL_ALL_USERS) != 0) {
            user = UserHandle.ALL;
        } else {
            user = new UserHandle(userId);
        }

        mPm.installStage(mPackageName, stageDir, stageCid, localObserver, params,
                installerPackageName, installerUid, user);
    
private voidcomputeProgressLocked(boolean forcePublish)

        mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f)
                + MathUtils.constrain(mInternalProgress * 0.2f, 0f, 0.2f);

        // Only publish when meaningful change
        if (forcePublish || Math.abs(mProgress - mReportedProgress) >= 0.01) {
            mReportedProgress = mProgress;
            mCallback.onSessionProgressChanged(this, mProgress);
        }
    
private static voidcopyFiles(java.util.List fromFiles, java.io.File toDir)

        // Remove any partial files from previous attempt
        for (File file : toDir.listFiles()) {
            if (file.getName().endsWith(".tmp")) {
                file.delete();
            }
        }

        for (File fromFile : fromFiles) {
            final File tmpFile = File.createTempFile("inherit", ".tmp", toDir);
            if (LOGD) Slog.d(TAG, "Copying " + fromFile + " to " + tmpFile);
            if (!FileUtils.copyFile(fromFile, tmpFile)) {
                throw new IOException("Failed to copy " + fromFile + " to " + tmpFile);
            }
            try {
                Os.chmod(tmpFile.getAbsolutePath(), 0644);
            } catch (ErrnoException e) {
                throw new IOException("Failed to chmod " + tmpFile);
            }
            final File toFile = new File(toDir, fromFile.getName());
            if (LOGD) Slog.d(TAG, "Renaming " + tmpFile + " to " + toFile);
            if (!tmpFile.renameTo(toFile)) {
                throw new IOException("Failed to rename " + tmpFile + " to " + toFile);
            }
        }
        Slog.d(TAG, "Copied " + fromFiles.size() + " files into " + toDir);
    
private voiddestroyInternal()

        synchronized (mLock) {
            mSealed = true;
            mDestroyed = true;

            // Force shut down all bridges
            for (FileBridge bridge : mBridges) {
                bridge.forceClose();
            }
        }
        if (stageDir != null) {
            FileUtils.deleteContents(stageDir);
            stageDir.delete();
        }
        if (stageCid != null) {
            PackageHelper.destroySdDir(stageCid);
        }
    
private voiddispatchSessionFinished(int returnCode, java.lang.String msg, android.os.Bundle extras)

        mFinalStatus = returnCode;
        mFinalMessage = msg;

        if (mRemoteObserver != null) {
            try {
                mRemoteObserver.onPackageInstalled(mPackageName, returnCode, msg, extras);
            } catch (RemoteException ignored) {
            }
        }

        final boolean success = (returnCode == PackageManager.INSTALL_SUCCEEDED);
        mCallback.onSessionFinished(this, success);
    
voiddump(com.android.internal.util.IndentingPrintWriter pw)

        synchronized (mLock) {
            dumpLocked(pw);
        }
    
private voiddumpLocked(com.android.internal.util.IndentingPrintWriter pw)

        pw.println("Session " + sessionId + ":");
        pw.increaseIndent();

        pw.printPair("userId", userId);
        pw.printPair("installerPackageName", installerPackageName);
        pw.printPair("installerUid", installerUid);
        pw.printPair("createdMillis", createdMillis);
        pw.printPair("stageDir", stageDir);
        pw.printPair("stageCid", stageCid);
        pw.println();

        params.dump(pw);

        pw.printPair("mClientProgress", mClientProgress);
        pw.printPair("mProgress", mProgress);
        pw.printPair("mSealed", mSealed);
        pw.printPair("mPermissionsAccepted", mPermissionsAccepted);
        pw.printPair("mDestroyed", mDestroyed);
        pw.printPair("mBridges", mBridges.size());
        pw.printPair("mFinalStatus", mFinalStatus);
        pw.printPair("mFinalMessage", mFinalMessage);
        pw.println();

        pw.decreaseIndent();
    
private static voidextractNativeLibraries(java.io.File packageDir, java.lang.String abiOverride)

        // Always start from a clean slate
        final File libDir = new File(packageDir, NativeLibraryHelper.LIB_DIR_NAME);
        NativeLibraryHelper.removeNativeBinariesFromDirLI(libDir, true);

        NativeLibraryHelper.Handle handle = null;
        try {
            handle = NativeLibraryHelper.Handle.create(packageDir);
            final int res = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libDir,
                    abiOverride);
            if (res != PackageManager.INSTALL_SUCCEEDED) {
                throw new PackageManagerException(res,
                        "Failed to extract native libraries, res=" + res);
            }
        } catch (IOException e) {
            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                    "Failed to extract native libraries", e);
        } finally {
            IoUtils.closeQuietly(handle);
        }
    
private voidfinalizeAndFixContainer(java.lang.String cid)

        if (!PackageHelper.finalizeSdDir(cid)) {
            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
                    "Failed to finalize container " + cid);
        }

        final int uid = mPm.getPackageUid(PackageManagerService.DEFAULT_CONTAINER_PACKAGE,
                UserHandle.USER_OWNER);
        final int gid = UserHandle.getSharedAppGid(uid);
        if (!PackageHelper.fixSdPermissions(cid, gid, null)) {
            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
                    "Failed to fix permissions on container " + cid);
        }
    
public android.content.pm.PackageInstaller.SessionInfogenerateInfo()

        final SessionInfo info = new SessionInfo();
        synchronized (mLock) {
            info.sessionId = sessionId;
            info.installerPackageName = installerPackageName;
            info.resolvedBaseCodePath = (mResolvedBaseFile != null) ?
                    mResolvedBaseFile.getAbsolutePath() : null;
            info.progress = mProgress;
            info.sealed = mSealed;
            info.active = mActiveCount.get() > 0;

            info.mode = params.mode;
            info.sizeBytes = params.sizeBytes;
            info.appPackageName = params.appPackageName;
            info.appIcon = params.appIcon;
            info.appLabel = params.appLabel;
        }
        return info;
    
public java.lang.String[]getNames()

        assertPreparedAndNotSealed("getNames");
        try {
            return resolveStageDir().list();
        } catch (IOException e) {
            throw ExceptionUtils.wrap(e);
        }
    
private booleanisLinkPossible(java.util.List fromFiles, java.io.File toDir)
Determine if creating hard links between source and destination is possible. That is, do they all live on the same underlying device.

        try {
            final StructStat toStat = Os.stat(toDir.getAbsolutePath());
            for (File fromFile : fromFiles) {
                final StructStat fromStat = Os.stat(fromFile.getAbsolutePath());
                if (fromStat.st_dev != toStat.st_dev) {
                    return false;
                }
            }
        } catch (ErrnoException e) {
            Slog.w(TAG, "Failed to detect if linking possible: " + e);
            return false;
        }
        return true;
    
public booleanisPrepared()

        synchronized (mLock) {
            return mPrepared;
        }
    
public booleanisSealed()

        synchronized (mLock) {
            return mSealed;
        }
    
private static voidlinkFiles(java.util.List fromFiles, java.io.File toDir)

        for (File fromFile : fromFiles) {
            final File toFile = new File(toDir, fromFile.getName());
            try {
                if (LOGD) Slog.d(TAG, "Linking " + fromFile + " to " + toFile);
                Os.link(fromFile.getAbsolutePath(), toFile.getAbsolutePath());
            } catch (ErrnoException e) {
                throw new IOException("Failed to link " + fromFile + " to " + toFile, e);
            }
        }
        Slog.d(TAG, "Linked " + fromFiles.size() + " files into " + toDir);
    
public voidopen()

        if (mActiveCount.getAndIncrement() == 0) {
            mCallback.onSessionActiveChanged(this, true);
        }

        synchronized (mLock) {
            if (!mPrepared) {
                if (stageDir != null) {
                    prepareInternalStageDir(stageDir);
                } else if (stageCid != null) {
                    prepareExternalStageCid(stageCid, params.sizeBytes);

                    // TODO: deliver more granular progress for ASEC allocation
                    mInternalProgress = 0.25f;
                    computeProgressLocked(true);
                } else {
                    throw new IllegalArgumentException(
                            "Exactly one of stageDir or stageCid stage must be set");
                }

                mPrepared = true;
                mCallback.onSessionPrepared(this);
            }
        }
    
public android.os.ParcelFileDescriptoropenRead(java.lang.String name)

        try {
            return openReadInternal(name);
        } catch (IOException e) {
            throw ExceptionUtils.wrap(e);
        }
    
private android.os.ParcelFileDescriptoropenReadInternal(java.lang.String name)

        assertPreparedAndNotSealed("openRead");

        try {
            if (!FileUtils.isValidExtFilename(name)) {
                throw new IllegalArgumentException("Invalid name: " + name);
            }
            final File target = new File(resolveStageDir(), name);

            final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), O_RDONLY, 0);
            return new ParcelFileDescriptor(targetFd);

        } catch (ErrnoException e) {
            throw e.rethrowAsIOException();
        }
    
public android.os.ParcelFileDescriptoropenWrite(java.lang.String name, long offsetBytes, long lengthBytes)

        try {
            return openWriteInternal(name, offsetBytes, lengthBytes);
        } catch (IOException e) {
            throw ExceptionUtils.wrap(e);
        }
    
private android.os.ParcelFileDescriptoropenWriteInternal(java.lang.String name, long offsetBytes, long lengthBytes)

        // Quick sanity check of state, and allocate a pipe for ourselves. We
        // then do heavy disk allocation outside the lock, but this open pipe
        // will block any attempted install transitions.
        final FileBridge bridge;
        synchronized (mLock) {
            assertPreparedAndNotSealed("openWrite");

            bridge = new FileBridge();
            mBridges.add(bridge);
        }

        try {
            // Use installer provided name for now; we always rename later
            if (!FileUtils.isValidExtFilename(name)) {
                throw new IllegalArgumentException("Invalid name: " + name);
            }
            final File target = new File(resolveStageDir(), name);

            // TODO: this should delegate to DCS so the system process avoids
            // holding open FDs into containers.
            final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(),
                    O_CREAT | O_WRONLY, 0644);
            Os.chmod(target.getAbsolutePath(), 0644);

            // If caller specified a total length, allocate it for them. Free up
            // cache space to grow, if needed.
            if (lengthBytes > 0) {
                final StructStat stat = Libcore.os.fstat(targetFd);
                final long deltaBytes = lengthBytes - stat.st_size;
                // Only need to free up space when writing to internal stage
                if (stageDir != null && deltaBytes > 0) {
                    mPm.freeStorage(deltaBytes);
                }
                Libcore.os.posix_fallocate(targetFd, 0, lengthBytes);
            }

            if (offsetBytes > 0) {
                Libcore.os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET);
            }

            bridge.setTargetFile(targetFd);
            bridge.start();
            return new ParcelFileDescriptor(bridge.getClientSocket());

        } catch (ErrnoException e) {
            throw e.rethrowAsIOException();
        }
    
private static voidresizeContainer(java.lang.String cid, long targetSize)

        String path = PackageHelper.getSdDir(cid);
        if (path == null) {
            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
                    "Failed to find mounted " + cid);
        }

        final long currentSize = new File(path).getTotalSpace();
        if (currentSize > targetSize) {
            Slog.w(TAG, "Current size " + currentSize + " is larger than target size "
                    + targetSize + "; skipping resize");
            return;
        }

        if (!PackageHelper.unMountSdDir(cid)) {
            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
                    "Failed to unmount " + cid + " before resize");
        }

        if (!PackageHelper.resizeSdDir(targetSize, cid,
                PackageManagerService.getEncryptKey())) {
            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
                    "Failed to resize " + cid + " to " + targetSize + " bytes");
        }

        path = PackageHelper.mountSdDir(cid, PackageManagerService.getEncryptKey(),
                Process.SYSTEM_UID, false);
        if (path == null) {
            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
                    "Failed to mount " + cid + " after resize");
        }
    
private java.io.FileresolveStageDir()
Resolve the actual location where staged data should be written. This might point at an ASEC mount point, which is why we delay path resolution until someone actively works with the session.

        synchronized (mLock) {
            if (mResolvedStageDir == null) {
                if (stageDir != null) {
                    mResolvedStageDir = stageDir;
                } else {
                    final String path = PackageHelper.getSdDir(stageCid);
                    if (path != null) {
                        mResolvedStageDir = new File(path);
                    } else {
                        throw new IOException("Failed to resolve path to container " + stageCid);
                    }
                }
            }
            return mResolvedStageDir;
        }
    
public voidsetClientProgress(float progress)

        synchronized (mLock) {
            // Always publish first staging movement
            final boolean forcePublish = (mClientProgress == 0);
            mClientProgress = progress;
            computeProgressLocked(forcePublish);
        }
    
voidsetPermissionsResult(boolean accepted)

        if (!mSealed) {
            throw new SecurityException("Must be sealed to accept permissions");
        }

        if (accepted) {
            // Mark and kick off another install pass
            mPermissionsAccepted = true;
            mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
        } else {
            destroyInternal();
            dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null);
        }
    
private voidvalidateInstallLocked()
Validate install by confirming that all application packages are have consistent package name, version code, and signing certificates.

Clears and populates {@link #mResolvedBaseFile}, {@link #mResolvedStagedFiles}, and {@link #mResolvedInheritedFiles}.

Renames package files in stage to match split names defined inside.

Note that upgrade compatibility is still performed by {@link PackageManagerService}.

        mPackageName = null;
        mVersionCode = -1;
        mSignatures = null;

        mResolvedBaseFile = null;
        mResolvedStagedFiles.clear();
        mResolvedInheritedFiles.clear();

        final File[] files = mResolvedStageDir.listFiles();
        if (ArrayUtils.isEmpty(files)) {
            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged");
        }

        // Verify that all staged packages are internally consistent
        final ArraySet<String> stagedSplits = new ArraySet<>();
        for (File file : files) {

            // Installers can't stage directories, so it's fine to ignore
            // entries like "lost+found".
            if (file.isDirectory()) continue;

            final ApkLite apk;
            try {
                apk = PackageParser.parseApkLite(file, PackageParser.PARSE_COLLECT_CERTIFICATES);
            } catch (PackageParserException e) {
                throw PackageManagerException.from(e);
            }

            if (!stagedSplits.add(apk.splitName)) {
                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                        "Split " + apk.splitName + " was defined multiple times");
            }

            // Use first package to define unknown values
            if (mPackageName == null) {
                mPackageName = apk.packageName;
                mVersionCode = apk.versionCode;
            }
            if (mSignatures == null) {
                mSignatures = apk.signatures;
            }

            assertApkConsistent(String.valueOf(file), apk);

            // Take this opportunity to enforce uniform naming
            final String targetName;
            if (apk.splitName == null) {
                targetName = "base.apk";
            } else {
                targetName = "split_" + apk.splitName + ".apk";
            }
            if (!FileUtils.isValidExtFilename(targetName)) {
                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                        "Invalid filename: " + targetName);
            }

            final File targetFile = new File(mResolvedStageDir, targetName);
            if (!file.equals(targetFile)) {
                file.renameTo(targetFile);
            }

            // Base is coming from session
            if (apk.splitName == null) {
                mResolvedBaseFile = targetFile;
            }

            mResolvedStagedFiles.add(targetFile);
        }

        if (params.mode == SessionParams.MODE_FULL_INSTALL) {
            // Full installs must include a base package
            if (!stagedSplits.contains(null)) {
                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                        "Full install must include a base package");
            }

        } else {
            // Partial installs must be consistent with existing install
            final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId);
            if (app == null) {
                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                        "Missing existing base package for " + mPackageName);
            }

            final PackageLite existing;
            final ApkLite existingBase;
            try {
                existing = PackageParser.parsePackageLite(new File(app.getCodePath()), 0);
                existingBase = PackageParser.parseApkLite(new File(app.getBaseCodePath()),
                        PackageParser.PARSE_COLLECT_CERTIFICATES);
            } catch (PackageParserException e) {
                throw PackageManagerException.from(e);
            }

            assertApkConsistent("Existing base", existingBase);

            // Inherit base if not overridden
            if (mResolvedBaseFile == null) {
                mResolvedBaseFile = new File(app.getBaseCodePath());
                mResolvedInheritedFiles.add(mResolvedBaseFile);
            }

            // Inherit splits if not overridden
            if (!ArrayUtils.isEmpty(existing.splitNames)) {
                for (int i = 0; i < existing.splitNames.length; i++) {
                    final String splitName = existing.splitNames[i];
                    final File splitFile = new File(existing.splitCodePaths[i]);

                    if (!stagedSplits.contains(splitName)) {
                        mResolvedInheritedFiles.add(splitFile);
                    }
                }
            }
        }