PackageInstallerSessionpublic 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 | stageDirStaging 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 | mPackageNameFields derived from commit parsing | private int | mVersionCode | private android.content.pm.Signature[] | mSignatures | private File | mResolvedBaseFilePath 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 void | abandon()
destroyInternal();
dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
| public void | addClientProgress(float progress)
synchronized (mLock) {
setClientProgress(mClientProgress + progress);
}
| private void | assertApkConsistent(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 void | assertPreparedAndNotSealed(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 long | calculateInstalledSize()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 void | close()
if (mActiveCount.decrementAndGet() == 0) {
mCallback.onSessionActiveChanged(this, false);
}
| public void | commit(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 void | commitLocked()
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 void | computeProgressLocked(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 void | copyFiles(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 void | destroyInternal()
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 void | dispatchSessionFinished(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);
| void | dump(com.android.internal.util.IndentingPrintWriter pw)
synchronized (mLock) {
dumpLocked(pw);
}
| private void | dumpLocked(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 void | extractNativeLibraries(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 void | finalizeAndFixContainer(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.SessionInfo | generateInfo()
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 boolean | isLinkPossible(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 boolean | isPrepared()
synchronized (mLock) {
return mPrepared;
}
| public boolean | isSealed()
synchronized (mLock) {
return mSealed;
}
| private static void | linkFiles(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 void | open()
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.ParcelFileDescriptor | openRead(java.lang.String name)
try {
return openReadInternal(name);
} catch (IOException e) {
throw ExceptionUtils.wrap(e);
}
| private android.os.ParcelFileDescriptor | openReadInternal(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.ParcelFileDescriptor | openWrite(java.lang.String name, long offsetBytes, long lengthBytes)
try {
return openWriteInternal(name, offsetBytes, lengthBytes);
} catch (IOException e) {
throw ExceptionUtils.wrap(e);
}
| private android.os.ParcelFileDescriptor | openWriteInternal(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 void | resizeContainer(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.File | resolveStageDir()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 void | setClientProgress(float progress)
synchronized (mLock) {
// Always publish first staging movement
final boolean forcePublish = (mClientProgress == 0);
mClientProgress = progress;
computeProgressLocked(forcePublish);
}
| void | setPermissionsResult(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 void | validateInstallLocked()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);
}
}
}
}
|
|