Add code cache directory for apps.
This provides a directory where apps can cache compiled or optimized
code generated at runtime. The platform will delete all files in
this location on both app and platform upgrade.
Bug: 16187224
Change-Id: I641b21d841c436247f35ff235317e3a4ba520441
diff --git a/api/current.txt b/api/current.txt
index 5eea910..3537354 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -7062,6 +7062,7 @@
method public abstract android.content.res.AssetManager getAssets();
method public abstract java.io.File getCacheDir();
method public abstract java.lang.ClassLoader getClassLoader();
+ method public abstract java.io.File getCodeCacheDir();
method public abstract android.content.ContentResolver getContentResolver();
method public abstract java.io.File getDatabasePath(java.lang.String);
method public abstract java.io.File getDir(java.lang.String, int);
@@ -7233,6 +7234,7 @@
method public android.content.Context getBaseContext();
method public java.io.File getCacheDir();
method public java.lang.ClassLoader getClassLoader();
+ method public java.io.File getCodeCacheDir();
method public android.content.ContentResolver getContentResolver();
method public java.io.File getDatabasePath(java.lang.String);
method public java.io.File getDir(java.lang.String, int);
@@ -29374,6 +29376,7 @@
method public android.content.res.AssetManager getAssets();
method public java.io.File getCacheDir();
method public java.lang.ClassLoader getClassLoader();
+ method public java.io.File getCodeCacheDir();
method public android.content.ContentResolver getContentResolver();
method public java.io.File getDatabasePath(java.lang.String);
method public java.io.File getDir(java.lang.String, int);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index b9de220..e274f09 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -250,6 +250,8 @@
private File mNoBackupFilesDir;
@GuardedBy("mSync")
private File mCacheDir;
+ @GuardedBy("mSync")
+ private File mCodeCacheDir;
@GuardedBy("mSync")
private File[] mExternalObbDirs;
@@ -1055,6 +1057,16 @@
}
@Override
+ public File getCodeCacheDir() {
+ synchronized (mSync) {
+ if (mCodeCacheDir == null) {
+ mCodeCacheDir = new File(getDataDirFile(), "code_cache");
+ }
+ return createFilesDirLocked(mCodeCacheDir);
+ }
+ }
+
+ @Override
public File getExternalCacheDir() {
// Operates on primary external storage
return getExternalCacheDirs()[0];
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index a52cbdd..d068b1f 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -867,6 +867,22 @@
public abstract File getCacheDir();
/**
+ * Returns the absolute path to the application specific cache directory on
+ * the filesystem designed for storing cached code. The system will delete
+ * any files stored in this location both when your specific application is
+ * upgraded, and when the entire platform is upgraded.
+ * <p>
+ * This location is optimal for storing compiled or optimized code generated
+ * by your application at runtime.
+ * <p>
+ * Apps require no extra permissions to read or write to the returned path,
+ * since this path lives in their private storage.
+ *
+ * @return The path of the directory holding application code cache files.
+ */
+ public abstract File getCodeCacheDir();
+
+ /**
* Returns the absolute path to the directory on the primary external filesystem
* (that is somewhere on {@link android.os.Environment#getExternalStorageDirectory()
* Environment.getExternalStorageDirectory()} where the application can
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 13eed07..4e1c4a7 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -232,6 +232,11 @@
}
@Override
+ public File getCodeCacheDir() {
+ return mBase.getCodeCacheDir();
+ }
+
+ @Override
public File getExternalCacheDir() {
return mBase.getExternalCacheDir();
}
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 1193968..8b0a46d 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -309,6 +309,15 @@
return execute(builder.toString());
}
+ public int deleteCodeCacheFiles(String name, int userId) {
+ StringBuilder builder = new StringBuilder("rmcodecache");
+ builder.append(' ');
+ builder.append(name);
+ builder.append(' ');
+ builder.append(userId);
+ return execute(builder.toString());
+ }
+
public int createUserData(String name, int uid, int userId, String seinfo) {
StringBuilder builder = new StringBuilder("mkuserdata");
builder.append(' ');
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index a8732dd..3bc515f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1752,6 +1752,16 @@
mSettings.readDefaultPreferredAppsLPw(this, 0);
}
+ // If this is first boot after an OTA, and a normal boot, then
+ // we need to clear code cache directories.
+ if (!Build.FINGERPRINT.equals(mSettings.mFingerprint) && !onlyCore) {
+ Slog.i(TAG, "Build fingerprint changed; clearing code caches");
+ for (String pkgName : mSettings.mPackages.keySet()) {
+ deleteCodeCacheDirsLI(pkgName);
+ }
+ mSettings.mFingerprint = Build.FINGERPRINT;
+ }
+
// All the changes are done during package scanning.
mSettings.updateInternalDatabaseVersion();
@@ -4828,6 +4838,18 @@
return res;
}
+ private int deleteCodeCacheDirsLI(String packageName) {
+ int[] users = sUserManager.getUserIds();
+ int res = 0;
+ for (int user : users) {
+ int resInner = mInstaller.deleteCodeCacheFiles(packageName, user);
+ if (resInner < 0) {
+ res = resInner;
+ }
+ }
+ return res;
+ }
+
private void addSharedLibraryLPw(ArraySet<String> usesLibraryFiles, SharedLibraryEntry file,
PackageParser.Package changingLib) {
if (file.path != null) {
@@ -9976,6 +9998,10 @@
perUserInstalled[i] = ps != null ? ps.getInstalled(allUsers[i]) : false;
}
}
+
+ // Nuke any cached code
+ deleteCodeCacheDirsLI(pkgName);
+
boolean sysPkg = (isSystemApp(oldPackage));
if (sysPkg) {
replaceSystemPackageLI(oldPackage, pkg, parseFlags, scanMode,
@@ -10122,7 +10148,7 @@
res.removedInfo.args = null;
}
}
-
+
// Successfully disabled the old package. Now proceed with re-installation
res.returnCode = PackageManager.INSTALL_SUCCEEDED;
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
@@ -11264,6 +11290,12 @@
+ packageName + " u" + userId);
return false;
}
+ retCode = mInstaller.deleteCodeCacheFiles(packageName, userId);
+ if (retCode < 0) {
+ Slog.w(TAG, "Couldn't remove code cache files for package: "
+ + packageName + " u" + userId);
+ return false;
+ }
return true;
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 40561ba..a3fd1df 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -30,6 +30,7 @@
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.Environment;
import android.os.FileUtils;
import android.os.PatternMatcher;
@@ -184,6 +185,12 @@
int mInternalDatabaseVersion;
int mExternalDatabaseVersion;
+ /**
+ * Last known value of {@link Build#FINGERPRINT}. Used to determine when an
+ * system update has occurred, meaning we need to clear code caches.
+ */
+ String mFingerprint;
+
Boolean mReadExternalStorageEnforced;
/** Device identity for the purpose of package verification. */
@@ -1664,6 +1671,7 @@
serializer.startTag(null, "last-platform-version");
serializer.attribute(null, "internal", Integer.toString(mInternalSdkPlatform));
serializer.attribute(null, "external", Integer.toString(mExternalSdkPlatform));
+ serializer.attribute(null, "fingerprint", mFingerprint);
serializer.endTag(null, "last-platform-version");
serializer.startTag(null, "database-version");
@@ -2089,6 +2097,7 @@
PackageManagerService.reportSettingsProblem(Log.INFO,
"No settings file; creating initial state");
mInternalSdkPlatform = mExternalSdkPlatform = sdkVersion;
+ mFingerprint = Build.FINGERPRINT;
return false;
}
str = new FileInputStream(mSettingsFilename);
@@ -2181,6 +2190,7 @@
}
} catch (NumberFormatException e) {
}
+ mFingerprint = parser.getAttributeValue(null, "fingerprint");
} else if (tagName.equals("database-version")) {
mInternalDatabaseVersion = mExternalDatabaseVersion = 0;
try {
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index 2ebce9b..8a2732d 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -195,6 +195,11 @@
}
@Override
+ public File getCodeCacheDir() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public File getExternalCacheDir() {
throw new UnsupportedOperationException();
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index ca61ffb..70a7be8 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -1045,6 +1045,12 @@
}
@Override
+ public File getCodeCacheDir() {
+ // pass
+ return null;
+ }
+
+ @Override
public File getExternalCacheDir() {
// pass
return null;