diff options
Diffstat (limited to 'services/core/java')
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; + } } |