From c028be4f3b8c7476b46859f66c3f33d528adf181 Mon Sep 17 00:00:00 2001 From: Suchi Amalapurapu Date: Mon, 25 Jan 2010 12:19:12 -0800 Subject: [PATCH] AppsOnSd feature - Add default container Add new remote interface to do temporary copies. The new remote stub handling is done on mHandler thread and doesn't need locking for now. Add new InstallArgs class and subclasses to isolate cases for installation. Move resource deletion for failed installs/upgrades to later on in installation cycle. Fix code path for forward locked apps when using scanPackageLI TODO's Fix installation paths to completely use InstallArgs based design later on. Get rid of using flags in various install/uninstall code paths. Ideally InstallArgs should be created using these flags and used in the rest of the code. Function renames. Revisit mount api's. --- Android.mk | 1 + cmds/installd/utils.c | 18 +- .../internal/app/IMediaContainerService.aidl | 28 + core/res/AndroidManifest.xml | 10 + core/res/res/values/strings.xml | 6 + packages/DefaultContainerService/Android.mk | 12 + .../AndroidManifest.xml | 14 + .../res/values/strings.xml | 23 + .../defcontainer/DefaultContainerService.java | 305 ++++++ .../java/com/android/server/Installer.java | 9 +- .../android/server/PackageManagerService.java | 949 +++++++++++++----- 11 files changed, 1106 insertions(+), 269 deletions(-) create mode 100755 core/java/com/android/internal/app/IMediaContainerService.aidl create mode 100755 packages/DefaultContainerService/Android.mk create mode 100755 packages/DefaultContainerService/AndroidManifest.xml create mode 100644 packages/DefaultContainerService/res/values/strings.xml create mode 100644 packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java diff --git a/Android.mk b/Android.mk index 568352ea796b5..5f9bea4eb86f4 100644 --- a/Android.mk +++ b/Android.mk @@ -136,6 +136,7 @@ LOCAL_SRC_FILES += \ core/java/android/speech/tts/ITtsCallback.aidl \ core/java/com/android/internal/app/IBatteryStats.aidl \ core/java/com/android/internal/app/IUsageStats.aidl \ + core/java/com/android/internal/app/IMediaContainerService.aidl \ core/java/com/android/internal/appwidget/IAppWidgetService.aidl \ core/java/com/android/internal/appwidget/IAppWidgetHost.aidl \ core/java/com/android/internal/backup/IBackupTransport.aidl \ diff --git a/cmds/installd/utils.c b/cmds/installd/utils.c index 5db5545947462..555c19e07aa9a 100644 --- a/cmds/installd/utils.c +++ b/cmds/installd/utils.c @@ -33,6 +33,7 @@ int create_pkg_path(char path[PKG_PATH_MAX], } x = pkgname; + int alpha = -1; while (*x) { if (isalnum(*x) || (*x == '_')) { /* alphanumeric or underscore are fine */ @@ -42,13 +43,28 @@ int create_pkg_path(char path[PKG_PATH_MAX], LOGE("invalid package name '%s'\n", pkgname); return -1; } - } else { + } else if (*x == '-') { + /* Suffix -X is fine to let versioning of packages. + But whatever follows should be alphanumeric.*/ + alpha = 1; + }else { /* anything not A-Z, a-z, 0-9, _, or . is invalid */ LOGE("invalid package name '%s'\n", pkgname); return -1; } x++; } + if (alpha == 1) { + // Skip current character + x++; + while (*x) { + if (!isalnum(*x)) { + LOGE("invalid package name '%s' should include only numbers after -\n", pkgname); + return -1; + } + x++; + } + } sprintf(path, "%s%s%s", prefix, pkgname, postfix); return 0; diff --git a/core/java/com/android/internal/app/IMediaContainerService.aidl b/core/java/com/android/internal/app/IMediaContainerService.aidl new file mode 100755 index 0000000000000..726e28f51551a --- /dev/null +++ b/core/java/com/android/internal/app/IMediaContainerService.aidl @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2008 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.internal.app; + +import android.net.Uri; +import android.os.ParcelFileDescriptor; + +interface IMediaContainerService { + String copyResourceToContainer(in Uri packageURI, + String containerId, + String key, String resFileName); + boolean copyResource(in Uri packageURI, + in ParcelFileDescriptor outStream); +} \ No newline at end of file diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ac6467d33a51c..54e15a598d54e 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1202,6 +1202,16 @@ android:description="@string/permdesc_cache_filesystem" android:protectionLevel="signatureOrSystem" /> + + + Allows the modification of collected component usage statistics. Not for use by normal applications. + + + Allows to invoke default container service to copy content. Not for use by normal applications. + + Allows to invoke default container service to copy content. Not for use by normal applications. + Tap twice for zoom control diff --git a/packages/DefaultContainerService/Android.mk b/packages/DefaultContainerService/Android.mk new file mode 100755 index 0000000000000..2f1d6abd8fa21 --- /dev/null +++ b/packages/DefaultContainerService/Android.mk @@ -0,0 +1,12 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_PACKAGE_NAME := DefaultContainerService + +LOCAL_CERTIFICATE := platform + +include $(BUILD_PACKAGE) diff --git a/packages/DefaultContainerService/AndroidManifest.xml b/packages/DefaultContainerService/AndroidManifest.xml new file mode 100755 index 0000000000000..fd77148a24edf --- /dev/null +++ b/packages/DefaultContainerService/AndroidManifest.xml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/packages/DefaultContainerService/res/values/strings.xml b/packages/DefaultContainerService/res/values/strings.xml new file mode 100644 index 0000000000000..2897f34d5845c --- /dev/null +++ b/packages/DefaultContainerService/res/values/strings.xml @@ -0,0 +1,23 @@ + + + + + Media Container Service + diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java new file mode 100644 index 0000000000000..d36207bfdc313 --- /dev/null +++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java @@ -0,0 +1,305 @@ +package com.android.defcontainer; + +import com.android.internal.app.IMediaContainerService; + +import android.content.Intent; +import android.net.Uri; +import android.os.Debug; +import android.os.IBinder; +import android.os.IMountService; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.app.Service; +import android.util.Log; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import android.os.FileUtils; + + +/* + * This service copies a downloaded apk to a file passed in as + * a ParcelFileDescriptor or to a newly created container specified + * by parameters. The DownloadManager gives access to this process + * based on its uid. This process also needs the ACCESS_DOWNLOAD_MANAGER + * permission to access apks downloaded via the download manager. + */ +public class DefaultContainerService extends Service { + private static final String TAG = "DefContainer"; + private static final boolean localLOGV = false; + + private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() { + /* + * Creates a new container and copies resource there. + * @param paackageURI the uri of resource to be copied. Can be either + * a content uri or a file uri + * @param containerId the id of the secure container that should + * be used for creating a secure container into which the resource + * will be copied. + * @param key Refers to key used for encrypting the secure container + * @param resFileName Name of the target resource file(relative to newly + * created secure container) + * @return Returns the new cache path where the resource has been copied into + * + */ + public String copyResourceToContainer(final Uri packageURI, + final String containerId, + final String key, final String resFileName) { + if (packageURI == null || containerId == null) { + return null; + } + return copyResourceInner(packageURI, containerId, key, resFileName); + } + + /* + * Copy specified resource to output stream + * @param packageURI the uri of resource to be copied. Should be a + * file uri + * @param outStream Remote file descriptor to be used for copying + * @return Returns true if copy succeded or false otherwise. + */ + public boolean copyResource(final Uri packageURI, + ParcelFileDescriptor outStream) { + if (packageURI == null || outStream == null) { + return false; + } + ParcelFileDescriptor.AutoCloseOutputStream + autoOut = new ParcelFileDescriptor.AutoCloseOutputStream(outStream); + return copyFile(packageURI, autoOut); + } + }; + + public IBinder onBind(Intent intent) { + return mBinder; + } + + private IMountService getMountService() { + return IMountService.Stub.asInterface(ServiceManager.getService("mount")); + } + + private String copyResourceInner(Uri packageURI, String newCacheId, String key, String resFileName) { + // Create new container at newCachePath + String codePath = packageURI.getPath(); + String newCachePath = null; + final int CREATE_FAILED = 1; + final int COPY_FAILED = 2; + final int FINALIZE_FAILED = 3; + final int PASS = 4; + int errCode = CREATE_FAILED; + // Create new container + if ((newCachePath = createSdDir(packageURI, newCacheId, key)) != null) { + File resFile = new File(newCachePath, resFileName); + errCode = COPY_FAILED; + if (localLOGV) Log.i(TAG, "Trying to copy " + codePath + " to " + resFile); + // Copy file from codePath + if (FileUtils.copyFile(new File(codePath), resFile)) { + errCode = FINALIZE_FAILED; + if (finalizeSdDir(newCacheId)) { + errCode = PASS; + } + } + } + // Print error based on errCode + String errMsg = ""; + switch (errCode) { + case CREATE_FAILED: + errMsg = "CREATE_FAILED"; + break; + case COPY_FAILED: + errMsg = "COPY_FAILED"; + destroySdDir(newCacheId); + break; + case FINALIZE_FAILED: + errMsg = "FINALIZE_FAILED"; + destroySdDir(newCacheId); + break; + default: + errMsg = "PASS"; + unMountSdDir(newCacheId); + break; + } + Log.i(TAG, "Status: " + errMsg); + if (errCode != PASS) { + return null; + } + return newCachePath; + } + + private String createSdDir(final Uri packageURI, + String containerId, String sdEncKey) { + File tmpPackageFile = new File(packageURI.getPath()); + // Create mount point via MountService + IMountService mountService = getMountService(); + long len = tmpPackageFile.length(); + int mbLen = (int) (len/(1024*1024)); + if ((len - (mbLen * 1024 * 1024)) > 0) { + mbLen++; + } + if (localLOGV) Log.i(TAG, "mbLen="+mbLen); + String cachePath = null; + int ownerUid = Process.myUid(); + try { + cachePath = mountService.createSecureContainer(containerId, + mbLen, + "vfat", sdEncKey, ownerUid); + if (localLOGV) Log.i(TAG, "Trying to create secure container for " + + containerId + ", cachePath =" + cachePath); + return cachePath; + } catch(IllegalStateException e) { + Log.e(TAG, "Failed to create storage on sdcard with exception: " + e); + } catch(RemoteException e) { + Log.e(TAG, "MounteService not running?"); + return null; + } + // TODO just fail here and let the user delete later on. + try { + mountService.destroySecureContainer(containerId); + if (localLOGV) Log.i(TAG, "Destroying cache for " + containerId + + ", cachePath =" + cachePath); + } catch(IllegalStateException e) { + Log.e(TAG, "Failed to destroy existing cache: " + e); + return null; + } catch(RemoteException e) { + Log.e(TAG, "MounteService not running?"); + return null; + } + try { + cachePath = mountService.createSecureContainer(containerId, + mbLen, + "vfat", sdEncKey, ownerUid); + if (localLOGV) Log.i(TAG, "Trying to install again " + containerId + + ", cachePath =" + cachePath); + return cachePath; + } catch(IllegalStateException e) { + Log.e(TAG, "Failed to create storage on sdcard with exception: " + e); + } catch(RemoteException e) { + Log.e(TAG, "MounteService not running?"); + } + return null; + } + + private boolean destroySdDir(String containerId) { + try { + // We need to destroy right away + getMountService().destroySecureContainer(containerId); + return true; + } catch (IllegalStateException e) { + Log.i(TAG, "Failed to destroy container : " + containerId); + } catch(RemoteException e) { + Log.e(TAG, "MounteService not running?"); + } + return false; + } + + private boolean finalizeSdDir(String containerId){ + try { + getMountService().finalizeSecureContainer(containerId); + return true; + } catch (IllegalStateException e) { + Log.i(TAG, "Failed to finalize container for pkg : " + containerId); + } catch(RemoteException e) { + Log.e(TAG, "MounteService not running?"); + } + return false; + } + + private boolean unMountSdDir(String containerId) { + try { + getMountService().unmountSecureContainer(containerId); + return true; + } catch (IllegalStateException e) { + Log.e(TAG, "Failed to unmount id: " + containerId + " with exception " + e); + } catch(RemoteException e) { + Log.e(TAG, "MounteService not running?"); + } + return false; + } + + private String mountSdDir(String containerId, String key) { + try { + return getMountService().mountSecureContainer(containerId, key, Process.myUid()); + } catch (IllegalStateException e) { + Log.e(TAG, "Failed to mount id: " + + containerId + " with exception " + e); + } catch(RemoteException e) { + Log.e(TAG, "MounteService not running?"); + } + return null; + } + + public static boolean copyToFile(InputStream inputStream, FileOutputStream out) { + try { + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) >= 0) { + out.write(buffer, 0, bytesRead); + } + return true; + } catch (IOException e) { + Log.i(TAG, "Exception : " + e + " when copying file"); + return false; + } + } + + public static boolean copyToFile(File srcFile, FileOutputStream out) { + InputStream inputStream = null; + try { + inputStream = new FileInputStream(srcFile); + return copyToFile(inputStream, out); + } catch (IOException e) { + return false; + } finally { + try { if (inputStream != null) inputStream.close(); } catch (IOException e) {} + } + } + + private boolean copyFile(Uri pPackageURI, FileOutputStream outStream) { + if (pPackageURI.getScheme().equals("file")) { + final File srcPackageFile = new File(pPackageURI.getPath()); + // We copy the source package file to a temp file and then rename it to the + // destination file in order to eliminate a window where the package directory + // scanner notices the new package file but it's not completely copied yet. + if (!copyToFile(srcPackageFile, outStream)) { + Log.e(TAG, "Couldn't copy file: " + srcPackageFile); + return false; + } + } else if (pPackageURI.getScheme().equals("content")) { + ParcelFileDescriptor fd = null; + try { + fd = getContentResolver().openFileDescriptor(pPackageURI, "r"); + } catch (FileNotFoundException e) { + Log.e(TAG, "Couldn't open file descriptor from download service. Failed with exception " + e); + return false; + } + if (fd == null) { + Log.e(TAG, "Couldn't open file descriptor from download service (null)."); + return false; + } else { + if (localLOGV) { + Log.v(TAG, "Opened file descriptor from download service."); + } + ParcelFileDescriptor.AutoCloseInputStream + dlStream = new ParcelFileDescriptor.AutoCloseInputStream(fd); + // We copy the source package file to a temp file and then rename it to the + // destination file in order to eliminate a window where the package directory + // scanner notices the new package file but it's not completely copied yet. + if (!copyToFile(dlStream, outStream)) { + Log.e(TAG, "Couldn't copy " + pPackageURI + " to temp file."); + return false; + } + } + } else { + Log.e(TAG, "Package URI is not 'file:' or 'content:' - " + pPackageURI); + return false; + } + return true; + } +} diff --git a/services/java/com/android/server/Installer.java b/services/java/com/android/server/Installer.java index 6a7d4328bf76b..88b1f02aeabbf 100644 --- a/services/java/com/android/server/Installer.java +++ b/services/java/com/android/server/Installer.java @@ -263,10 +263,15 @@ class Installer { return execute(builder.toString()); } - public int setForwardLockPerm(String packageName, int gid) { + /* + * @param packagePathSuffix The name of the path relative to install + * directory. Say if the path name is /data/app/com.test-1.apk, + * the package suffix path will be com.test-1 + */ + public int setForwardLockPerm(String packagePathSuffix, int gid) { StringBuilder builder = new StringBuilder("protect"); builder.append(' '); - builder.append(packageName); + builder.append(packagePathSuffix); builder.append(' '); builder.append(gid); return execute(builder.toString()); diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java index 13079728d5043..1cf3badb25559 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -16,6 +16,7 @@ package com.android.server; +import com.android.internal.app.IMediaContainerService; import com.android.internal.app.ResolverActivity; import com.android.common.FastXmlSerializer; import com.android.common.XmlUtils; @@ -31,6 +32,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; +import android.content.ServiceConnection; import android.content.IntentSender.SendIntentException; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; @@ -62,6 +64,7 @@ import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.HandlerThread; +import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.Parcel; @@ -283,26 +286,79 @@ class PackageManagerService extends IPackageManager.Stub { final HashMap> mPendingBroadcasts = new HashMap>(); static final int SEND_PENDING_BROADCAST = 1; - static final int DESTROY_SD_CONTAINER = 2; + static final int MCS_BOUND = 3; + static final int END_COPY = 4; + static final int INIT_COPY = 5; + static final int MCS_UNBIND = 6; // Delay time in millisecs static final int BROADCAST_DELAY = 10 * 1000; - static final int DESTROY_SD_CONTAINER_DELAY = 30 * 1000; + private ServiceConnection mDefContainerConn = new ServiceConnection() { + public void onServiceConnected(ComponentName name, IBinder service) { + IMediaContainerService imcs = + IMediaContainerService.Stub.asInterface(service); + Message msg = mHandler.obtainMessage(MCS_BOUND, imcs); + mHandler.sendMessage(msg); + } + + public void onServiceDisconnected(ComponentName name) { + } + }; class PackageHandler extends Handler { + final ArrayList mPendingInstalls = + new ArrayList(); + // Service Connection to remote media container service to copy + // package uri's from external media onto secure containers + // or internal storage. + private IMediaContainerService mContainerService = null; + PackageHandler(Looper looper) { super(looper); } public void handleMessage(Message msg) { switch (msg.what) { - case DESTROY_SD_CONTAINER: - String pkgName = (String) msg.obj; - if (pkgName != null) { - // Too bad we cannot handle the errors from destroying the containers. - if (!destroySdDir(pkgName)) { - Log.e(TAG, "Failed to destroy container for pkg : " + pkgName); + case INIT_COPY: { + InstallArgs args = (InstallArgs) msg.obj; + args.createCopyFile(); + Intent service = new Intent().setComponent(new ComponentName( + "com.android.defcontainer", + "com.android.defcontainer.DefaultContainerService")); + if (mContainerService != null) { + // No need to add to pending list. Use remote stub directly + handleStartCopy(args); + } else { + if (mContext.bindService(service, mDefContainerConn, + Context.BIND_AUTO_CREATE)) { + mPendingInstalls.add(args); + } else { + Log.e(TAG, "Failed to bind to media container service"); + // Indicate install failure TODO add new error code + processPendingInstall(args, + PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE); } } break; + } + case MCS_BOUND: { + // Initialize mContainerService if needed. + if (msg.obj != null) { + mContainerService = (IMediaContainerService) msg.obj; + } + if (mPendingInstalls.size() > 0) { + InstallArgs args = mPendingInstalls.remove(0); + if (args != null) { + handleStartCopy(args); + } + } + break; + } + case MCS_UNBIND : { + if (mPendingInstalls.size() == 0) { + mContext.unbindService(mDefContainerConn); + mContainerService = null; + } + break; + } case SEND_PENDING_BROADCAST : { String packages[]; ArrayList components[]; @@ -343,7 +399,37 @@ class PackageManagerService extends IPackageManager.Stub { } } } + + // Utility method to initiate copying apk via media + // container service. + private void handleStartCopy(InstallArgs args) { + int ret = PackageManager.INSTALL_SUCCEEDED; + if (mContainerService == null) { + // Install error + ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; + } else { + ret = args.copyApk(mContainerService); + } + mHandler.sendEmptyMessage(MCS_UNBIND); + processPendingInstall(args, ret); + } } + + static boolean installOnSd(int flags) { + if (((flags & PackageManager.INSTALL_FORWARD_LOCK) != 0) || + ((flags & PackageManager.INSTALL_ON_SDCARD) == 0)) { + return false; + } + return true; + } + + static boolean isFwdLocked(int flags) { + if ((flags & PackageManager.INSTALL_FORWARD_LOCK) != 0) { + return true; + } + return false; + } + public static final IPackageManager main(Context context, boolean factoryTest) { PackageManagerService m = new PackageManagerService(context, factoryTest); ServiceManager.addService("package", m); @@ -2022,11 +2108,12 @@ class PackageManagerService extends IPackageManager.Stub { int parseFlags, int scanMode) { mLastScanError = PackageManager.INSTALL_SUCCEEDED; + String scanPath = scanFile.getPath(); parseFlags |= mDefParseFlags; - PackageParser pp = new PackageParser(scanFile.getPath()); + PackageParser pp = new PackageParser(scanPath); pp.setSeparateProcesses(mSeparateProcesses); final PackageParser.Package pkg = pp.parsePackage(scanFile, - scanFile.getPath(), + scanPath, mMetrics, parseFlags); if (pkg == null) { mLastScanError = pp.getParseError(); @@ -2066,7 +2153,8 @@ class PackageManagerService extends IPackageManager.Stub { // Just remove the loaded entries from package lists. mPackages.remove(ps.name); } - deletePackageResourcesLI(ps.name, ps.codePathString, ps.resourcePathString); + InstallArgs args = new FileInstallArgs(ps.codePathString, ps.resourcePathString); + args.cleanUpResourcesLI(); mSettings.enableSystemPackageLP(ps.name); } } @@ -2077,6 +2165,18 @@ class PackageManagerService extends IPackageManager.Stub { if (ps != null && !ps.codePath.equals(ps.resourcePath)) { parseFlags |= PackageParser.PARSE_FORWARD_LOCK; } + + if ((parseFlags & PackageParser.PARSE_FORWARD_LOCK) != 0) { + if (ps != null && ps.resourcePathString != null) { + pkg.applicationInfo.publicSourceDir = ps.resourcePathString; + } else { + // Should not happen at all. Just log an error. + Log.e(TAG, "Resource path not set for pkg : " + pkg.packageName); + } + } else { + pkg.applicationInfo.publicSourceDir = pkg.mScanPath; + } + pkg.applicationInfo.sourceDir = pkg.mScanPath; // Note that we invoke the following method only if we are about to unpack an application return scanPackageLI(pkg, parseFlags, scanMode | SCAN_UPDATE_SIGNATURE); } @@ -2230,41 +2330,8 @@ class PackageManagerService extends IPackageManager.Stub { } // Initialize package source and resource directories - File destResourceFile = null; - File destCodeFile = null; - if ((scanMode & SCAN_NO_PATHS) == 0) { - boolean fwdLocked = (parseFlags & PackageParser.PARSE_FORWARD_LOCK) != 0; - final String pkgFileName = pkgName + ".apk"; - File destDir = null; - - if (fwdLocked) { - destDir = mDrmAppPrivateInstallDir; - destResourceFile = new File(mAppInstallDir, pkgName + ".zip"); - } else { - boolean onSd = (parseFlags & PackageParser.PARSE_ON_SDCARD) != 0; - if (!onSd) { - destDir = mAppInstallDir; - } else { - String cachePath = getSdDir(pkgName); - if (cachePath == null) { - Log.e(TAG, "Secure container path for pkg: " + pkgName + " at location: " + cachePath + - " not found"); - mLastScanError = PackageManager.INSTALL_FAILED_CONTAINER_ERROR; - return null; - } - destDir = new File(cachePath); - } - destResourceFile = new File(destDir, pkgFileName); - } - destCodeFile = new File(destDir, pkgFileName); - pkg.mPath = destCodeFile.getAbsolutePath(); - } else { - pkg.mPath = pkg.mScanPath; - destCodeFile = new File(pkg.mScanPath); - destResourceFile = new File(pkg.mScanPath); - } - pkg.applicationInfo.sourceDir = destCodeFile.getAbsolutePath(); - pkg.applicationInfo.publicSourceDir = destResourceFile.getAbsolutePath(); + File destCodeFile = new File(pkg.applicationInfo.sourceDir); + File destResourceFile = new File(pkg.applicationInfo.publicSourceDir); SharedUserSetting suid = null; PackageSetting pkgSetting = null; @@ -2568,21 +2635,17 @@ class PackageManagerService extends IPackageManager.Stub { // We don't expect installation to fail beyond this point, if ((scanMode&SCAN_MONITOR) != 0) { - pkg.mPath = destCodeFile.getAbsolutePath(); mAppDirs.put(pkg.mPath, pkg); } // Request the ActivityManager to kill the process(only for existing packages) // so that we do not end up in a confused state while the user is still using the older // version of the application while the new one gets installed. - IActivityManager am = ActivityManagerNative.getDefault(); - if ((am != null) && ((parseFlags & PackageManager.INSTALL_REPLACE_EXISTING ) != 0)) { - try { - am.killApplicationWithUid(pkg.applicationInfo.packageName, + if ((parseFlags & PackageManager.INSTALL_REPLACE_EXISTING ) != 0) { + killApplication(pkg.applicationInfo.packageName, pkg.applicationInfo.uid); - } catch (RemoteException e) { - } } + synchronized (mPackages) { // Add the new setting to mSettings mSettings.insertPackageSettingLP(pkgSetting, pkg); @@ -2835,6 +2898,19 @@ class PackageManagerService extends IPackageManager.Stub { return pkg; } + private void killApplication(String pkgName, int uid) { + // Request the ActivityManager to kill the process(only for existing packages) + // so that we do not end up in a confused state while the user is still using the older + // version of the application while the new one gets installed. + IActivityManager am = ActivityManagerNative.getDefault(); + if (am != null) { + try { + am.killApplicationWithUid(pkgName, uid); + } catch (RemoteException e) { + } + } + } + // The following constants are returned by cachePackageSharedLibsForAbiLI // to indicate if native shared libraries were found in the package. // Values are: @@ -3866,26 +3942,51 @@ class PackageManagerService extends IPackageManager.Stub { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.INSTALL_PACKAGES, null); + Message msg = mHandler.obtainMessage(INIT_COPY); + msg.obj = createInstallArgs(packageURI, observer, flags, installerPackageName); + mHandler.sendMessage(msg); + } + + private InstallArgs createInstallArgs(Uri packageURI, IPackageInstallObserver observer, + int flags, String installerPackageName) { + if (installOnSd(flags)) { + return new SdInstallArgs(packageURI, observer, flags, + installerPackageName); + } else { + return new FileInstallArgs(packageURI, observer, flags, + installerPackageName); + } + } + + private InstallArgs createInstallArgs(int flags, String fullCodePath, String fullResourcePath) { + if (installOnSd(flags)) { + return new SdInstallArgs(fullCodePath, fullResourcePath); + } else { + return new FileInstallArgs(fullCodePath, fullResourcePath); + } + } + + private void processPendingInstall(final InstallArgs args, final int currentStatus) { // Queue up an async operation since the package installation may take a little while. mHandler.post(new Runnable() { public void run() { mHandler.removeCallbacks(this); // Result object to be returned PackageInstalledInfo res = new PackageInstalledInfo(); - res.returnCode = PackageManager.INSTALL_SUCCEEDED; + res.returnCode = currentStatus; res.uid = -1; res.pkg = null; res.removedInfo = new PackageRemovedInfo(); - // Make a temporary copy of file from given packageURI - File tmpPackageFile = copyTempInstallFile(packageURI, res); - if (tmpPackageFile != null) { + args.doPreInstall(res.returnCode); + if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) { synchronized (mInstallLock) { - installPackageLI(packageURI, flags, true, installerPackageName, tmpPackageFile, res); + installPackageLI(args, true, res); } + args.doPostInstall(res.returnCode); } - if (observer != null) { + if (args.observer != null) { try { - observer.packageInstalled(res.name, res.returnCode); + args.observer.packageInstalled(res.name, res.returnCode); } catch (RemoteException e) { Log.i(TAG, "Observer no longer exists."); } @@ -3908,12 +4009,403 @@ class PackageManagerService extends IPackageManager.Stub { res.pkg.applicationInfo.packageName, extras); } + if (res.removedInfo.args != null) { + // Remove the replaced package's older resources safely now + synchronized (mInstallLock) { + res.removedInfo.args.cleanUpResourcesLI(); + } + } } Runtime.getRuntime().gc(); } }); } + static abstract class InstallArgs { + final IPackageInstallObserver observer; + final int flags; + final Uri packageURI; + final String installerPackageName; + + InstallArgs(Uri packageURI, + IPackageInstallObserver observer, int flags, + String installerPackageName) { + this.packageURI = packageURI; + this.flags = flags; + this.observer = observer; + this.installerPackageName = installerPackageName; + } + + abstract void createCopyFile(); + abstract int copyApk(IMediaContainerService imcs); + abstract void doPreInstall(int status); + abstract boolean doRename(int status, String pkgName, String oldCodePath); + abstract void doPostInstall(int status); + abstract String getCodePath(); + abstract String getResourcePath(); + // Need installer lock especially for dex file removal. + abstract void cleanUpResourcesLI(); + } + + class FileInstallArgs extends InstallArgs { + File installDir; + String codeFileName; + String resourceFileName; + + FileInstallArgs(Uri packageURI, + IPackageInstallObserver observer, int flags, + String installerPackageName) { + super(packageURI, observer, flags, installerPackageName); + } + + FileInstallArgs(String fullCodePath, String fullResourcePath) { + super(null, null, 0, null); + File codeFile = new File(fullCodePath); + installDir = codeFile.getParentFile(); + codeFileName = fullCodePath; + resourceFileName = fullResourcePath; + } + + void createCopyFile() { + boolean fwdLocked = isFwdLocked(flags); + installDir = fwdLocked ? mDrmAppPrivateInstallDir : mAppInstallDir; + codeFileName = createTempPackageFile(installDir).getPath(); + resourceFileName = getResourcePathFromCodePath(); + } + + String getCodePath() { + return codeFileName; + } + + int copyApk(IMediaContainerService imcs) { + // Get a ParcelFileDescriptor to write to the output file + File codeFile = new File(codeFileName); + ParcelFileDescriptor out = null; + try { + out = ParcelFileDescriptor.open(codeFile, + ParcelFileDescriptor.MODE_READ_WRITE); + } catch (FileNotFoundException e) { + Log.e(TAG, "Failed to create file descritpor for : " + codeFileName); + return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; + } + // Copy the resource now + int ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; + try { + if (imcs.copyResource(packageURI, out)) { + ret = PackageManager.INSTALL_SUCCEEDED; + } + } catch (RemoteException e) { + } finally { + try { if (out != null) out.close(); } catch (IOException e) {} + } + return ret; + } + + void doPreInstall(int status) { + if (status != PackageManager.INSTALL_SUCCEEDED) { + cleanUp(); + } + } + + boolean doRename(int status, final String pkgName, String oldCodePath) { + if (status != PackageManager.INSTALL_SUCCEEDED) { + cleanUp(); + return false; + } else { + // Rename based on packageName + File codeFile = new File(getCodePath()); + String apkName = getNextCodePath(oldCodePath, pkgName, ".apk"); + File desFile = new File(installDir, apkName + ".apk"); + if (!codeFile.renameTo(desFile)) { + return false; + } + // Reset paths since the file has been renamed. + codeFileName = desFile.getPath(); + resourceFileName = getResourcePathFromCodePath(); + // Set permissions + if (!setPermissions(pkgName)) { + // Failed setting permissions. + return false; + } + return true; + } + } + + void doPostInstall(int status) { + if (status != PackageManager.INSTALL_SUCCEEDED) { + cleanUp(); + } + } + + String getResourcePath() { + return resourceFileName; + } + + String getResourcePathFromCodePath() { + String codePath = getCodePath(); + if ((flags & PackageManager.INSTALL_FORWARD_LOCK) != 0) { + String apkNameOnly = getApkName(codePath); + return mAppInstallDir.getPath() + "/" + apkNameOnly + ".zip"; + } else { + return codePath; + } + } + + private boolean cleanUp() { + boolean ret = true; + String sourceDir = getCodePath(); + String publicSourceDir = getResourcePath(); + if (sourceDir != null) { + File sourceFile = new File(sourceDir); + if (!sourceFile.exists()) { + Log.w(TAG, "Package source " + sourceDir + " does not exist."); + ret = false; + } + // Delete application's code and resources + sourceFile.delete(); + } + if (publicSourceDir != null && !publicSourceDir.equals(sourceDir)) { + final File publicSourceFile = new File(publicSourceDir); + if (!publicSourceFile.exists()) { + Log.w(TAG, "Package public source " + publicSourceFile + " does not exist."); + } + if (publicSourceFile.exists()) { + publicSourceFile.delete(); + } + } + return ret; + } + + void cleanUpResourcesLI() { + String sourceDir = getCodePath(); + if (cleanUp() && mInstaller != null) { + int retCode = mInstaller.rmdex(sourceDir); + if (retCode < 0) { + Log.w(TAG, "Couldn't remove dex file for package: " + + " at location " + + sourceDir + ", retcode=" + retCode); + // we don't consider this to be a failure of the core package deletion + } + } + } + + private boolean setPermissions(String pkgName) { + // TODO Do this in a more elegant way later on. for now just a hack + if (!isFwdLocked(flags)) { + final int filePermissions = + FileUtils.S_IRUSR|FileUtils.S_IWUSR|FileUtils.S_IRGRP + |FileUtils.S_IROTH; + int retCode = FileUtils.setPermissions(getCodePath(), filePermissions, -1, -1); + if (retCode != 0) { + Log.e(TAG, "Couldn't set new package file permissions for " + + getCodePath() + + ". The return code was: " + retCode); + // TODO Define new internal error + return false; + } + return true; + } + return true; + } + } + + class SdInstallArgs extends InstallArgs { + String cid; + String cachePath; + static final String RES_FILE_NAME = "pkg.apk"; + + SdInstallArgs(Uri packageURI, + IPackageInstallObserver observer, int flags, + String installerPackageName) { + super(packageURI, observer, flags, installerPackageName); + } + + SdInstallArgs(String fullCodePath, String fullResourcePath) { + super(null, null, 0, null); + // Extract cid from fullCodePath + int eidx = fullCodePath.lastIndexOf("/"); + String subStr1 = fullCodePath.substring(0, eidx); + int sidx = subStr1.lastIndexOf("/"); + cid = subStr1.substring(sidx+1, eidx); + cachePath = subStr1; + } + + void createCopyFile() { + cid = getTempContainerId(); + } + + int copyApk(IMediaContainerService imcs) { + try { + cachePath = imcs.copyResourceToContainer( + packageURI, cid, + getEncryptKey(), RES_FILE_NAME); + } catch (RemoteException e) { + } + + if (cachePath != null) { + // Mount container once its created with system_uid + cachePath = mountSdDir(cid, Process.SYSTEM_UID); + } + if (cachePath == null) { + return PackageManager.INSTALL_FAILED_CONTAINER_ERROR; + } else { + return PackageManager.INSTALL_SUCCEEDED; + } + } + + @Override + String getCodePath() { + return cachePath + "/" + RES_FILE_NAME; + } + + @Override + String getResourcePath() { + return cachePath + "/" + RES_FILE_NAME; + } + + void doPreInstall(int status) { + if (status != PackageManager.INSTALL_SUCCEEDED) { + // Destroy container + destroySdDir(cid); + } + } + + boolean doRename(int status, final String pkgName, + String oldCodePath) { + String newCacheId = getNextCodePath(oldCodePath, pkgName, "/" + RES_FILE_NAME); + // STOPSHIP TEMPORARY HACK FOR RENAME + // Create new container at newCachePath + String codePath = getCodePath(); + String newCachePath = null; + final int CREATE_FAILED = 1; + final int COPY_FAILED = 3; + final int FINALIZE_FAILED = 5; + final int PASS = 7; + int errCode = CREATE_FAILED; + if ((newCachePath = createSdDir(new File(codePath), newCacheId)) != null) { + errCode = COPY_FAILED; + // Copy file from codePath + if (FileUtils.copyFile(new File(codePath), new File(newCachePath, RES_FILE_NAME))) { + errCode = FINALIZE_FAILED; + if (finalizeSdDir(newCacheId)) { + errCode = PASS; + } + } + } + // Print error based on errCode + String errMsg = ""; + switch (errCode) { + case CREATE_FAILED: + errMsg = "CREATE_FAILED"; + break; + case COPY_FAILED: + errMsg = "COPY_FAILED"; + destroySdDir(newCacheId); + break; + case FINALIZE_FAILED: + errMsg = "FINALIZE_FAILED"; + destroySdDir(newCacheId); + break; + default: + errMsg = "PASS"; + break; + } + // Destroy the temporary container + destroySdDir(cid); + Log.i(TAG, "Status: " + errMsg); + if (errCode != PASS) { + return false; + } + cid = newCacheId; + cachePath = newCachePath; + + return true; + } + + void doPostInstall(int status) { + if (status != PackageManager.INSTALL_SUCCEEDED) { + cleanUp(); + } else { + // Unmount container + // Rename and remount based on package name and new uid + } + } + + private void cleanUp() { + // Destroy secure container + destroySdDir(cid); + } + + void cleanUpResourcesLI() { + String sourceFile = getCodePath(); + // Remove dex file + if (mInstaller != null) { + int retCode = mInstaller.rmdex(sourceFile.toString()); + if (retCode < 0) { + Log.w(TAG, "Couldn't remove dex file for package: " + + " at location " + + sourceFile.toString() + ", retcode=" + retCode); + // we don't consider this to be a failure of the core package deletion + } + } + cleanUp(); + } + }; + + // Utility method used to create code paths based on package name and available index. + private static String getNextCodePath(String oldCodePath, String prefix, String suffix) { + String idxStr = ""; + int idx = 1; + if (oldCodePath != null) { + int eidx = -1; + if (suffix != null) { + eidx = oldCodePath.indexOf(suffix); + } + if (eidx == -1) { + eidx = oldCodePath.length(); + } + int sidx = oldCodePath.indexOf(prefix); + if (sidx == -1) { + sidx = 0; + } + String subStr = oldCodePath.substring(sidx + prefix.length(), eidx); + if (subStr != null) { + if (subStr.startsWith("-")) { + subStr = subStr.substring(1); + } + try { + idx = Integer.parseInt(subStr); + if (idx <= 1) { + idx++; + } else { + idx--; + } + } catch(NumberFormatException e) { + } + } + } + idxStr = "-" + Integer.toString(idx); + return prefix + idxStr; + } + + // Utility method that returns the relative package path with respect + // to the installation directory. Like say for /data/data/com.test-1.apk + // string com.test-1 is returned. + static String getApkName(String codePath) { + if (codePath == null) { + return null; + } + int sidx = codePath.lastIndexOf("/"); + int eidx = codePath.lastIndexOf("."); + if (eidx == -1) { + eidx = codePath.length(); + } else if (eidx == 0) { + Log.w(TAG, " Invalid code path, "+ codePath + " Not a valid apk name"); + return null; + } + return codePath.substring(sidx+1, eidx); + } + class PackageInstalledInfo { String name; int uid; @@ -3932,7 +4424,6 @@ class PackageManagerService extends IPackageManager.Stub { // Remember this for later, in case we need to rollback this install boolean dataDirExists; String pkgName = pkg.packageName; - boolean onSd = (parseFlags & PackageParser.PARSE_ON_SDCARD) != 0; if (useEncryptedFilesystemForPackage(pkg)) { dataDirExists = (new File(mSecureAppDataDir, pkgName)).exists(); @@ -3950,14 +4441,6 @@ class PackageManagerService extends IPackageManager.Stub { } } mLastScanError = PackageManager.INSTALL_SUCCEEDED; - if (onSd) { - // Create secure container mount point for package - String cPath = createSdDir(new File(pkg.mScanPath), pkgName); - if (cPath == null) { - mLastScanError = res.returnCode = PackageManager.INSTALL_FAILED_CONTAINER_ERROR; - return; - } - } PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanMode); if (newPackage == null) { Log.w(TAG, "Package couldn't be installed in " + pkg.mPath); @@ -3965,21 +4448,9 @@ class PackageManagerService extends IPackageManager.Stub { res.returnCode = PackageManager.INSTALL_FAILED_INVALID_APK; } } else { - File destPackageFile = new File(pkg.mPath); - if (destPackageFile.exists()) { - // It's safe to do this because we know (from the above check) that the file - // isn't currently used for an installed package. - destPackageFile.delete(); - } updateSettingsLI(newPackage, installerPackageName, res); - if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) { - // Check if container can be finalized - if(onSd && !finalizeSdDir(pkgName)) { - res.returnCode = PackageManager.INSTALL_FAILED_CONTAINER_ERROR; - } - } // delete the partially installed application. the data directory will have to be // restored if it was already existing if (res.returnCode != PackageManager.INSTALL_SUCCEEDED) { @@ -3988,15 +4459,11 @@ class PackageManagerService extends IPackageManager.Stub { // scanPackageLocked, unless those directories existed before we even tried to // install. deletePackageLI( - pkgName, true, + pkgName, false, dataDirExists ? PackageManager.DONT_DELETE_DATA : 0, res.removedInfo); } } - if (onSd && res.returnCode != PackageManager.INSTALL_SUCCEEDED) { - // Destroy cache - destroySdDir(pkgName); - } } private void replacePackageLI(PackageParser.Package pkg, @@ -4039,7 +4506,7 @@ class PackageManagerService extends IPackageManager.Stub { parseFlags |= PackageManager.INSTALL_REPLACE_EXISTING; // First delete the existing package while retaining the data directory - if (!deletePackageLI(pkgName, false, PackageManager.DONT_DELETE_DATA, + if (!deletePackageLI(pkgName, true, PackageManager.DONT_DELETE_DATA, res.removedInfo)) { // If the existing package was'nt successfully deleted res.returnCode = PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE; @@ -4063,17 +4530,10 @@ class PackageManagerService extends IPackageManager.Stub { if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) { // If we deleted an exisiting package, the old source and resource files that we - // were keeping around in case we needed them (see below) can now be deleted - final ApplicationInfo deletedPackageAppInfo = deletedPackage.applicationInfo; - final ApplicationInfo installedPackageAppInfo = - newPackage.applicationInfo; - deletePackageResourcesLI(pkgName, - !deletedPackageAppInfo.sourceDir - .equals(installedPackageAppInfo.sourceDir) - ? deletedPackageAppInfo.sourceDir : null, - !deletedPackageAppInfo.publicSourceDir - .equals(installedPackageAppInfo.publicSourceDir) - ? deletedPackageAppInfo.publicSourceDir : null); + // were keeping around in case we needed them (see below) can now be deleted. + // This info will be set on the res.removedInfo to clean up later on as post + // install action. + //update signature on the new package setting //this should always succeed, since we checked the //signature earlier. @@ -4100,22 +4560,15 @@ class PackageManagerService extends IPackageManager.Stub { Log.e(TAG, "Failed allocating storage when restoring pkg : " + pkgName); return; } - File restoreTmpFile = createTempPackageFile(); - if (restoreTmpFile == null) { - Log.e(TAG, "Failed creating temp file when restoring pkg : " + pkgName); - return; - } - if (!FileUtils.copyFile(restoreFile, restoreTmpFile)) { - Log.e(TAG, "Failed copying temp file when restoring pkg : " + pkgName); - return; - } PackageInstalledInfo restoreRes = new PackageInstalledInfo(); restoreRes.removedInfo = new PackageRemovedInfo(); - installPackageLI( - Uri.fromFile(restoreFile), - isForwardLocked(deletedPackage) - ? PackageManager.INSTALL_FORWARD_LOCK - : 0, false, oldInstallerPackageName, restoreTmpFile, restoreRes); + // Parse old package + parseFlags |= ~PackageManager.INSTALL_REPLACE_EXISTING; + scanPackageLI(restoreFile, parseFlags, scanMode); + synchronized (mPackages) { + grantPermissionsLP(deletedPackage, false); + mSettings.writeLP(); + } if (restoreRes.returnCode != PackageManager.INSTALL_SUCCEEDED) { Log.e(TAG, "Failed restoring pkg : " + pkgName + " after failed upgrade"); } @@ -4220,24 +4673,11 @@ class PackageManagerService extends IPackageManager.Stub { return; } } - // XXX There are probably some big issues here: upon doing - // the rename, we have reached the point of no return (the - // original .apk is gone!), so we can't fail. Yet... we can. - File scanFile = new File(newPackage.mScanPath); - if (!scanFile.renameTo(new File(newPackage.mPath))) { - Log.e(TAG, "Couldn't move package file: " + newPackage.mScanPath + " to: " + newPackage.mPath); - // TODO rename should work. Workaround - if (!FileUtils.copyFile(scanFile, new File(newPackage.mPath))) { - Log.e(TAG, "Couldn't move package file to: " + newPackage.mPath); - res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; - } + res.returnCode = setPermissionsLI(newPackage); + if(res.returnCode != PackageManager.INSTALL_SUCCEEDED) { + return; } else { - res.returnCode = setPermissionsLI(newPackage); - if(res.returnCode != PackageManager.INSTALL_SUCCEEDED) { - return; - } else { - Log.d(TAG, "New package installed in " + newPackage.mPath); - } + Log.d(TAG, "New package installed in " + newPackage.mPath); } if(res.returnCode != PackageManager.INSTALL_SUCCEEDED) { if (mInstaller != null) { @@ -4258,67 +4698,11 @@ class PackageManagerService extends IPackageManager.Stub { } } - private File copyTempInstallFile(Uri pPackageURI, - PackageInstalledInfo res) { - File tmpPackageFile = createTempPackageFile(); - int retCode = PackageManager.INSTALL_SUCCEEDED; - if (tmpPackageFile == null) { - res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; - return null; - } - - if (pPackageURI.getScheme().equals("file")) { - final File srcPackageFile = new File(pPackageURI.getPath()); - // We copy the source package file to a temp file and then rename it to the - // destination file in order to eliminate a window where the package directory - // scanner notices the new package file but it's not completely copied yet. - if (!FileUtils.copyFile(srcPackageFile, tmpPackageFile)) { - Log.e(TAG, "Couldn't copy package file to temp file."); - retCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; - } - } else if (pPackageURI.getScheme().equals("content")) { - ParcelFileDescriptor fd = null; - try { - fd = mContext.getContentResolver().openFileDescriptor(pPackageURI, "r"); - } catch (FileNotFoundException e) { - Log.e(TAG, "Couldn't open file descriptor from download service. Failed with exception " + e); - retCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; - } - if (fd == null) { - Log.e(TAG, "Couldn't open file descriptor from download service (null)."); - retCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; - } else { - if (Config.LOGV) { - Log.v(TAG, "Opened file descriptor from download service."); - } - ParcelFileDescriptor.AutoCloseInputStream - dlStream = new ParcelFileDescriptor.AutoCloseInputStream(fd); - // We copy the source package file to a temp file and then rename it to the - // destination file in order to eliminate a window where the package directory - // scanner notices the new package file but it's not completely copied yet. - if (!FileUtils.copyToFile(dlStream, tmpPackageFile)) { - Log.e(TAG, "Couldn't copy package stream to temp file."); - retCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; - } - } - } else { - Log.e(TAG, "Package URI is not 'file:' or 'content:' - " + pPackageURI); - retCode = PackageManager.INSTALL_FAILED_INVALID_URI; - } - - res.returnCode = retCode; - if (retCode != PackageManager.INSTALL_SUCCEEDED) { - if (tmpPackageFile != null && tmpPackageFile.exists()) { - tmpPackageFile.delete(); - } - return null; - } - return tmpPackageFile; - } - - private void installPackageLI(Uri pPackageURI, - int pFlags, boolean newInstall, String installerPackageName, - File tmpPackageFile, PackageInstalledInfo res) { + private void installPackageLI(InstallArgs args, + boolean newInstall, PackageInstalledInfo res) { + int pFlags = args.flags; + String installerPackageName = args.installerPackageName; + File tmpPackageFile = new File(args.getCodePath()); boolean forwardLocked = ((pFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0); boolean onSd = ((pFlags & PackageManager.INSTALL_ON_SDCARD) != 0); boolean replacingExistingPackage = false; @@ -4353,14 +4737,25 @@ class PackageManagerService extends IPackageManager.Stub { break main_flow; } + // Get rid of all references to package scan path via parser. + pp = null; + String oldCodePath = null; synchronized (mPackages) { //check if installing already existing package if ((pFlags&PackageManager.INSTALL_REPLACE_EXISTING) != 0 && mPackages.containsKey(pkgName)) { replacingExistingPackage = true; + oldCodePath = mSettings.mPackages.get(pkgName).codePathString; } } + if (!args.doRename(res.returnCode, pkgName, oldCodePath)) { + res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; + break main_flow; + } + // TODO rename pkg.mScanPath In scanPackageLI let it just set values based on mScanPath + pkg.applicationInfo.sourceDir = pkg.mScanPath= pkg.mPath = args.getCodePath(); + pkg.applicationInfo.publicSourceDir = args.getResourcePath(); if(replacingExistingPackage) { replacePackageLI(pkg, parseFlags, scanMode, installerPackageName, res); @@ -4369,15 +4764,16 @@ class PackageManagerService extends IPackageManager.Stub { installerPackageName,res); } } finally { - if (tmpPackageFile != null && tmpPackageFile.exists()) { - tmpPackageFile.delete(); + if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) { } } } private int setPermissionsLI(PackageParser.Package newPackage) { String pkgName = newPackage.packageName; - int retCode; + int retCode = 0; + // TODO Gross hack but fix later. Ideally move this to be a post installation + // check after alloting uid. if ((newPackage.applicationInfo.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0) { File destResourceFile = new File(newPackage.applicationInfo.publicSourceDir); @@ -4391,7 +4787,7 @@ class PackageManagerService extends IPackageManager.Stub { //TODO clean up the extracted public files } if (mInstaller != null) { - retCode = mInstaller.setForwardLockPerm(pkgName, + retCode = mInstaller.setForwardLockPerm(getApkName(newPackage.mPath), newPackage.applicationInfo.uid); } else { final int filePermissions = @@ -4400,15 +4796,16 @@ class PackageManagerService extends IPackageManager.Stub { newPackage.applicationInfo.uid); } } else { - final int filePermissions = - FileUtils.S_IRUSR|FileUtils.S_IWUSR|FileUtils.S_IRGRP - |FileUtils.S_IROTH; - retCode = FileUtils.setPermissions(newPackage.mPath, filePermissions, -1, -1); + // The permissions on the resource file was set when it was copied for + // non forward locked apps and apps on sdcard } + if (retCode != 0) { Log.e(TAG, "Couldn't set new package file permissions for " + newPackage.mPath + ". The return code was: " + retCode); + // TODO Define new internal error + return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; } return PackageManager.INSTALL_SUCCEEDED; } @@ -4491,10 +4888,10 @@ class PackageManagerService extends IPackageManager.Stub { } } - private File createTempPackageFile() { + private File createTempPackageFile(File installDir) { File tmpPackageFile; try { - tmpPackageFile = File.createTempFile("vmdl", ".tmp", mAppInstallDir); + tmpPackageFile = File.createTempFile("vmdl", ".tmp", installDir); } catch (IOException e) { Log.e(TAG, "Couldn't create temp file for downloaded package file."); return null; @@ -4570,6 +4967,13 @@ class PackageManagerService extends IPackageManager.Stub { sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, extras); } } + // Delete the resources here after sending the broadcast to let + // other processes clean up before deleting resources. + synchronized (mInstallLock) { + if (info.args != null) { + info.args.cleanUpResourcesLI(); + } + } return res; } @@ -4578,6 +4982,8 @@ class PackageManagerService extends IPackageManager.Stub { int uid = -1; int removedUid = -1; boolean isRemovedPackageSystemUpdate = false; + // Clean up resources deleted packages. + InstallArgs args = null; void sendBroadcast(boolean fullRemove, boolean replacing) { Bundle extras = new Bundle(1); @@ -4707,36 +5113,6 @@ class PackageManagerService extends IPackageManager.Stub { return true; } - private void deletePackageResourcesLI(String packageName, - String sourceDir, String publicSourceDir) { - if (sourceDir != null) { - File sourceFile = new File(sourceDir); - if (!sourceFile.exists()) { - Log.w(TAG, "Package source " + sourceDir + " does not exist."); - } - // Delete application's code and resources - sourceFile.delete(); - if (mInstaller != null) { - int retCode = mInstaller.rmdex(sourceFile.toString()); - if (retCode < 0) { - Log.w(TAG, "Couldn't remove dex file for package: " - + packageName + " at location " - + sourceFile.toString() + ", retcode=" + retCode); - // we don't consider this to be a failure of the core package deletion - } - } - } - if (publicSourceDir != null && !publicSourceDir.equals(sourceDir)) { - final File publicSourceFile = new File(publicSourceDir); - if (!publicSourceFile.exists()) { - Log.w(TAG, "Package public source " + publicSourceFile + " does not exist."); - } - if (publicSourceFile.exists()) { - publicSourceFile.delete(); - } - } - } - private boolean deleteInstalledPackageLI(PackageParser.Package p, boolean deleteCodeAndResources, int flags, PackageRemovedInfo outInfo) { ApplicationInfo applicationInfo = p.applicationInfo; @@ -4753,7 +5129,12 @@ class PackageManagerService extends IPackageManager.Stub { // Delete application code and resources if (deleteCodeAndResources) { - deletePackageResourcesLI(applicationInfo.packageName, + // TODO can pick up from PackageSettings as well + int installFlags = ((p.applicationInfo.flags & ApplicationInfo.FLAG_ON_SDCARD)!=0) ? + PackageManager.INSTALL_ON_SDCARD : 0; + installFlags |= ((p.applicationInfo.flags & ApplicationInfo.FLAG_FORWARD_LOCK)!=0) ? + PackageManager.INSTALL_FORWARD_LOCK : 0; + outInfo.args = createInstallArgs(installFlags, applicationInfo.sourceDir, applicationInfo.publicSourceDir); } return true; @@ -4798,12 +5179,6 @@ class PackageManagerService extends IPackageManager.Stub { Log.w(TAG, "Package " + p.packageName + " has no applicationInfo."); return false; } - boolean onSd = (p.applicationInfo.flags & ApplicationInfo.FLAG_ON_SDCARD) != 0; - // Mount sd container if needed - if (onSd) { - // TODO Better error handling from MountService api later - mountSdDir(p.packageName, Process.SYSTEM_UID) ; - } boolean ret = false; if ( (p.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { Log.i(TAG, "Removing system package:"+p.packageName); @@ -4812,22 +5187,10 @@ class PackageManagerService extends IPackageManager.Stub { ret = deleteSystemPackageLI(p, flags, outInfo); } else { Log.i(TAG, "Removing non-system package:"+p.packageName); + // Kill application pre-emptively especially for apps on sd. + killApplication(packageName, p.applicationInfo.uid); ret = deleteInstalledPackageLI (p, deleteCodeAndResources, flags, outInfo); } - if (ret && onSd) { - if (deleteCodeAndResources) { - // Post a delayed destroy on the container since there might - // be active processes holding open file handles to package - // resources which will get killed by the process killer when - // destroying the container. This might even kill the current - // process and crash the system. Delay the destroy a bit so - // that the active processes get to handle the uninstall broadcasts. - sendDelayedDestroySdDir(packageName); - } else { - // Just unmount the directory - unMountSdDir(packageName); - } - } return ret; } @@ -7520,11 +7883,31 @@ class PackageManagerService extends IPackageManager.Stub { final private String mSdEncryptKey = "AppsOnSD"; final private String mSdEncryptAlg = "AES"; private boolean mMediaMounted = false; + private static final int MAX_CONTAINERS = 250; - private MountService getMountService() { + + static MountService getMountService() { return (MountService) ServiceManager.getService("mount"); } + private String getEncryptKey() { + try { + String sdEncKey = SystemKeyStore.getInstance().retrieveKeyHexString(mSdEncryptKey); + if (sdEncKey == null) { + sdEncKey = SystemKeyStore.getInstance(). + generateNewKeyHexString(128, mSdEncryptAlg, mSdEncryptKey); + if (sdEncKey == null) { + Log.e(TAG, "Failed to create encryption keys"); + return null; + } + } + return sdEncKey; + } catch (NoSuchAlgorithmException nsae) { + Log.e(TAG, "Failed to create encryption keys with exception: " + nsae); + return null; + } + } + private String createSdDir(File tmpPackageFile, String pkgName) { // Create mount point via MountService MountService mountService = getMountService(); @@ -7535,8 +7918,6 @@ class PackageManagerService extends IPackageManager.Stub { } if (DEBUG_SD_INSTALL) Log.i(TAG, "mbLen="+mbLen); String cachePath = null; - // Remove any pending destroy messages - mHandler.removeMessages(DESTROY_SD_CONTAINER, pkgName); String sdEncKey; try { sdEncKey = SystemKeyStore.getInstance().retrieveKeyHexString(mSdEncryptKey); @@ -7597,7 +7978,13 @@ class PackageManagerService extends IPackageManager.Stub { private boolean unMountSdDir(String pkgName) { // STOPSHIP unmount directory - return true; + try { + getMountService().unmountSecureContainer(pkgName); + return true; + } catch (IllegalStateException e) { + Log.e(TAG, "Failed to unmount : " + pkgName + " with exception " + e); + } + return false; } private String getSdDir(String pkgName) { @@ -7622,10 +8009,6 @@ class PackageManagerService extends IPackageManager.Stub { private boolean destroySdDir(String pkgName) { try { - if (mHandler.hasMessages(DESTROY_SD_CONTAINER, pkgName)) { - // Don't have to send message again - mHandler.removeMessages(DESTROY_SD_CONTAINER, pkgName); - } // We need to destroy right away getMountService().destroySecureContainer(pkgName); return true; @@ -7635,7 +8018,7 @@ class PackageManagerService extends IPackageManager.Stub { } } - private String[] getSecureContainerList() { + static String[] getSecureContainerList() { try { return getMountService().getSecureContainerList(); } catch (IllegalStateException e) { @@ -7644,13 +8027,47 @@ class PackageManagerService extends IPackageManager.Stub { return null; } - private void sendDelayedDestroySdDir(String pkgName) { - if (mHandler.hasMessages(DESTROY_SD_CONTAINER, pkgName)) { - // Don't have to send message again - return; + static String getTempContainerId() { + String prefix = "smdl1tmp"; + int tmpIdx = 1; + String list[] = getSecureContainerList(); + if (list != null) { + int idx = 0; + int idList[] = new int[MAX_CONTAINERS]; + boolean neverFound = true; + for (String name : list) { + // Ignore null entries + if (name == null) { + continue; + } + int sidx = name.indexOf(prefix); + if (sidx == -1) { + // Not a temp file. just ignore + continue; + } + String subStr = name.substring(sidx + prefix.length()); + idList[idx] = -1; + if (subStr != null) { + try { + int cid = Integer.parseInt(subStr); + idList[idx++] = cid; + neverFound = false; + } catch (NumberFormatException e) { + } + } + } + if (!neverFound) { + // Sort idList + Arrays.sort(idList); + for (int j = 1; j <= idList.length; j++) { + if (idList[j-1] != j) { + tmpIdx = j; + break; + } + } + } } - Message msg = mHandler.obtainMessage(DESTROY_SD_CONTAINER, pkgName); - mHandler.sendMessageDelayed(msg, DESTROY_SD_CONTAINER_DELAY); + return prefix + tmpIdx; } public void updateExternalMediaStatus(final boolean mediaStatus) {