Change new file installs to be cluster-based!

Now that all the other pieces are in place, we're ready to start
installing new file-based packages as a cluster (the new unified
directory-based layout).  This greatly simplifies the renaming
process.

Also add helper methods to ApplicationInfo to give a much clearer
mapping between it and internal field names, since we can't change
the public API.

Add recursive restorecon().

Bug: 14975160
Change-Id: I72a63c5ddbc594c2fec4a91dd59f73ef253fbfd7
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index bb90fd7..514b26e 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -410,10 +410,14 @@
      */
     public int largestWidthLimitDp = 0;
 
+    /** {@hide} */
+    public String scanSourceDir;
+    /** {@hide} */
+    public String scanPublicSourceDir;
+
     /**
      * Full path to the base APK for this application.
      */
-    // TODO: verify that nobody is doing codePath comparisons against this
     public String sourceDir;
 
     /**
@@ -779,11 +783,25 @@
             return true;
         }
     }
-    
+
     /**
      * @hide
      */
     @Override protected ApplicationInfo getApplicationInfo() {
         return this;
     }
+
+    /** {@hide} */ public void setCodePath(String codePath) { scanSourceDir = codePath; }
+    /** {@hide} */ public void setBaseCodePath(String baseCodePath) { sourceDir = baseCodePath; }
+    /** {@hide} */ public void setSplitCodePaths(String[] splitCodePaths) { splitSourceDirs = splitCodePaths; }
+    /** {@hide} */ public void setResourcePath(String resourcePath) { scanPublicSourceDir = resourcePath; }
+    /** {@hide} */ public void setBaseResourcePath(String baseResourcePath) { publicSourceDir = baseResourcePath; }
+    /** {@hide} */ public void setSplitResourcePaths(String[] splitResourcePaths) { splitPublicSourceDirs = splitResourcePaths; }
+
+    /** {@hide} */ public String getCodePath() { return scanSourceDir; }
+    /** {@hide} */ public String getBaseCodePath() { return sourceDir; }
+    /** {@hide} */ public String[] getSplitCodePaths() { return splitSourceDirs; }
+    /** {@hide} */ public String getResourcePath() { return scanPublicSourceDir; }
+    /** {@hide} */ public String getBaseResourcePath() { return publicSourceDir; }
+    /** {@hide} */ public String[] getSplitResourcePaths() { return splitSourceDirs; }
 }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index bb47124..7651aef 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -719,7 +719,12 @@
      * <p>
      * Note that this <em>does not</em> perform signature verification; that
      * must be done separately in {@link #collectCertificates(Package, int)}.
+     *
+     * @deprecated external callers should move to
+     *             {@link #parsePackage(File, int)}. Eventually this method will
+     *             be marked private.
      */
+    @Deprecated
     public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
         final Package pkg = parseBaseApk(apkFile, flags);
         if (pkg == null) {
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 4c34d46..fe47f5b 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -371,6 +371,8 @@
      * attacks.
      */
     public static boolean contains(File dir, File file) {
+        if (file == null) return false;
+
         String dirPath = dir.getAbsolutePath();
         String filePath = file.getAbsolutePath();
 
@@ -418,16 +420,27 @@
     }
 
     public static String rewriteAfterRename(File beforeDir, File afterDir, String path) {
+        if (path == null) return null;
         final File result = rewriteAfterRename(beforeDir, afterDir, new File(path));
         return (result != null) ? result.getAbsolutePath() : null;
     }
 
+    public static String[] rewriteAfterRename(File beforeDir, File afterDir, String[] paths) {
+        if (paths == null) return null;
+        final String[] result = new String[paths.length];
+        for (int i = 0; i < paths.length; i++) {
+            result[i] = rewriteAfterRename(beforeDir, afterDir, paths[i]);
+        }
+        return result;
+    }
+
     /**
      * Given a path under the "before" directory, rewrite it to live under the
      * "after" directory. For example, {@code /before/foo/bar.txt} would become
      * {@code /after/foo/bar.txt}.
      */
     public static File rewriteAfterRename(File beforeDir, File afterDir, File file) {
+        if (file == null) return null;
         if (contains(beforeDir, file)) {
             final String splice = file.getAbsolutePath().substring(
                     beforeDir.getAbsolutePath().length());
diff --git a/core/java/android/os/SELinux.java b/core/java/android/os/SELinux.java
index 71d12c6..84aa427 100644
--- a/core/java/android/os/SELinux.java
+++ b/core/java/android/os/SELinux.java
@@ -28,9 +28,15 @@
  * {@hide}
  */
 public class SELinux {
-
     private static final String TAG = "SELinux";
 
+    /** Keep in sync with ./external/libselinux/include/selinux/android.h */
+    private static final int SELINUX_ANDROID_RESTORECON_NOCHANGE = 1;
+    private static final int SELINUX_ANDROID_RESTORECON_VERBOSE = 2;
+    private static final int SELINUX_ANDROID_RESTORECON_RECURSE = 4;
+    private static final int SELINUX_ANDROID_RESTORECON_FORCE = 8;
+    private static final int SELINUX_ANDROID_RESTORECON_DATADATA = 16;
+
     /**
      * Determine whether SELinux is disabled or enabled.
      * @return a boolean indicating whether SELinux is enabled.
@@ -136,7 +142,7 @@
      */
     public static boolean restorecon(String pathname) throws NullPointerException {
         if (pathname == null) { throw new NullPointerException(); }
-        return native_restorecon(pathname);
+        return native_restorecon(pathname, 0);
     }
 
     /**
@@ -149,7 +155,7 @@
      * @param pathname The pathname of the file to be relabeled.
      * @return a boolean indicating whether the relabeling succeeded.
      */
-    private static native boolean native_restorecon(String pathname);
+    private static native boolean native_restorecon(String pathname, int flags);
 
     /**
      * Restores a file to its default SELinux security context.
@@ -164,10 +170,10 @@
      */
     public static boolean restorecon(File file) throws NullPointerException {
         try {
-            return native_restorecon(file.getCanonicalPath());
+            return native_restorecon(file.getCanonicalPath(), 0);
         } catch (IOException e) {
             Slog.e(TAG, "Error getting canonical path. Restorecon failed for " +
-                   file.getPath(), e);
+                    file.getPath(), e);
             return false;
         }
     }
@@ -180,14 +186,13 @@
      *
      * @return a boolean indicating whether the relabeling succeeded.
      */
-    public static boolean restoreconTree(File dir) {
-        final File[] files = dir.listFiles();
-        boolean success = true;
-        if (files != null) {
-            for (File file : files) {
-                success &= restorecon(file);
-            }
+    public static boolean restoreconRecursive(File file) {
+        try {
+            return native_restorecon(file.getCanonicalPath(), SELINUX_ANDROID_RESTORECON_RECURSE);
+        } catch (IOException e) {
+            Slog.e(TAG, "Error getting canonical path. Restorecon failed for " +
+                    file.getPath(), e);
+            return false;
         }
-        return success;
     }
 }
diff --git a/core/jni/android_os_SELinux.cpp b/core/jni/android_os_SELinux.cpp
index 26405b5..ffa569e 100644
--- a/core/jni/android_os_SELinux.cpp
+++ b/core/jni/android_os_SELinux.cpp
@@ -404,7 +404,7 @@
  * Returns: boolean: (true) file label successfully restored, (false) otherwise
  * Exceptions: none
  */
-static jboolean native_restorecon(JNIEnv *env, jobject, jstring pathnameStr) {
+static jboolean native_restorecon(JNIEnv *env, jobject, jstring pathnameStr, jint flags) {
     if (isSELinuxDisabled) {
         return true;
     }
@@ -415,7 +415,7 @@
         return false;
     }
 
-    int ret = selinux_android_restorecon(pathname.c_str(), 0);
+    int ret = selinux_android_restorecon(pathname.c_str(), flags);
     ALOGV("restorecon(%s) => %d", pathname.c_str(), ret);
     return (ret == 0);
 }
@@ -434,7 +434,7 @@
     { "getPidContext"            , "(I)Ljava/lang/String;"                        , (void*)getPidCon        },
     { "isSELinuxEnforced"        , "()Z"                                          , (void*)isSELinuxEnforced},
     { "isSELinuxEnabled"         , "()Z"                                          , (void*)isSELinuxEnabled },
-    { "native_restorecon"        , "(Ljava/lang/String;)Z"                        , (void*)native_restorecon},
+    { "native_restorecon"        , "(Ljava/lang/String;I)Z"                       , (void*)native_restorecon},
     { "setBooleanValue"          , "(Ljava/lang/String;Z)Z"                       , (void*)setBooleanValue  },
     { "setFileContext"           , "(Ljava/lang/String;Ljava/lang/String;)Z"      , (void*)setFileCon       },
     { "setFSCreateContext"       , "(Ljava/lang/String;)Z"                        , (void*)setFSCreateCon   },