summaryrefslogtreecommitdiffstats
path: root/services/core
diff options
context:
space:
mode:
Diffstat (limited to 'services/core')
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java210
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java539
-rwxr-xr-xservices/core/java/com/android/server/pm/PackageManagerService.java54
3 files changed, 794 insertions, 9 deletions
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
new file mode 100644
index 0000000..5fdfce4
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2014 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 com.android.server.pm;
+
+import static android.content.pm.PackageManager.INSTALL_ALL_USERS;
+import static android.content.pm.PackageManager.INSTALL_FROM_ADB;
+import static android.content.pm.PackageManager.INSTALL_REPLACE_EXISTING;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.IPackageInstaller;
+import android.content.pm.IPackageInstallerSession;
+import android.content.pm.PackageInstallerParams;
+import android.os.Binder;
+import android.os.FileUtils;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.IoThread;
+import com.google.android.collect.Sets;
+
+import java.io.File;
+
+public class PackageInstallerService extends IPackageInstaller.Stub {
+ private static final String TAG = "PackageInstaller";
+
+ // TODO: destroy sessions with old timestamps
+ // TODO: remove outstanding sessions when installer package goes away
+
+ private final Context mContext;
+ private final PackageManagerService mPm;
+ private final AppOpsManager mAppOps;
+
+ private final File mStagingDir;
+
+ private final HandlerThread mInstallThread = new HandlerThread(TAG);
+ private final Callback mCallback = new Callback();
+
+ @GuardedBy("mSessions")
+ private int mNextSessionId;
+ @GuardedBy("mSessions")
+ private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>();
+
+ public PackageInstallerService(Context context, PackageManagerService pm, File stagingDir) {
+ mContext = context;
+ mPm = pm;
+ mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+
+ mStagingDir = stagingDir;
+ mStagingDir.mkdirs();
+
+ synchronized (mSessions) {
+ readSessionsLocked();
+
+ // Clean up orphaned staging directories
+ final ArraySet<String> dirs = Sets.newArraySet(mStagingDir.list());
+ for (int i = 0; i < mSessions.size(); i++) {
+ dirs.remove(Integer.toString(mSessions.keyAt(i)));
+ }
+ for (String dirName : dirs) {
+ Slog.w(TAG, "Deleting orphan session " + dirName);
+ final File dir = new File(mStagingDir, dirName);
+ FileUtils.deleteContents(dir);
+ dir.delete();
+ }
+ }
+ }
+
+ private void readSessionsLocked() {
+ // TODO: implement persisting
+ mSessions.clear();
+ mNextSessionId = 1;
+ }
+
+ private void writeSessionsLocked() {
+ // TODO: implement persisting
+ }
+
+ private void writeSessionsAsync() {
+ IoThread.getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mSessions) {
+ writeSessionsLocked();
+ }
+ }
+ });
+ }
+
+ @Override
+ public int createSession(int userId, String installerPackageName,
+ PackageInstallerParams params) {
+ final int callingUid = Binder.getCallingUid();
+ mPm.enforceCrossUserPermission(callingUid, userId, false, TAG);
+ mAppOps.checkPackage(callingUid, installerPackageName);
+
+ if (mPm.isUserRestricted(UserHandle.getUserId(callingUid),
+ UserManager.DISALLOW_INSTALL_APPS)) {
+ throw new SecurityException("User restriction prevents installing");
+ }
+
+ if ((callingUid == Process.SHELL_UID) || (callingUid == 0)) {
+ params.installFlags |= INSTALL_FROM_ADB;
+ } else {
+ params.installFlags &= ~INSTALL_FROM_ADB;
+ params.installFlags &= ~INSTALL_ALL_USERS;
+ params.installFlags |= INSTALL_REPLACE_EXISTING;
+ }
+
+ synchronized (mSessions) {
+ final int sessionId = allocateSessionIdLocked();
+ final long createdMillis = System.currentTimeMillis();
+ final File sessionDir = new File(mStagingDir, Integer.toString(sessionId));
+ sessionDir.mkdirs();
+
+ final PackageInstallerSession session = new PackageInstallerSession(mCallback, mPm,
+ sessionId, userId, installerPackageName, callingUid, params, createdMillis,
+ sessionDir, mInstallThread.getLooper());
+ mSessions.put(sessionId, session);
+
+ writeSessionsAsync();
+ return sessionId;
+ }
+ }
+
+ @Override
+ public IPackageInstallerSession openSession(int sessionId) {
+ synchronized (mSessions) {
+ final PackageInstallerSession session = mSessions.get(sessionId);
+ if (session == null) {
+ throw new IllegalStateException("Missing session " + sessionId);
+ }
+ if (Binder.getCallingUid() != session.installerUid) {
+ throw new SecurityException("Caller has no access to session " + sessionId);
+ }
+ return session;
+ }
+ }
+
+ private int allocateSessionIdLocked() {
+ if (mSessions.get(mNextSessionId) != null) {
+ throw new IllegalStateException("Next session already allocated");
+ }
+ return mNextSessionId++;
+ }
+
+ @Override
+ public int[] getSessions(int userId, String installerPackageName) {
+ final int callingUid = Binder.getCallingUid();
+ mPm.enforceCrossUserPermission(callingUid, userId, false, TAG);
+ mAppOps.checkPackage(callingUid, installerPackageName);
+
+ int[] matching = new int[0];
+ synchronized (mSessions) {
+ for (int i = 0; i < mSessions.size(); i++) {
+ final int key = mSessions.keyAt(i);
+ final PackageInstallerSession session = mSessions.valueAt(i);
+ if (session.userId == userId
+ && session.installerPackageName.equals(installerPackageName)) {
+ matching = ArrayUtils.appendInt(matching, key);
+ }
+ }
+ }
+ return matching;
+ }
+
+ @Override
+ public void uninstall(int userId, String basePackageName, IPackageDeleteObserver observer) {
+ mPm.deletePackageAsUser(basePackageName, observer, userId, 0);
+ }
+
+ @Override
+ public void uninstallSplit(int userId, String basePackageName, String overlayName,
+ IPackageDeleteObserver observer) {
+ // TODO: flesh out once PM has split support
+ throw new UnsupportedOperationException();
+ }
+
+ class Callback {
+ public void onProgressChanged(PackageInstallerSession session) {
+ // TODO: notify listeners
+ }
+
+ public void onSessionInvalid(PackageInstallerSession session) {
+ writeSessionsAsync();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
new file mode 100644
index 0000000..f90d7ab
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -0,0 +1,539 @@
+/*
+ * Copyright (C) 2014 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 com.android.server.pm;
+
+import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
+import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
+import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
+import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageInstallObserver2;
+import android.content.pm.IPackageInstallerSession;
+import android.content.pm.PackageInstallerParams;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.PackageLite;
+import android.content.pm.Signature;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.SELinux;
+import android.system.ErrnoException;
+import android.system.OsConstants;
+import android.system.StructStat;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.content.NativeLibraryHelper;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
+
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+import libcore.io.Streams;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
+public class PackageInstallerSession extends IPackageInstallerSession.Stub {
+ private static final String TAG = "PackageInstaller";
+
+ private final PackageInstallerService.Callback mCallback;
+ private final PackageManagerService mPm;
+ private final Handler mHandler;
+
+ public final int sessionId;
+ public final int userId;
+ public final String installerPackageName;
+ /** UID not persisted */
+ public final int installerUid;
+ public final PackageInstallerParams params;
+ public final long createdMillis;
+ public final File sessionDir;
+
+ private static final int MSG_INSTALL = 0;
+
+ private Handler.Callback mHandlerCallback = new Handler.Callback() {
+ @Override
+ public boolean handleMessage(Message msg) {
+ synchronized (mLock) {
+ if (msg.obj != null) {
+ mRemoteObserver = (IPackageInstallObserver2) msg.obj;
+ }
+
+ try {
+ installLocked();
+ } catch (InstallFailedException e) {
+ Slog.e(TAG, "Install failed: " + e);
+ try {
+ mRemoteObserver.packageInstalled(mPackageName, null, e.error);
+ } catch (RemoteException ignored) {
+ }
+ }
+
+ return true;
+ }
+ }
+ };
+
+ private final Object mLock = new Object();
+
+ private int mProgress;
+
+ private String mPackageName;
+ private int mVersionCode;
+ private Signature[] mSignatures;
+
+ private boolean mMutationsAllowed;
+ private boolean mVerifierConfirmed;
+ private boolean mPermissionsConfirmed;
+ private boolean mInvalid;
+
+ private ArrayList<WritePipe> mPipes = new ArrayList<>();
+
+ private IPackageInstallObserver2 mRemoteObserver;
+
+ public PackageInstallerSession(PackageInstallerService.Callback callback,
+ PackageManagerService pm, int sessionId, int userId, String installerPackageName,
+ int installerUid, PackageInstallerParams params, long createdMillis, File sessionDir,
+ Looper looper) {
+ mCallback = callback;
+ 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.sessionDir = sessionDir;
+
+ // Check against any explicitly provided signatures
+ mSignatures = params.signatures;
+
+ // TODO: splice in flag when restoring persisted session
+ mMutationsAllowed = true;
+
+ if (pm.checkPermission(android.Manifest.permission.INSTALL_PACKAGES, installerPackageName)
+ == PackageManager.PERMISSION_GRANTED) {
+ mPermissionsConfirmed = true;
+ }
+ }
+
+ @Override
+ public void updateProgress(int progress) {
+ mProgress = progress;
+ mCallback.onProgressChanged(this);
+ }
+
+ @Override
+ public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) {
+ // TODO: relay over to DCS when installing to ASEC
+
+ // 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 WritePipe pipe;
+ synchronized (mLock) {
+ if (!mMutationsAllowed) {
+ throw new IllegalStateException("Mutations not allowed");
+ }
+
+ pipe = new WritePipe();
+ mPipes.add(pipe);
+ }
+
+ 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(sessionDir, name);
+
+ final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(),
+ OsConstants.O_CREAT | OsConstants.O_WRONLY, 00700);
+
+ // 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;
+ if (deltaBytes > 0) {
+ mPm.freeStorage(deltaBytes);
+ }
+ Libcore.os.posix_fallocate(targetFd, 0, lengthBytes);
+ }
+
+ if (offsetBytes > 0) {
+ Libcore.os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET);
+ }
+
+ pipe.setTargetFd(targetFd);
+ pipe.start();
+ return pipe.getWriteFd();
+
+ } catch (ErrnoException e) {
+ throw new IllegalStateException("Failed to write", e);
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to write", e);
+ }
+ }
+
+ @Override
+ public void install(IPackageInstallObserver2 observer) {
+ Preconditions.checkNotNull(observer);
+ mHandler.obtainMessage(MSG_INSTALL, observer).sendToTarget();
+ }
+
+ private void installLocked() throws InstallFailedException {
+ if (mInvalid) {
+ throw new InstallFailedException(INSTALL_FAILED_ALREADY_EXISTS, "Invalid session");
+ }
+
+ // Verify that all writers are hands-off
+ if (mMutationsAllowed) {
+ for (WritePipe pipe : mPipes) {
+ if (!pipe.isClosed()) {
+ throw new InstallFailedException(INSTALL_FAILED_PACKAGE_CHANGED,
+ "Files still open");
+ }
+ }
+ mMutationsAllowed = false;
+
+ // TODO: persist disabled mutations before going forward, since
+ // beyond this point we may have hardlinks to the valid install
+ }
+
+ // 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);
+
+ if (!mVerifierConfirmed) {
+ // TODO: async communication with verifier
+ // when they confirm, we'll kick off another install() pass
+ mVerifierConfirmed = true;
+ }
+
+ if (!mPermissionsConfirmed) {
+ // TODO: async confirm permissions with user
+ // when they confirm, we'll kick off another install() pass
+ mPermissionsConfirmed = true;
+ }
+
+ // Unpack any native libraries contained in this session
+ unpackNativeLibraries();
+
+ // Inherit any packages and native libraries from existing install that
+ // haven't been overridden.
+ if (!params.fullInstall) {
+ spliceExistingFilesIntoStage();
+ }
+
+ // TODO: for ASEC based applications, grow and stream in packages
+
+ // 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 remoteObserver = mRemoteObserver;
+ final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
+ @Override
+ public void packageInstalled(String basePackageName, Bundle extras, int returnCode)
+ throws RemoteException {
+ destroy();
+ remoteObserver.packageInstalled(basePackageName, extras, returnCode);
+ }
+ };
+
+ mPm.installStage(mPackageName, this.sessionDir, localObserver, params.installFlags);
+ }
+
+ /**
+ * Validate install by confirming that all application packages are have
+ * consistent package name, version code, and signing certificates.
+ * <p>
+ * Renames package files in stage to match split names defined inside.
+ */
+ private void validateInstallLocked() throws InstallFailedException {
+ mPackageName = null;
+ mVersionCode = -1;
+ mSignatures = null;
+
+ final File[] files = sessionDir.listFiles();
+ if (ArrayUtils.isEmpty(files)) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, "No packages staged");
+ }
+
+ final ArraySet<String> seenSplits = new ArraySet<>();
+
+ // Verify that all staged packages are internally consistent
+ for (File file : files) {
+ final PackageLite info = PackageParser.parsePackageLite(file.getAbsolutePath(),
+ PackageParser.PARSE_GET_SIGNATURES);
+ if (info == null) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+ "Failed to parse " + file);
+ }
+
+ if (!seenSplits.add(info.splitName)) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+ "Split " + info.splitName + " was defined multiple times");
+ }
+
+ // Use first package to define unknown values
+ if (mPackageName != null) {
+ mPackageName = info.packageName;
+ mVersionCode = info.versionCode;
+ }
+ if (mSignatures != null) {
+ mSignatures = info.signatures;
+ }
+
+ assertPackageConsistent(String.valueOf(file), info.packageName, info.versionCode,
+ info.signatures);
+
+ // Take this opportunity to enforce uniform naming
+ final String name;
+ if (info.splitName == null) {
+ name = info.packageName + ".apk";
+ } else {
+ name = info.packageName + "-" + info.splitName + ".apk";
+ }
+ if (!FileUtils.isValidExtFilename(name)) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+ "Invalid filename: " + name);
+ }
+ if (!file.getName().equals(name)) {
+ file.renameTo(new File(file.getParentFile(), name));
+ }
+ }
+
+ // TODO: shift package signature verification to installer; we're
+ // currently relying on PMS to do this.
+ // TODO: teach about compatible upgrade keysets.
+
+ if (params.fullInstall) {
+ // Full installs must include a base package
+ if (!seenSplits.contains(null)) {
+ throw new InstallFailedException(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 InstallFailedException(INSTALL_FAILED_INVALID_APK,
+ "Missing existing base package for " + mPackageName);
+ }
+
+ final PackageLite info = PackageParser.parsePackageLite(app.sourceDir,
+ PackageParser.PARSE_GET_SIGNATURES);
+ if (info == null) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+ "Failed to parse existing base " + app.sourceDir);
+ }
+
+ assertPackageConsistent("Existing base", info.packageName, info.versionCode,
+ info.signatures);
+ }
+ }
+
+ private void assertPackageConsistent(String tag, String packageName, int versionCode,
+ Signature[] signatures) throws InstallFailedException {
+ if (!mPackageName.equals(packageName)) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, tag + " package "
+ + packageName + " inconsistent with " + mPackageName);
+ }
+ if (mVersionCode != versionCode) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, tag
+ + " version code " + versionCode + " inconsistent with "
+ + mVersionCode);
+ }
+ if (!Signature.areExactMatch(mSignatures, signatures)) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+ tag + " signatures are inconsistent");
+ }
+ }
+
+ /**
+ * Application is already installed; splice existing files that haven't been
+ * overridden into our stage.
+ */
+ private void spliceExistingFilesIntoStage() throws InstallFailedException {
+ final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId);
+ final File existingDir = new File(app.sourceDir).getParentFile();
+
+ try {
+ linkTreeIgnoringExisting(existingDir, sessionDir);
+ } catch (ErrnoException e) {
+ throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR,
+ "Failed to splice into stage");
+ }
+ }
+
+ /**
+ * Recursively hard link all files from source directory tree to target.
+ * When a file already exists in the target tree, it leaves that file
+ * intact.
+ */
+ private void linkTreeIgnoringExisting(File sourceDir, File targetDir) throws ErrnoException {
+ final File[] sourceContents = sourceDir.listFiles();
+ if (ArrayUtils.isEmpty(sourceContents)) return;
+
+ for (File sourceFile : sourceContents) {
+ final File targetFile = new File(targetDir, sourceFile.getName());
+
+ if (sourceFile.isDirectory()) {
+ targetFile.mkdir();
+ linkTreeIgnoringExisting(sourceFile, targetFile);
+ } else {
+ Libcore.os.link(sourceFile.getAbsolutePath(), targetFile.getAbsolutePath());
+ }
+ }
+ }
+
+ private void unpackNativeLibraries() throws InstallFailedException {
+ final File libDir = new File(sessionDir, "lib");
+
+ if (!libDir.mkdir()) {
+ throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR,
+ "Failed to create " + libDir);
+ }
+
+ try {
+ Libcore.os.chmod(libDir.getAbsolutePath(), 0755);
+ } catch (ErrnoException e) {
+ throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR,
+ "Failed to prepare " + libDir + ": " + e);
+ }
+
+ if (!SELinux.restorecon(libDir)) {
+ throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR,
+ "Failed to set context on " + libDir);
+ }
+
+ // Unpack all native libraries under stage
+ final File[] files = sessionDir.listFiles();
+ if (ArrayUtils.isEmpty(files)) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, "No packages staged");
+ }
+
+ for (File file : files) {
+ final NativeLibraryHelper.ApkHandle handle = new NativeLibraryHelper.ApkHandle(file);
+ try {
+ final int abiIndex = NativeLibraryHelper.findSupportedAbi(handle,
+ Build.SUPPORTED_ABIS);
+ if (abiIndex >= 0) {
+ int copyRet = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle, libDir,
+ Build.SUPPORTED_ABIS[abiIndex]);
+ if (copyRet != INSTALL_SUCCEEDED) {
+ throw new InstallFailedException(copyRet,
+ "Failed to copy native libraries for " + file);
+ }
+ } else if (abiIndex != PackageManager.NO_NATIVE_LIBRARIES) {
+ throw new InstallFailedException(abiIndex,
+ "Failed to copy native libraries for " + file);
+ }
+ } finally {
+ handle.close();
+ }
+ }
+ }
+
+ @Override
+ public void destroy() {
+ try {
+ synchronized (mLock) {
+ mInvalid = true;
+ }
+ FileUtils.deleteContents(sessionDir);
+ sessionDir.delete();
+ } finally {
+ mCallback.onSessionInvalid(this);
+ }
+ }
+
+ private static class WritePipe extends Thread {
+ private final ParcelFileDescriptor[] mPipe;
+
+ private FileDescriptor mTargetFd;
+
+ private volatile boolean mClosed;
+
+ public WritePipe() {
+ try {
+ mPipe = ParcelFileDescriptor.createPipe();
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to create pipe");
+ }
+ }
+
+ public boolean isClosed() {
+ return mClosed;
+ }
+
+ public void setTargetFd(FileDescriptor targetFd) {
+ mTargetFd = targetFd;
+ }
+
+ public ParcelFileDescriptor getWriteFd() {
+ return mPipe[1];
+ }
+
+ @Override
+ public void run() {
+ FileInputStream in = null;
+ FileOutputStream out = null;
+ try {
+ // TODO: look at switching to sendfile(2) to speed up
+ in = new FileInputStream(mPipe[0].getFileDescriptor());
+ out = new FileOutputStream(mTargetFd);
+ Streams.copy(in, out);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to stream data: " + e);
+ } finally {
+ IoUtils.closeQuietly(mPipe[0]);
+ IoUtils.closeQuietly(mTargetFd);
+ mClosed = true;
+ }
+ }
+ }
+
+ private class InstallFailedException extends Exception {
+ private final int error;
+
+ public InstallFailedException(int error, String detailMessage) {
+ super(detailMessage);
+ this.error = error;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index d1333b2..a3ecc05 100755
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -18,6 +18,7 @@ package com.android.server.pm;
import static android.Manifest.permission.GRANT_REVOKE_PERMISSIONS;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
+import static android.Manifest.permission.INSTALL_PACKAGES;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
@@ -59,6 +60,7 @@ import org.xmlpull.v1.XmlSerializer;
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.IActivityManager;
+import android.app.PackageInstallObserver;
import android.app.admin.IDevicePolicyManager;
import android.app.backup.IBackupManager;
import android.content.BroadcastReceiver;
@@ -78,6 +80,7 @@ import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageDeleteObserver;
import android.content.pm.IPackageInstallObserver;
import android.content.pm.IPackageInstallObserver2;
+import android.content.pm.IPackageInstaller;
import android.content.pm.IPackageManager;
import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageStatsObserver;
@@ -86,6 +89,7 @@ import android.content.pm.ManifestDigest;
import android.content.pm.PackageCleanItem;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInfoLite;
+import android.content.pm.PackageInstallerParams;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser.ActivityIntentInfo;
import android.content.pm.PackageParser;
@@ -179,6 +183,7 @@ import java.util.concurrent.atomic.AtomicLong;
import dalvik.system.DexFile;
import dalvik.system.StaleDexCacheError;
import dalvik.system.VMRuntime;
+
import libcore.io.IoUtils;
/**
@@ -351,6 +356,8 @@ public class PackageManagerService extends IPackageManager.Stub {
// apps.
final File mDrmAppPrivateInstallDir;
+ final File mAppStagingDir;
+
// ----------------------------------------------------------------
// Lock for state used when installing and doing other long running
@@ -457,6 +464,8 @@ public class PackageManagerService extends IPackageManager.Stub {
final SparseArray<PackageVerificationState> mPendingVerification
= new SparseArray<PackageVerificationState>();
+ final PackageInstallerService mInstallerService;
+
HashSet<PackageParser.Package> mDeferredDexOpt = null;
/** Token for keys in mPendingVerification. */
@@ -1339,6 +1348,7 @@ public class PackageManagerService extends IPackageManager.Stub {
mAsecInternalPath = new File(dataDir, "app-asec").getPath();
mUserAppDataDir = new File(dataDir, "user");
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
+ mAppStagingDir = new File(dataDir, "app-staging");
sUserManager = new UserManagerService(context, this,
mInstallLock, mPackages);
@@ -1701,14 +1711,17 @@ public class PackageManagerService extends IPackageManager.Stub {
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_READY,
SystemClock.uptimeMillis());
- // Now after opening every single application zip, make sure they
- // are all flushed. Not really needed, but keeps things nice and
- // tidy.
- Runtime.getRuntime().gc();
mRequiredVerifierPackage = getRequiredVerifierLPr();
} // synchronized (mPackages)
} // synchronized (mInstallLock)
+
+ mInstallerService = new PackageInstallerService(context, this, mAppStagingDir);
+
+ // Now after opening every single application zip, make sure they
+ // are all flushed. Not really needed, but keeps things nice and
+ // tidy.
+ Runtime.getRuntime().gc();
}
private static void pruneDexFiles(File cacheDir) {
@@ -2252,7 +2265,8 @@ public class PackageManagerService extends IPackageManager.Stub {
if ((flags & PackageManager.GET_UNINSTALLED_PACKAGES) == 0) {
return null;
}
- pkg = new PackageParser.Package(packageName);
+ // TODO: teach about reading split name
+ pkg = new PackageParser.Package(packageName, null);
pkg.applicationInfo.packageName = packageName;
pkg.applicationInfo.flags = ps.pkgFlags | ApplicationInfo.FLAG_IS_DATA_ONLY;
pkg.applicationInfo.publicSourceDir = ps.resourcePathString;
@@ -2350,6 +2364,14 @@ public class PackageManagerService extends IPackageManager.Stub {
});
}
+ void freeStorage(long freeStorageSize) throws IOException {
+ synchronized (mInstallLock) {
+ if (mInstaller.freeCache(freeStorageSize) < 0) {
+ throw new IOException("Failed to free enough space");
+ }
+ }
+ }
+
@Override
public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) {
if (!sUserManager.exists(userId)) return null;
@@ -2533,10 +2555,9 @@ public class PackageManagerService extends IPackageManager.Stub {
* Checks if the request is from the system or an app that has INTERACT_ACROSS_USERS
* or INTERACT_ACROSS_USERS_FULL permissions, if the userid is not for the caller.
* @param message the message to log on security exception
- * @return
*/
- private void enforceCrossUserPermission(int callingUid, int userId,
- boolean requireFullPermission, String message) {
+ void enforceCrossUserPermission(int callingUid, int userId, boolean requireFullPermission,
+ String message) {
if (userId < 0) {
throw new IllegalArgumentException("Invalid userId " + userId);
}
@@ -7734,6 +7755,16 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+ void installStage(String basePackageName, File stageDir, IPackageInstallObserver2 observer,
+ int flags) {
+ // TODO: install stage!
+ try {
+ observer.packageInstalled(basePackageName, null,
+ PackageManager.INSTALL_FAILED_INTERNAL_ERROR);
+ } catch (RemoteException ignored) {
+ }
+ }
+
/**
* @hide
*/
@@ -7777,7 +7808,7 @@ public class PackageManagerService extends IPackageManager.Stub {
return PackageManager.INSTALL_SUCCEEDED;
}
- private boolean isUserRestricted(int userId, String restrictionKey) {
+ boolean isUserRestricted(int userId, String restrictionKey) {
Bundle restrictions = sUserManager.getUserRestrictions(userId);
if (restrictions.getBoolean(restrictionKey, false)) {
Log.w(TAG, "User is restricted: " + restrictionKey);
@@ -12900,4 +12931,9 @@ public class PackageManagerService extends IPackageManager.Stub {
Binder.restoreCallingIdentity(token);
}
}
+
+ @Override
+ public IPackageInstaller getPackageInstaller() {
+ return mInstallerService;
+ }
}