summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cmds/installd/commands.c12
-rw-r--r--cmds/installd/installd.h1
-rw-r--r--cmds/pm/src/com/android/commands/pm/Pm.java5
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java18
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl9
-rw-r--r--core/java/android/content/pm/PackageManager.java16
-rw-r--r--core/java/android/content/pm/PackageParser.java31
-rw-r--r--services/java/com/android/server/PackageManagerService.java483
8 files changed, 372 insertions, 203 deletions
diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c
index dcae0c7..79bda74 100644
--- a/cmds/installd/commands.c
+++ b/cmds/installd/commands.c
@@ -218,14 +218,20 @@ int free_cache(int free_size)
static int is_valid_apk_path(const char *path)
{
int len = strlen(APK_DIR_PREFIX);
+int nosubdircheck = 0;
if (strncmp(path, APK_DIR_PREFIX, len)) {
len = strlen(PROTECTED_DIR_PREFIX);
if (strncmp(path, PROTECTED_DIR_PREFIX, len)) {
- LOGE("invalid apk path '%s' (bad prefix)\n", path);
- return 0;
+ len = strlen(SDCARD_DIR_PREFIX);
+ if (strncmp(path, SDCARD_DIR_PREFIX, len)) {
+ LOGE("invalid apk path '%s' (bad prefix)\n", path);
+ return 0;
+ } else {
+ nosubdircheck = 1;
+ }
}
}
- if (strchr(path + len, '/')) {
+ if ((nosubdircheck != 1) && strchr(path + len, '/')) {
LOGE("invalid apk path '%s' (subdir?)\n", path);
return 0;
}
diff --git a/cmds/installd/installd.h b/cmds/installd/installd.h
index 1679d14..35a173e 100644
--- a/cmds/installd/installd.h
+++ b/cmds/installd/installd.h
@@ -68,6 +68,7 @@
/* other handy constants */
#define PROTECTED_DIR_PREFIX "/data/app-private/"
+#define SDCARD_DIR_PREFIX "/asec/"
#define DALVIK_CACHE_PREFIX "/data/dalvik-cache/"
#define DALVIK_CACHE_POSTFIX "/classes.dex"
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 79eb310..4953f5d 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -594,6 +594,8 @@ public final class Pm {
}
} else if (opt.equals("-t")) {
installFlags |= PackageManager.INSTALL_ALLOW_TEST;
+ } else if (opt.equals("-s")) {
+ installFlags |= PackageManager.INSTALL_ON_SDCARD;
} else {
System.err.println("Error: Unknown option: " + opt);
showUsage();
@@ -822,7 +824,7 @@ public final class Pm {
System.err.println(" pm list instrumentation [-f] [TARGET-PACKAGE]");
System.err.println(" pm list features");
System.err.println(" pm path PACKAGE");
- System.err.println(" pm install [-l] [-r] [-t] [-i INSTALLER_PACKAGE_NAME] PATH");
+ System.err.println(" pm install [-l] [-r] [-t] [-i INSTALLER_PACKAGE_NAME] [-s] PATH");
System.err.println(" pm uninstall [-k] PACKAGE");
System.err.println(" pm enable PACKAGE_OR_COMPONENT");
System.err.println(" pm disable PACKAGE_OR_COMPONENT");
@@ -854,6 +856,7 @@ public final class Pm {
System.err.println(" -r: reinstall an exisiting app, keeping its data.");
System.err.println(" -t: allow test .apks to be installed.");
System.err.println(" -i: specify the installer package name.");
+ System.err.println(" -s: install package on sdcard.");
System.err.println("");
System.err.println("The uninstall command removes a package from the system. Options:");
System.err.println(" -k: keep the data and cache directories around.");
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 3dea286..b27cd6c 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -218,6 +218,22 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
public static final int FLAG_NEVER_ENCRYPT = 1<<17;
/**
+ * Value for {@link #flags}: Set to true if the application has been
+ * installed using the forward lock option.
+ *
+ * {@hide}
+ */
+ public static final int FLAG_FORWARD_LOCK = 1<<18;
+
+ /**
+ * Value for {@link #flags}: Set to true if the application is
+ * currently installed on the sdcard.
+ *
+ * {@hide}
+ */
+ public static final int FLAG_ON_SDCARD = 1<<19;
+
+ /**
* Flags associated with the application. Any combination of
* {@link #FLAG_SYSTEM}, {@link #FLAG_DEBUGGABLE}, {@link #FLAG_HAS_CODE},
* {@link #FLAG_PERSISTENT}, {@link #FLAG_FACTORY_TEST}, and
@@ -227,6 +243,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
* {@link #FLAG_SUPPORTS_NORMAL_SCREENS},
* {@link #FLAG_SUPPORTS_LARGE_SCREENS}, {@link #FLAG_RESIZEABLE_FOR_SCREENS},
* {@link #FLAG_SUPPORTS_SCREEN_DENSITIES}.
+ * {@link #FLAG_FWD_LOCKED},
+ * {@link #FLAG_ON_SDCARD}
*/
public int flags = 0;
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index fc6538f..54db5e0 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -296,4 +296,13 @@ interface IPackageManager {
* in the special development "no pre-dexopt" mode.
*/
boolean performDexOpt(String packageName);
+
+ /**
+ * Update status of external media on the package manager to scan and
+ * install packages installed on the external media. Like say the
+ * MountService uses this to call into the package manager to update
+ * status of sdcard.
+ */
+ void updateExternalMediaStatus(boolean mounted);
+
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 53a966d..bc59c94 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -252,6 +252,13 @@ public abstract class PackageManager {
public static final int INSTALL_ALLOW_TEST = 0x00000004;
/**
+ * Flag parameter for {@link #installPackage} to indicate that this
+ * package has to be installed on the sdcard.
+ * @hide
+ */
+ public static final int INSTALL_ON_SDCARD = 0x00000008;
+
+ /**
* Flag parameter for
* {@link #setComponentEnabledSetting(android.content.ComponentName, int, int)} to indicate
* that you don't want to kill the app containing the component. Be careful when you set this
@@ -411,6 +418,15 @@ public abstract class PackageManager {
*/
public static final int INSTALL_FAILED_MISSING_FEATURE = -17;
+ // ------ Errors related to sdcard
+ /**
+ * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
+ * a secure container mount point couldn't be accessed on external media.
+ * @hide
+ */
+ public static final int INSTALL_FAILED_CONTAINER_ERROR = -18;
+
/**
* Installation parse return code: this is passed to the {@link IPackageInstallObserver} by
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index ad99f54..8a5df32 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -327,16 +327,18 @@ public class PackageParser {
return null;
}
- public final static int PARSE_IS_SYSTEM = 0x0001;
- public final static int PARSE_CHATTY = 0x0002;
- public final static int PARSE_MUST_BE_APK = 0x0004;
- public final static int PARSE_IGNORE_PROCESSES = 0x0008;
+ public final static int PARSE_IS_SYSTEM = 1<<0;
+ public final static int PARSE_CHATTY = 1<<1;
+ public final static int PARSE_MUST_BE_APK = 1<<2;
+ public final static int PARSE_IGNORE_PROCESSES = 1<<3;
+ public final static int PARSE_FORWARD_LOCK = 1<<4;
+ public final static int PARSE_ON_SDCARD = 1<<5;
public int getParseError() {
return mParseError;
}
- public Package parsePackage(File sourceFile, String destFileName,
+ public Package parsePackage(File sourceFile, String destCodePath,
DisplayMetrics metrics, int flags) {
mParseError = PackageManager.INSTALL_SUCCEEDED;
@@ -413,8 +415,11 @@ public class PackageParser {
parser.close();
assmgr.close();
- pkg.applicationInfo.sourceDir = destFileName;
- pkg.applicationInfo.publicSourceDir = destFileName;
+ // Set code and resource paths
+ pkg.mPath = destCodePath;
+ pkg.mScanPath = mArchiveSourcePath;
+ //pkg.applicationInfo.sourceDir = destCodePath;
+ //pkg.applicationInfo.publicSourceDir = destRes;
pkg.mSignatures = null;
return pkg;
@@ -1369,6 +1374,14 @@ public class PackageParser {
}
}
+ if ((flags & PARSE_FORWARD_LOCK) != 0) {
+ ai.flags |= ApplicationInfo.FLAG_FORWARD_LOCK;
+ }
+
+ if ((flags & PARSE_ON_SDCARD) != 0) {
+ ai.flags |= ApplicationInfo.FLAG_ON_SDCARD;
+ }
+
if (sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestApplication_debuggable,
false)) {
@@ -2530,10 +2543,6 @@ public class PackageParser {
// preferred up order.
public int mPreferredOrder = 0;
- // For use by package manager service to keep track of which apps
- // have been installed with forward locking.
- public boolean mForwardLocked;
-
// For use by the package manager to keep track of the path to the
// file an app came from.
public String mScanPath;
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 2b12268..4b9b366 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -135,8 +135,8 @@ class PackageManagerService extends IPackageManager.Stub {
static final int SCAN_NO_DEX = 1<<1;
static final int SCAN_FORCE_DEX = 1<<2;
static final int SCAN_UPDATE_SIGNATURE = 1<<3;
- static final int SCAN_FORWARD_LOCKED = 1<<4;
- static final int SCAN_NEW_INSTALL = 1<<5;
+ static final int SCAN_NEW_INSTALL = 1<<4;
+ static final int SCAN_NO_PATHS = 1<<5;
final HandlerThread mHandlerThread = new HandlerThread("PackageManager",
Process.THREAD_PRIORITY_BACKGROUND);
@@ -281,8 +281,10 @@ class PackageManagerService extends IPackageManager.Stub {
final HashMap<String, ArrayList<String>> mPendingBroadcasts
= new HashMap<String, ArrayList<String>>();
static final int SEND_PENDING_BROADCAST = 1;
+ static final int DESTROY_SD_CONTAINER = 2;
// Delay time in millisecs
static final int BROADCAST_DELAY = 10 * 1000;
+ static final int DESTROY_SD_CONTAINER_DELAY = 30 * 1000;
class PackageHandler extends Handler {
PackageHandler(Looper looper) {
@@ -290,6 +292,15 @@ class PackageManagerService extends IPackageManager.Stub {
}
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);
+ }
+ }
+ break;
case SEND_PENDING_BROADCAST : {
String packages[];
ArrayList components[];
@@ -562,12 +573,12 @@ class PackageManagerService extends IPackageManager.Stub {
mFrameworkDir.getPath(), OBSERVER_EVENTS, true);
mFrameworkInstallObserver.startWatching();
scanDirLI(mFrameworkDir, PackageParser.PARSE_IS_SYSTEM,
- scanMode | SCAN_NO_DEX);
+ scanMode | SCAN_NO_DEX | SCAN_NO_PATHS);
mSystemAppDir = new File(Environment.getRootDirectory(), "app");
mSystemInstallObserver = new AppDirObserver(
mSystemAppDir.getPath(), OBSERVER_EVENTS, true);
mSystemInstallObserver.startWatching();
- scanDirLI(mSystemAppDir, PackageParser.PARSE_IS_SYSTEM, scanMode);
+ scanDirLI(mSystemAppDir, PackageParser.PARSE_IS_SYSTEM, scanMode | SCAN_NO_PATHS);
mAppInstallDir = new File(dataDir, "app");
if (mInstaller == null) {
// Make sure these dirs exist, when we are running in
@@ -594,7 +605,7 @@ class PackageManagerService extends IPackageManager.Stub {
mDrmAppInstallObserver = new AppDirObserver(
mDrmAppPrivateInstallDir.getPath(), OBSERVER_EVENTS, false);
mDrmAppInstallObserver.startWatching();
- scanDirLI(mDrmAppPrivateInstallDir, 0, scanMode | SCAN_FORWARD_LOCKED);
+ scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK, scanMode);
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END,
SystemClock.uptimeMillis());
@@ -1961,12 +1972,7 @@ class PackageManagerService extends IPackageManager.Stub {
int i;
for (i=0; i<files.length; i++) {
File file = new File(dir, files[i]);
- File resFile = file;
- // Pick up the resource path from settings for fwd locked apps
- if ((scanMode & SCAN_FORWARD_LOCKED) != 0) {
- resFile = null;
- }
- PackageParser.Package pkg = scanPackageLI(file, file, resFile,
+ PackageParser.Package pkg = scanPackageLI(file,
flags|PackageParser.PARSE_MUST_BE_APK, scanMode);
}
}
@@ -2009,14 +2015,15 @@ class PackageManagerService extends IPackageManager.Stub {
* Returns null in case of errors and the error code is stored in mLastScanError
*/
private PackageParser.Package scanPackageLI(File scanFile,
- File destCodeFile, File destResourceFile, int parseFlags,
+ int parseFlags,
int scanMode) {
mLastScanError = PackageManager.INSTALL_SUCCEEDED;
parseFlags |= mDefParseFlags;
PackageParser pp = new PackageParser(scanFile.getPath());
pp.setSeparateProcesses(mSeparateProcesses);
final PackageParser.Package pkg = pp.parsePackage(scanFile,
- destCodeFile.getAbsolutePath(), mMetrics, parseFlags);
+ scanFile.getPath(),
+ mMetrics, parseFlags);
if (pkg == null) {
mLastScanError = pp.getParseError();
return null;
@@ -2062,16 +2069,12 @@ class PackageManagerService extends IPackageManager.Stub {
}
// The apk is forward locked (not public) if its code and resources
// are kept in different files.
+ // TODO grab this value from PackageSettings
if (ps != null && !ps.codePath.equals(ps.resourcePath)) {
- scanMode |= SCAN_FORWARD_LOCKED;
- }
- File resFile = destResourceFile;
- if (ps != null && ((scanMode & SCAN_FORWARD_LOCKED) != 0)) {
- resFile = getFwdLockedResource(ps.name);
+ parseFlags |= PackageParser.PARSE_FORWARD_LOCK;
}
// Note that we invoke the following method only if we are about to unpack an application
- return scanPackageLI(scanFile, destCodeFile, resFile,
- pkg, parseFlags, scanMode | SCAN_UPDATE_SIGNATURE);
+ return scanPackageLI(pkg, parseFlags, scanMode | SCAN_UPDATE_SIGNATURE);
}
private static String fixProcessName(String defProcessName,
@@ -2138,7 +2141,7 @@ class PackageManagerService extends IPackageManager.Stub {
try {
if (forceDex || dalvik.system.DexFile.isDexOptNeeded(path)) {
ret = mInstaller.dexopt(path, pkg.applicationInfo.uid,
- !pkg.mForwardLocked);
+ !isForwardLocked(pkg));
pkg.mDidDexOpt = true;
performed = true;
}
@@ -2164,9 +2167,8 @@ class PackageManagerService extends IPackageManager.Stub {
}
private PackageParser.Package scanPackageLI(
- File scanFile, File destCodeFile, File destResourceFile,
PackageParser.Package pkg, int parseFlags, int scanMode) {
-
+ File scanFile = new File(pkg.mScanPath);
mScanningPath = scanFile;
if (pkg == null) {
mLastScanError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
@@ -2223,6 +2225,43 @@ class PackageManagerService extends IPackageManager.Stub {
return null;
}
+ // 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();
+
SharedUserSetting suid = null;
PackageSetting pkgSetting = null;
@@ -2394,7 +2433,6 @@ class PackageManagerService extends IPackageManager.Stub {
pkg.applicationInfo.packageName,
pkg.applicationInfo.processName,
pkg.applicationInfo.uid);
- pkg.applicationInfo.publicSourceDir = destResourceFile.toString();
File dataPath;
if (mPlatformPackage == pkg) {
@@ -2509,8 +2547,6 @@ class PackageManagerService extends IPackageManager.Stub {
return null;
}
}
-
- pkg.mForwardLocked = (scanMode&SCAN_FORWARD_LOCKED) != 0;
pkg.mScanPath = path;
if ((scanMode&SCAN_NO_DEX) == 0) {
@@ -2545,7 +2581,7 @@ class PackageManagerService extends IPackageManager.Stub {
}
synchronized (mPackages) {
// Add the new setting to mSettings
- mSettings.insertPackageSettingLP(pkgSetting, pkg, destCodeFile, destResourceFile);
+ mSettings.insertPackageSettingLP(pkgSetting, pkg);
// Add the new setting to mPackages
mPackages.put(pkg.applicationInfo.packageName, pkg);
int N = pkg.providers.size();
@@ -3714,7 +3750,7 @@ class PackageManagerService extends IPackageManager.Stub {
if ((event&ADD_EVENTS) != 0) {
PackageParser.Package p = mAppDirs.get(fullPathStr);
if (p == null) {
- p = scanPackageLI(fullPath, fullPath, fullPath,
+ p = scanPackageLI(fullPath,
(mIsRom ? PackageParser.PARSE_IS_SYSTEM : 0) |
PackageParser.PARSE_CHATTY |
PackageParser.PARSE_MUST_BE_APK,
@@ -3823,13 +3859,14 @@ class PackageManagerService extends IPackageManager.Stub {
/*
* Install a non-existing package.
*/
- private void installNewPackageLI(String pkgName,
- File tmpPackageFile,
- String destFilePath, File destPackageFile, File destResourceFile,
- PackageParser.Package pkg, boolean forwardLocked, boolean newInstall,
+ private void installNewPackageLI(PackageParser.Package pkg,
+ int parseFlags,
+ int scanMode,
String installerPackageName, PackageInstalledInfo res) {
// 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();
@@ -3838,7 +3875,7 @@ class PackageManagerService extends IPackageManager.Stub {
}
res.name = pkgName;
synchronized(mPackages) {
- if (mPackages.containsKey(pkgName) || mAppDirs.containsKey(destFilePath)) {
+ if (mPackages.containsKey(pkgName) || mAppDirs.containsKey(pkg.mPath)) {
// Don't allow installation over an existing package with the same name.
Log.w(TAG, "Attempt to re-install " + pkgName
+ " without first uninstalling.");
@@ -3846,32 +3883,37 @@ class PackageManagerService extends IPackageManager.Stub {
return;
}
}
- 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();
- }
mLastScanError = PackageManager.INSTALL_SUCCEEDED;
- PackageParser.Package newPackage = scanPackageLI(tmpPackageFile, destPackageFile,
- destResourceFile, pkg, 0,
- SCAN_MONITOR | SCAN_FORCE_DEX
- | SCAN_UPDATE_SIGNATURE
- | (forwardLocked ? SCAN_FORWARD_LOCKED : 0)
- | (newInstall ? SCAN_NEW_INSTALL : 0));
+ 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 " + destPackageFile);
+ Log.w(TAG, "Package couldn't be installed in " + pkg.mPath);
if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) {
res.returnCode = PackageManager.INSTALL_FAILED_INVALID_APK;
}
} else {
- updateSettingsLI(pkgName, tmpPackageFile,
- destFilePath, destPackageFile,
- destResourceFile, pkg,
- newPackage,
- true,
- forwardLocked,
+ 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) {
@@ -3885,15 +3927,19 @@ class PackageManagerService extends IPackageManager.Stub {
res.removedInfo);
}
}
+ if (onSd && res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
+ // Destroy cache
+ destroySdDir(pkgName);
+ }
}
- private void replacePackageLI(String pkgName,
- File tmpPackageFile,
- String destFilePath, File destPackageFile, File destResourceFile,
- PackageParser.Package pkg, boolean forwardLocked, boolean newInstall,
+ private void replacePackageLI(PackageParser.Package pkg,
+ int parseFlags,
+ int scanMode,
String installerPackageName, PackageInstalledInfo res) {
PackageParser.Package oldPackage;
+ String pkgName = pkg.packageName;
// First find the old package info and check signatures
synchronized(mPackages) {
oldPackage = mPackages.get(pkgName);
@@ -3905,21 +3951,15 @@ class PackageManagerService extends IPackageManager.Stub {
}
boolean sysPkg = ((oldPackage.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
if(sysPkg) {
- replaceSystemPackageLI(oldPackage,
- tmpPackageFile, destFilePath,
- destPackageFile, destResourceFile, pkg, forwardLocked,
- newInstall, installerPackageName, res);
+ replaceSystemPackageLI(oldPackage, pkg, parseFlags, scanMode, installerPackageName, res);
} else {
- replaceNonSystemPackageLI(oldPackage, tmpPackageFile, destFilePath,
- destPackageFile, destResourceFile, pkg, forwardLocked,
- newInstall, installerPackageName, res);
+ replaceNonSystemPackageLI(oldPackage, pkg, parseFlags, scanMode, installerPackageName, res);
}
}
private void replaceNonSystemPackageLI(PackageParser.Package deletedPackage,
- File tmpPackageFile,
- String destFilePath, File destPackageFile, File destResourceFile,
- PackageParser.Package pkg, boolean forwardLocked, boolean newInstall,
+ PackageParser.Package pkg,
+ int parseFlags, int scanMode,
String installerPackageName, PackageInstalledInfo res) {
PackageParser.Package newPackage = null;
String pkgName = deletedPackage.packageName;
@@ -3931,7 +3971,7 @@ class PackageManagerService extends IPackageManager.Stub {
oldInstallerPackageName = mSettings.getInstallerPackageName(pkgName);
}
- int parseFlags = PackageManager.INSTALL_REPLACE_EXISTING;
+ parseFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
// First delete the existing package while retaining the data directory
if (!deletePackageLI(pkgName, false, PackageManager.DONT_DELETE_DATA,
res.removedInfo)) {
@@ -3941,24 +3981,13 @@ class PackageManagerService extends IPackageManager.Stub {
} else {
// Successfully deleted the old package. Now proceed with re-installation
mLastScanError = PackageManager.INSTALL_SUCCEEDED;
- newPackage = scanPackageLI(tmpPackageFile, destPackageFile,
- destResourceFile, pkg, parseFlags,
- SCAN_MONITOR | SCAN_FORCE_DEX
- | SCAN_UPDATE_SIGNATURE
- | (forwardLocked ? SCAN_FORWARD_LOCKED : 0)
- | (newInstall ? SCAN_NEW_INSTALL : 0));
+ newPackage = scanPackageLI(pkg, parseFlags, scanMode);
if (newPackage == null) {
- Log.w(TAG, "Package couldn't be installed in " + destPackageFile);
+ Log.w(TAG, "Package couldn't be installed in " + pkg.mPath);
if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) {
res.returnCode = PackageManager.INSTALL_FAILED_INVALID_APK;
- }
- } else {
- updateSettingsLI(pkgName, tmpPackageFile,
- destFilePath, destPackageFile,
- destResourceFile, pkg,
- newPackage,
- true,
- forwardLocked,
+ }
+ updateSettingsLI(newPackage,
installerPackageName,
res);
updatedSettings = true;
@@ -4028,13 +4057,12 @@ class PackageManagerService extends IPackageManager.Stub {
}
private void replaceSystemPackageLI(PackageParser.Package deletedPackage,
- File tmpPackageFile,
- String destFilePath, File destPackageFile, File destResourceFile,
- PackageParser.Package pkg, boolean forwardLocked, boolean newInstall,
+ PackageParser.Package pkg,
+ int parseFlags, int scanMode,
String installerPackageName, PackageInstalledInfo res) {
PackageParser.Package newPackage = null;
boolean updatedSettings = false;
- int parseFlags = PackageManager.INSTALL_REPLACE_EXISTING |
+ parseFlags |= PackageManager.INSTALL_REPLACE_EXISTING |
PackageParser.PARSE_IS_SYSTEM;
String packageName = deletedPackage.packageName;
res.returnCode = PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE;
@@ -4064,26 +4092,14 @@ class PackageManagerService extends IPackageManager.Stub {
// Successfully disabled the old package. Now proceed with re-installation
mLastScanError = PackageManager.INSTALL_SUCCEEDED;
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
- newPackage = scanPackageLI(tmpPackageFile, destPackageFile,
- destResourceFile, pkg, parseFlags,
- SCAN_MONITOR | SCAN_FORCE_DEX
- | SCAN_UPDATE_SIGNATURE
- | (forwardLocked ? SCAN_FORWARD_LOCKED : 0)
- | (newInstall ? SCAN_NEW_INSTALL : 0));
+ newPackage = scanPackageLI(pkg, parseFlags, scanMode);
if (newPackage == null) {
- Log.w(TAG, "Package couldn't be installed in " + destPackageFile);
+ Log.w(TAG, "Package couldn't be installed in " + pkg.mPath);
if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) {
res.returnCode = PackageManager.INSTALL_FAILED_INVALID_APK;
}
} else {
- updateSettingsLI(packageName, tmpPackageFile,
- destFilePath, destPackageFile,
- destResourceFile, pkg,
- newPackage,
- true,
- forwardLocked,
- installerPackageName,
- res);
+ updateSettingsLI(newPackage, installerPackageName, res);
updatedSettings = true;
}
@@ -4102,9 +4118,7 @@ class PackageManagerService extends IPackageManager.Stub {
removePackageLI(newPackage, true);
}
// Add back the old system package
- scanPackageLI(oldPkgSetting.codePath, oldPkgSetting.codePath,
- oldPkgSetting.resourcePath,
- oldPkg, parseFlags,
+ scanPackageLI(oldPkg, parseFlags,
SCAN_MONITOR
| SCAN_UPDATE_SIGNATURE);
// Restore the old system information in Settings
@@ -4119,14 +4133,9 @@ class PackageManagerService extends IPackageManager.Stub {
}
}
- private void updateSettingsLI(String pkgName, File tmpPackageFile,
- String destFilePath, File destPackageFile,
- File destResourceFile,
- PackageParser.Package pkg,
- PackageParser.Package newPackage,
- boolean replacingExistingPackage,
- boolean forwardLocked,
+ private void updateSettingsLI(PackageParser.Package newPackage,
String installerPackageName, PackageInstalledInfo res) {
+ String pkgName = newPackage.packageName;
synchronized (mPackages) {
//write settings. the installStatus will be incomplete at this stage.
//note that the new package setting would have already been
@@ -4136,11 +4145,10 @@ class PackageManagerService extends IPackageManager.Stub {
}
int retCode = 0;
- if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0) {
- retCode = mInstaller.movedex(tmpPackageFile.toString(),
- destPackageFile.toString());
+ if ((newPackage.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0) {
+ retCode = mInstaller.movedex(newPackage.mScanPath, newPackage.mPath);
if (retCode != 0) {
- Log.e(TAG, "Couldn't rename dex file: " + destPackageFile);
+ Log.e(TAG, "Couldn't rename dex file: " + newPackage.mPath);
res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
return;
}
@@ -4148,22 +4156,25 @@ class PackageManagerService extends IPackageManager.Stub {
// 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.
- if (!tmpPackageFile.renameTo(destPackageFile)) {
- Log.e(TAG, "Couldn't move package file to: " + destPackageFile);
- res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+ 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;
+ }
} else {
- res.returnCode = setPermissionsLI(pkgName, newPackage, destFilePath,
- destResourceFile,
- forwardLocked);
+ res.returnCode = setPermissionsLI(newPackage);
if(res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
return;
} else {
- Log.d(TAG, "New package installed in " + destPackageFile);
+ Log.d(TAG, "New package installed in " + newPackage.mPath);
}
}
if(res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
if (mInstaller != null) {
- mInstaller.rmdex(tmpPackageFile.getPath());
+ mInstaller.rmdex(newPackage.mScanPath);
}
}
@@ -4180,11 +4191,6 @@ class PackageManagerService extends IPackageManager.Stub {
}
}
- private File getFwdLockedResource(String pkgName) {
- final String publicZipFileName = pkgName + ".zip";
- return new File(mAppInstallDir, publicZipFileName);
- }
-
private File copyTempInstallFile(Uri pPackageURI,
PackageInstalledInfo res) {
File tmpPackageFile = createTempPackageFile();
@@ -4246,46 +4252,29 @@ class PackageManagerService extends IPackageManager.Stub {
private void installPackageLI(Uri pPackageURI,
int pFlags, boolean newInstall, String installerPackageName,
File tmpPackageFile, PackageInstalledInfo res) {
- String pkgName = null;
- boolean forwardLocked = false;
+ boolean forwardLocked = ((pFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0);
+ boolean onSd = ((pFlags & PackageManager.INSTALL_ON_SDCARD) != 0);
boolean replacingExistingPackage = false;
+ int scanMode = SCAN_MONITOR | SCAN_FORCE_DEX | SCAN_UPDATE_SIGNATURE
+ | (newInstall ? SCAN_NEW_INSTALL : 0);
// Result object to be returned
res.returnCode = PackageManager.INSTALL_SUCCEEDED;
main_flow: try {
- pkgName = PackageParser.parsePackageName(
- tmpPackageFile.getAbsolutePath(), 0);
- if (pkgName == null) {
- Log.e(TAG, "Couldn't find a package name in : " + tmpPackageFile);
- res.returnCode = PackageManager.INSTALL_FAILED_INVALID_APK;
- break main_flow;
- }
- res.name = pkgName;
- //initialize some variables before installing pkg
- final String pkgFileName = pkgName + ".apk";
- final File destDir = ((pFlags&PackageManager.INSTALL_FORWARD_LOCK) != 0)
- ? mDrmAppPrivateInstallDir
- : mAppInstallDir;
- final File destPackageFile = new File(destDir, pkgFileName);
- final String destFilePath = destPackageFile.getAbsolutePath();
- File destResourceFile;
- if ((pFlags&PackageManager.INSTALL_FORWARD_LOCK) != 0) {
- destResourceFile = getFwdLockedResource(pkgName);
- forwardLocked = true;
- } else {
- destResourceFile = destPackageFile;
- }
// Retrieve PackageSettings and parse package
- int parseFlags = PackageParser.PARSE_CHATTY;
+ int parseFlags = PackageParser.PARSE_CHATTY |
+ (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0) |
+ (onSd ? PackageParser.PARSE_ON_SDCARD : 0);
parseFlags |= mDefParseFlags;
PackageParser pp = new PackageParser(tmpPackageFile.getPath());
pp.setSeparateProcesses(mSeparateProcesses);
final PackageParser.Package pkg = pp.parsePackage(tmpPackageFile,
- destPackageFile.getAbsolutePath(), mMetrics, parseFlags);
+ null, mMetrics, parseFlags);
if (pkg == null) {
res.returnCode = pp.getParseError();
break main_flow;
}
+ String pkgName = res.name = pkg.packageName;
if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_TEST_ONLY) != 0) {
if ((pFlags&PackageManager.INSTALL_ALLOW_TEST) == 0) {
res.returnCode = PackageManager.INSTALL_FAILED_TEST_ONLY;
@@ -4306,17 +4295,11 @@ class PackageManagerService extends IPackageManager.Stub {
}
if(replacingExistingPackage) {
- replacePackageLI(pkgName,
- tmpPackageFile,
- destFilePath, destPackageFile, destResourceFile,
- pkg, forwardLocked, newInstall, installerPackageName,
- res);
+ replacePackageLI(pkg, parseFlags, scanMode,
+ installerPackageName, res);
} else {
- installNewPackageLI(pkgName,
- tmpPackageFile,
- destFilePath, destPackageFile, destResourceFile,
- pkg, forwardLocked, newInstall, installerPackageName,
- res);
+ installNewPackageLI(pkg, parseFlags, scanMode,
+ installerPackageName,res);
}
} finally {
if (tmpPackageFile != null && tmpPackageFile.exists()) {
@@ -4325,13 +4308,12 @@ class PackageManagerService extends IPackageManager.Stub {
}
}
- private int setPermissionsLI(String pkgName,
- PackageParser.Package newPackage,
- String destFilePath,
- File destResourceFile,
- boolean forwardLocked) {
+ private int setPermissionsLI(PackageParser.Package newPackage) {
+ String pkgName = newPackage.packageName;
int retCode;
- if (forwardLocked) {
+ if ((newPackage.applicationInfo.flags
+ & ApplicationInfo.FLAG_FORWARD_LOCK) != 0) {
+ File destResourceFile = new File(newPackage.applicationInfo.publicSourceDir);
try {
extractPublicFiles(newPackage, destResourceFile);
} catch (IOException e) {
@@ -4347,25 +4329,25 @@ class PackageManagerService extends IPackageManager.Stub {
} else {
final int filePermissions =
FileUtils.S_IRUSR|FileUtils.S_IWUSR|FileUtils.S_IRGRP;
- retCode = FileUtils.setPermissions(destFilePath, filePermissions, -1,
+ retCode = FileUtils.setPermissions(newPackage.mPath, filePermissions, -1,
newPackage.applicationInfo.uid);
}
} else {
final int filePermissions =
FileUtils.S_IRUSR|FileUtils.S_IWUSR|FileUtils.S_IRGRP
|FileUtils.S_IROTH;
- retCode = FileUtils.setPermissions(destFilePath, filePermissions, -1, -1);
+ retCode = FileUtils.setPermissions(newPackage.mPath, filePermissions, -1, -1);
}
if (retCode != 0) {
- Log.e(TAG, "Couldn't set new package file permissions for " + destFilePath
+ Log.e(TAG, "Couldn't set new package file permissions for " +
+ newPackage.mPath
+ ". The return code was: " + retCode);
}
return PackageManager.INSTALL_SUCCEEDED;
}
- private boolean isForwardLocked(PackageParser.Package deletedPackage) {
- final ApplicationInfo applicationInfo = deletedPackage.applicationInfo;
- return applicationInfo.sourceDir.startsWith(mDrmAppPrivateInstallDir.getAbsolutePath());
+ private boolean isForwardLocked(PackageParser.Package pkg) {
+ return ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0);
}
private void extractPublicFiles(PackageParser.Package newPackage,
@@ -4643,9 +4625,9 @@ class PackageManagerService extends IPackageManager.Stub {
mSettings.enableSystemPackageLP(p.packageName);
}
// Install the system package
- PackageParser.Package newPkg = scanPackageLI(ps.codePath, ps.codePath, ps.resourcePath,
+ PackageParser.Package newPkg = scanPackageLI(ps.codePath,
PackageParser.PARSE_MUST_BE_APK | PackageParser.PARSE_IS_SYSTEM,
- SCAN_MONITOR);
+ SCAN_MONITOR | SCAN_NO_PATHS);
if (newPkg == null) {
Log.w(TAG, "Failed to restore system package:"+p.packageName+" with error:" + mLastScanError);
@@ -4749,14 +4731,32 @@ 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);
// When an updated system application is deleted we delete the existing resources as well and
// fall back to existing code in system partition
- return deleteSystemPackageLI(p, flags, outInfo);
+ ret = deleteSystemPackageLI(p, flags, outInfo);
+ } else {
+ Log.i(TAG, "Removing non-system package:"+p.packageName);
+ ret = deleteInstalledPackageLI (p, deleteCodeAndResources, flags, outInfo);
+ }
+ if (ret && onSd) {
+ // 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);
}
- Log.i(TAG, "Removing non-system package:"+p.packageName);
- return deleteInstalledPackageLI (p, deleteCodeAndResources, flags, outInfo);
+ return ret;
}
public void clearApplicationUserData(final String packageName,
@@ -6343,22 +6343,23 @@ class PackageManagerService extends IPackageManager.Stub {
return p;
}
- private void insertPackageSettingLP(PackageSetting p, PackageParser.Package pkg,
- File codePath, File resourcePath) {
+ private void insertPackageSettingLP(PackageSetting p, PackageParser.Package pkg) {
p.pkg = pkg;
+ String codePath = pkg.applicationInfo.sourceDir;
+ String resourcePath = pkg.applicationInfo.publicSourceDir;
// Update code path if needed
- if (!codePath.toString().equalsIgnoreCase(p.codePathString)) {
+ if (!codePath.equalsIgnoreCase(p.codePathString)) {
Log.w(TAG, "Code path for pkg : " + p.pkg.packageName +
" changing from " + p.codePathString + " to " + codePath);
- p.codePath = codePath;
- p.codePathString = codePath.toString();
+ p.codePath = new File(codePath);
+ p.codePathString = codePath;
}
//Update resource path if needed
- if (!resourcePath.toString().equalsIgnoreCase(p.resourcePathString)) {
+ if (!resourcePath.equalsIgnoreCase(p.resourcePathString)) {
Log.w(TAG, "Resource path for pkg : " + p.pkg.packageName +
" changing from " + p.resourcePathString + " to " + resourcePath);
- p.resourcePath = resourcePath;
- p.resourcePathString = resourcePath.toString();
+ p.resourcePath = new File(resourcePath);
+ p.resourcePathString = resourcePath;
}
// Update version code if needed
if (pkg.mVersionCode != p.versionCode) {
@@ -7432,4 +7433,110 @@ class PackageManagerService extends IPackageManager.Stub {
|| packageSettings.enabledComponents.contains(componentInfo.name));
}
}
+
+ // ------- apps on sdcard specific code -------
+ static final boolean DEBUG_SD_INSTALL = false;
+ final private String mSdEncryptKey = "none";
+
+ private MountService getMountService() {
+ return (MountService) ServiceManager.getService("mount");
+ }
+
+ private String createSdDir(File tmpPackageFile, String pkgName) {
+ // Create mount point via MountService
+ MountService mountService = getMountService();
+ long len = tmpPackageFile.length();
+ int mbLen = (int) (len/(1024*1024));
+ if ((len - (mbLen * 1024 * 1024)) > 0) {
+ mbLen++;
+ }
+ if (DEBUG_SD_INSTALL) Log.i(TAG, "mbLen="+mbLen);
+ String cachePath = null;
+ // Remove any pending destroy messages
+ mHandler.removeMessages(DESTROY_SD_CONTAINER, pkgName);
+ try {
+ cachePath = mountService.createSecureContainer(pkgName,
+ mbLen,
+ "vfat", mSdEncryptKey, Process.SYSTEM_UID);
+ if (DEBUG_SD_INSTALL) Log.i(TAG, "Trying to install " + pkgName + ", cachePath =" + cachePath);
+ return cachePath;
+ } catch(IllegalStateException e) {
+ Log.e(TAG, "Failed to create storage on sdcard with exception: " + e);
+ }
+ // TODO just fail here and let the user delete later on.
+ try {
+ mountService.destroySecureContainer(pkgName);
+ if (DEBUG_SD_INSTALL) Log.i(TAG, "Destroying cache for " + pkgName + ", cachePath =" + cachePath);
+ } catch(IllegalStateException e) {
+ Log.e(TAG, "Failed to destroy existing cache: " + e);
+ return null;
+ }
+ try {
+ cachePath = mountService.createSecureContainer(pkgName,
+ mbLen,
+ "vfat", mSdEncryptKey, Process.SYSTEM_UID);
+ if (DEBUG_SD_INSTALL) Log.i(TAG, "Trying to install again " + pkgName + ", cachePath =" + cachePath);
+ return cachePath;
+ } catch(IllegalStateException e) {
+ Log.e(TAG, "Failed to create storage on sdcard with exception: " + e);
+ return null;
+ }
+ }
+
+ private String mountSdDir(String pkgName, int ownerUid) {
+ try {
+ return getMountService().mountSecureContainer(pkgName, mSdEncryptKey, ownerUid);
+ } catch (IllegalStateException e) {
+ Log.i(TAG, "Failed to mount container for pkg : " + pkgName + " exception : " + e);
+ }
+ return null;
+ }
+
+ private String getSdDir(String pkgName) {
+ String cachePath = null;
+ try {
+ cachePath = getMountService().getSecureContainerPath(pkgName);
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Failed to retrieve secure container path for pkg : " + pkgName + " with exception " + e);
+ }
+ return cachePath;
+ }
+
+ private boolean finalizeSdDir(String pkgName) {
+ try {
+ getMountService().finalizeSecureContainer(pkgName);
+ return true;
+ } catch (IllegalStateException e) {
+ Log.i(TAG, "Failed to destroy container for pkg : " + pkgName);
+ return false;
+ }
+ }
+
+ 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;
+ } catch (IllegalStateException e) {
+ Log.i(TAG, "Failed to destroy container for pkg : " + pkgName);
+ return false;
+ }
+ }
+
+ private void sendDelayedDestroySdDir(String pkgName) {
+ if (mHandler.hasMessages(DESTROY_SD_CONTAINER, pkgName)) {
+ // Don't have to send message again
+ return;
+ }
+ Message msg = mHandler.obtainMessage(DESTROY_SD_CONTAINER, pkgName);
+ mHandler.sendMessageDelayed(msg, DESTROY_SD_CONTAINER_DELAY);
+ }
+
+ public void updateExternalMediaStatus(boolean mediaStatus) {
+ // TODO
+ }
}