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;