Always derive native library paths at runtime.

Over time, we've unpacked native libraries at various places with
respect to their source APK.  Persisting this path in PackageSettings
has caused more pain recently with the switch to supporting multiArch
and cluster installs.

This change switches us to always derive the native library paths at
runtime based on the type of install.  This also ensures that
transitioning between a bundled system app and an upgraded system
app will always build the right path.

We still persist the last generated path into PackageSettings to make
cleanup at uninstall time easier.

Bug: 16208505, 16206748, 16212206
Change-Id: Ieb82a424ca4a92b5674983453c50ba4b695abfb0
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 4939fb6..b93bbe0 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -491,18 +491,23 @@
     public String nativeLibraryDir;
 
     /**
-     * The path under the apps data directory we store unpacked libraries. For
-     * new installs, we create subdirectories under legacyNativeLibraryDir that are
-     * architecture specific. For legacy installs, the shared libraries are
-     * placed directly under this path.
+     * The root path where unpacked native libraries are stored.
+     * <p>
+     * When {@link #nativeLibraryRootRequiresIsa} is set, the libraries are
+     * placed in ISA-specific subdirectories under this path, otherwise the
+     * libraries are placed directly at this path.
      *
-     * For "legacy" installs {@code nativeLibraryDir} will be equal to this path.
-     * For newer installs, it will be derived based on the codePath and the primary
-     * cpu abi.
-     *
-     * @hide.
+     * @hide
      */
-    public String legacyNativeLibraryDir;
+    public String nativeLibraryRootDir;
+
+    /**
+     * Flag indicating that ISA must be appended to
+     * {@link #nativeLibraryRootDir} to be useful.
+     *
+     * @hide
+     */
+    public boolean nativeLibraryRootRequiresIsa;
 
     /**
      * The primary ABI that this application requires, This is inferred from the ABIs
@@ -683,7 +688,8 @@
         splitSourceDirs = orig.splitSourceDirs;
         splitPublicSourceDirs = orig.splitPublicSourceDirs;
         nativeLibraryDir = orig.nativeLibraryDir;
-        legacyNativeLibraryDir = orig.legacyNativeLibraryDir;
+        nativeLibraryRootDir = orig.nativeLibraryRootDir;
+        nativeLibraryRootRequiresIsa = orig.nativeLibraryRootRequiresIsa;
         primaryCpuAbi = orig.primaryCpuAbi;
         secondaryCpuAbi = orig.secondaryCpuAbi;
         apkRoot = orig.apkRoot;
@@ -730,7 +736,8 @@
         dest.writeStringArray(splitSourceDirs);
         dest.writeStringArray(splitPublicSourceDirs);
         dest.writeString(nativeLibraryDir);
-        dest.writeString(legacyNativeLibraryDir);
+        dest.writeString(nativeLibraryRootDir);
+        dest.writeInt(nativeLibraryRootRequiresIsa ? 1 : 0);
         dest.writeString(primaryCpuAbi);
         dest.writeString(secondaryCpuAbi);
         dest.writeString(apkRoot);
@@ -776,7 +783,8 @@
         splitSourceDirs = source.readStringArray();
         splitPublicSourceDirs = source.readStringArray();
         nativeLibraryDir = source.readString();
-        legacyNativeLibraryDir = source.readString();
+        nativeLibraryRootDir = source.readString();
+        nativeLibraryRootRequiresIsa = source.readInt() != 0;
         primaryCpuAbi = source.readString();
         secondaryCpuAbi = source.readString();
         apkRoot = source.readString();
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index b5da4cd..0b6a926 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -333,7 +333,7 @@
     }
 
     public static final boolean isApkFile(File file) {
-        return file.isFile() && file.getName().endsWith(".apk");
+        return file.getName().endsWith(".apk");
     }
 
     /*
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index dea07ee..d8f4f78 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -5061,7 +5061,7 @@
             // Just create the setting, don't add it yet. For already existing packages
             // the PkgSetting exists already and doesn't have to be created.
             pkgSetting = mSettings.getPackageLPw(pkg, origPackage, realName, suid, destCodeFile,
-                    destResourceFile, pkg.applicationInfo.legacyNativeLibraryDir,
+                    destResourceFile, pkg.applicationInfo.nativeLibraryRootDir,
                     pkg.applicationInfo.primaryCpuAbi,
                     pkg.applicationInfo.secondaryCpuAbi,
                     pkg.applicationInfo.flags, user, false);
@@ -5287,7 +5287,7 @@
                             + pkg.applicationInfo.uid + "/fs_"
                             + currentUid;
                         pkg.applicationInfo.nativeLibraryDir = pkg.applicationInfo.dataDir;
-                        pkg.applicationInfo.legacyNativeLibraryDir = pkg.applicationInfo.dataDir;
+                        pkg.applicationInfo.nativeLibraryRootDir = pkg.applicationInfo.dataDir;
                         String msg = "Package " + pkg.packageName
                                 + " has mismatched uid: "
                                 + currentUid + " on disk, "
@@ -5346,22 +5346,20 @@
             NativeLibraryHelper.removeNativeBinariesFromDirLI(
                     new File(codePath, LIB_DIR_NAME), false /* delete dirs */);
             setBundledAppAbisAndRoots(pkg, pkgSetting);
+            setNativeLibraryPaths(pkg);
         } else {
             // TODO: We can probably be smarter about this stuff. For installed apps,
             // we can calculate this information at install time once and for all. For
             // system apps, we can probably assume that this information doesn't change
             // after the first boot scan. As things stand, we do lots of unnecessary work.
 
+            // Give ourselves some initial paths; we'll come back for another
+            // pass once we've determined ABI below.
+            setNativeLibraryPaths(pkg);
+
             final boolean isAsec = isForwardLocked(pkg) || isExternal(pkg);
-            final String nativeLibraryRootStr;
-            final boolean useIsaSpecificSubdirs;
-            if (pkg.applicationInfo.legacyNativeLibraryDir != null) {
-                nativeLibraryRootStr = pkg.applicationInfo.legacyNativeLibraryDir;
-                useIsaSpecificSubdirs = false;
-            } else {
-                nativeLibraryRootStr = new File(pkg.codePath, LIB_DIR_NAME).getAbsolutePath();
-                useIsaSpecificSubdirs = true;
-            }
+            final String nativeLibraryRootStr = pkg.applicationInfo.nativeLibraryRootDir;
+            final boolean useIsaSpecificSubdirs = pkg.applicationInfo.nativeLibraryRootRequiresIsa;
 
             NativeLibraryHelper.Handle handle = null;
             try {
@@ -5467,6 +5465,10 @@
                 IoUtils.closeQuietly(handle);
             }
 
+            // Now that we've calculated the ABIs and determined if it's an internal app,
+            // we will go ahead and populate the nativeLibraryPath.
+            setNativeLibraryPaths(pkg);
+
             if (DEBUG_INSTALL) Slog.i(TAG, "Linking native library dir for " + path);
             final int[] userIds = sUserManager.getUserIds();
             synchronized (mInstallLock) {
@@ -5475,14 +5477,7 @@
                 // this symlink for 64 bit libraries.
                 if (pkg.applicationInfo.primaryCpuAbi != null &&
                         !VMRuntime.is64BitAbi(pkg.applicationInfo.primaryCpuAbi)) {
-                    final String nativeLibPath;
-                    if (pkg.applicationInfo.legacyNativeLibraryDir != null) {
-                        nativeLibPath = pkg.applicationInfo.legacyNativeLibraryDir;
-                    } else {
-                        nativeLibPath = new File(nativeLibraryRootStr,
-                                VMRuntime.getInstructionSet(pkg.applicationInfo.primaryCpuAbi)).getAbsolutePath();
-                    }
-
+                    final String nativeLibPath = pkg.applicationInfo.nativeLibraryDir;
                     for (int userId : userIds) {
                         if (mInstaller.linkNativeLibraryDirectory(pkg.packageName, nativeLibPath, userId) < 0) {
                             Slog.w(TAG, "Failed linking native library dir (user=" + userId
@@ -5498,19 +5493,20 @@
             pkgSetting.secondaryCpuAbiString = pkg.applicationInfo.secondaryCpuAbi;
         }
 
+        Slog.d(TAG, "Resolved nativeLibraryRoot for " + pkg.applicationInfo.packageName
+                + " to root=" + pkg.applicationInfo.nativeLibraryRootDir + ", isa="
+                + pkg.applicationInfo.nativeLibraryRootRequiresIsa);
+
+        // Push the derived path down into PackageSettings so we know what to
+        // clean up at uninstall time.
+        pkgSetting.legacyNativeLibraryPathString = pkg.applicationInfo.nativeLibraryRootDir;
+
         if (DEBUG_ABI_SELECTION) {
             Log.d(TAG, "Abis for package[" + pkg.packageName + "] are" +
                     " primary=" + pkg.applicationInfo.primaryCpuAbi +
                     " secondary=" + pkg.applicationInfo.secondaryCpuAbi);
         }
 
-        // Check if we have a legacy native library path, use it if we do.
-        pkg.applicationInfo.legacyNativeLibraryDir = pkgSetting.legacyNativeLibraryPathString;
-
-        // Now that we've calculated the ABIs and determined if it's an internal app,
-        // we will go ahead and populate the nativeLibraryPath.
-        populateDefaultNativeLibraryPath(pkg, pkg.applicationInfo);
-
         if ((scanMode&SCAN_BOOTING) == 0 && pkgSetting.sharedUser != null) {
             // We don't do this here during boot because we can do it all
             // at once after scanning all existing packages.
@@ -6161,49 +6157,66 @@
         return codeRoot.getPath();
     }
 
-    private void populateDefaultNativeLibraryPath(PackageParser.Package pkg,
-                                                  ApplicationInfo info) {
-        if (info.legacyNativeLibraryDir != null) {
-            // Not a cluster install.
-            if (DEBUG_ABI_SELECTION) {
-                Log.i(TAG, "Set nativeLibraryDir [non_cluster] for: " + pkg.packageName +
-                        " to " + info.legacyNativeLibraryDir);
-            }
-            info.nativeLibraryDir = info.legacyNativeLibraryDir;
-        } else if (info.primaryCpuAbi != null) {
-            final boolean is64Bit = VMRuntime.is64BitAbi(info.primaryCpuAbi);
-            if (info.apkRoot != null) {
-                // This is a bundled system app so choose the path based on the ABI.
-                // if it's a 64 bit abi, use lib64 otherwise use lib32. Note that this
-                // is just the default path.
-                final String apkName = deriveCodePathName(pkg.applicationInfo.getCodePath());
-                final String libDir = is64Bit ? LIB64_DIR_NAME : LIB_DIR_NAME;
-                info.nativeLibraryDir = (new File(info.apkRoot, new File(libDir, apkName).getAbsolutePath()))
+    /**
+     * Derive and set the location of native libraries for the given package,
+     * which varies depending on where and how the package was installed.
+     */
+    private void setNativeLibraryPaths(PackageParser.Package pkg) {
+        final ApplicationInfo info = pkg.applicationInfo;
+        final String codePath = pkg.codePath;
+        final File codeFile = new File(codePath);
+
+        final boolean bundledApp = isSystemApp(info) && !isUpdatedSystemApp(info);
+        final boolean asecApp = isForwardLocked(info) || isExternal(info);
+
+        info.nativeLibraryRootDir = null;
+        info.nativeLibraryRootRequiresIsa = false;
+        info.nativeLibraryDir = null;
+
+        if (bundledApp) {
+            // Monolithic bundled install
+            // TODO: support cluster bundled installs?
+
+            final boolean is64Bit = (info.primaryCpuAbi != null)
+                    && VMRuntime.is64BitAbi(info.primaryCpuAbi);
+
+            // This is a bundled system app so choose the path based on the ABI.
+            // if it's a 64 bit abi, use lib64 otherwise use lib32. Note that this
+            // is just the default path.
+            final String apkName = deriveCodePathName(codePath);
+            final String libDir = is64Bit ? LIB64_DIR_NAME : LIB_DIR_NAME;
+            info.nativeLibraryRootDir = Environment.buildPath(new File(info.apkRoot), libDir,
+                    apkName).getAbsolutePath();
+            info.nativeLibraryRootRequiresIsa = false;
+
+        } else if (isApkFile(codeFile)) {
+            // Monolithic install
+            if (asecApp) {
+                info.nativeLibraryRootDir = new File(codeFile.getParentFile(), LIB_DIR_NAME)
                         .getAbsolutePath();
-
-                if (DEBUG_ABI_SELECTION) {
-                    Log.i(TAG, "Set nativeLibraryDir [system] for: " + pkg.packageName +
-                            " to " + info.nativeLibraryDir);
-                }
+                info.nativeLibraryRootRequiresIsa = false;
             } else {
-                // Cluster install. legacyNativeLibraryDir == null && primaryCpuAbi = null
-                // implies this must be a cluster package.
-                final String codePath = pkg.codePath;
-                final File libPath = new File(new File(codePath, LIB_DIR_NAME),
-                        VMRuntime.getInstructionSet(info.primaryCpuAbi));
-                info.nativeLibraryDir = libPath.getAbsolutePath();
-
-                if (DEBUG_ABI_SELECTION) {
-                    Log.i(TAG, "Set nativeLibraryDir [cluster] for: " + pkg.packageName +
-                            " to " + info.nativeLibraryDir);
-                }
+                final String apkName = deriveCodePathName(codePath);
+                info.nativeLibraryRootDir = new File(mAppLib32InstallDir, apkName)
+                        .getAbsolutePath();
+                info.nativeLibraryRootRequiresIsa = false;
             }
         } else {
-            if (DEBUG_ABI_SELECTION) {
-                Log.i(TAG, "Setting nativeLibraryDir to null for: " + pkg.packageName);
-            }
+            // Cluster install
+            info.nativeLibraryRootDir = new File(codeFile, LIB_DIR_NAME).getAbsolutePath();
+            info.nativeLibraryRootRequiresIsa = true;
+        }
 
-            info.nativeLibraryDir = null;
+        if (info.nativeLibraryRootRequiresIsa) {
+            if (info.primaryCpuAbi != null) {
+                info.nativeLibraryDir = new File(info.nativeLibraryRootDir,
+                        VMRuntime.getInstructionSet(info.primaryCpuAbi)).getAbsolutePath();
+            } else {
+                Slog.w(TAG, "Package " + info.packageName
+                        + " missing ABI; unable to derive nativeLibraryDir");
+            }
+        } else {
+            info.nativeLibraryDir = info.nativeLibraryRootDir;
         }
     }
 
@@ -9124,13 +9137,13 @@
         }
 
         /** Existing install */
-        FileInstallArgs(String codePath, String resourcePath, String legacyNativeLibraryRoot,
+        FileInstallArgs(String codePath, String resourcePath, String legacyNativeLibraryPath,
                 String[] instructionSets, boolean isMultiArch) {
             super(null, false, null, 0, null, null, null, instructionSets, null, isMultiArch);
             this.codeFile = (codePath != null) ? new File(codePath) : null;
             this.resourceFile = (resourcePath != null) ? new File(resourcePath) : null;
-            this.legacyNativeLibraryPath = (legacyNativeLibraryRoot != null) ?
-                    new File(legacyNativeLibraryRoot) : null;
+            this.legacyNativeLibraryPath = (legacyNativeLibraryPath != null) ?
+                    new File(legacyNativeLibraryPath) : null;
         }
 
         /** New install from existing */
@@ -9314,8 +9327,6 @@
                 pkg.applicationInfo.setResourcePath(pkg.codePath);
                 pkg.applicationInfo.setBaseResourcePath(pkg.baseCodePath);
                 pkg.applicationInfo.setSplitResourcePaths(pkg.splitCodePaths);
-                // Null out the legacy native dir so we stop using it.
-                pkg.applicationInfo.legacyNativeLibraryDir = null;
 
                 return true;
             }
@@ -9600,9 +9611,6 @@
             pkg.applicationInfo.setResourcePath(getResourcePath());
             pkg.applicationInfo.setBaseResourcePath(getResourcePath());
             pkg.applicationInfo.setSplitResourcePaths(null);
-            // ASEC installs are considered "legacy" because we don't support
-            // multiarch on them yet, and use the old style paths on them.
-            pkg.applicationInfo.legacyNativeLibraryDir = legacyNativeLibraryDir;
 
             return true;
         }
@@ -10074,7 +10082,7 @@
                 res.removedInfo.args = createInstallArgsForExisting(0,
                         deletedPackage.applicationInfo.getCodePath(),
                         deletedPackage.applicationInfo.getResourcePath(),
-                        deletedPackage.applicationInfo.legacyNativeLibraryDir,
+                        deletedPackage.applicationInfo.nativeLibraryRootDir,
                         getAppDexInstructionSets(deletedPackage.applicationInfo),
                         isMultiArch(deletedPackage.applicationInfo));
             } else {
@@ -10384,6 +10392,9 @@
         return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0;
     }
 
+    private static boolean isForwardLocked(ApplicationInfo info) {
+        return (info.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0;
+    }
 
     private boolean isForwardLocked(PackageSetting ps) {
         return (ps.pkgFlags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0;
@@ -10405,6 +10416,10 @@
         return (ps.pkgFlags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
     }
 
+    private static boolean isExternal(ApplicationInfo info) {
+        return (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
+    }
+
     private static boolean isSystemApp(PackageParser.Package pkg) {
         return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
     }
@@ -10429,6 +10444,10 @@
         return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
     }
 
+    private static boolean isUpdatedSystemApp(ApplicationInfo info) {
+        return (info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+    }
+
     private int packageFlagsToInstallFlags(PackageSetting ps) {
         int installFlags = 0;
         if (isExternal(ps)) {
@@ -12850,7 +12869,7 @@
                 final boolean multiArch = isMultiArch(pkg.applicationInfo);
                 InstallArgs srcArgs = createInstallArgsForExisting(currFlags,
                         pkg.applicationInfo.getCodePath(), pkg.applicationInfo.getResourcePath(),
-                        pkg.applicationInfo.legacyNativeLibraryDir, instructionSets, multiArch);
+                        pkg.applicationInfo.nativeLibraryRootDir, instructionSets, multiArch);
                 MoveParams mp = new MoveParams(srcArgs, observer, newFlags, packageName,
                         instructionSets, pkg.applicationInfo.uid, user, multiArch);
                 msg.obj = mp;
@@ -12977,9 +12996,6 @@
                                         pkg.applicationInfo.setResourcePath(newResPath);
                                         pkg.applicationInfo.setBaseResourcePath(newResPath);
                                         pkg.applicationInfo.setSplitResourcePaths(null);
-                                        // Null out the legacy nativeLibraryDir so that we stop using it and
-                                        // always derive the codepath.
-                                        pkg.applicationInfo.legacyNativeLibraryDir = null;
 
                                         PackageSetting ps = (PackageSetting) pkg.mExtras;
                                         ps.codePath = new File(pkg.applicationInfo.getCodePath());
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 3390efe..e164f5f 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -57,10 +57,11 @@
     String resourcePathString;
 
     /**
-     * The path under which native libraries for legacy apps are unpacked.
-     * Will be set to {@code null} for newer installs, where the path can be
-     * derived from {@link #codePath} unambiguously.
+     * The path under which native libraries have been unpacked. This path is
+     * always derived at runtime, and is only stored here for cleanup when a
+     * package is uninstalled.
      */
+    @Deprecated
     String legacyNativeLibraryPathString;
 
     String primaryCpuAbiString;
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index bb900db..40561ba 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -82,6 +82,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.Map.Entry;
 
@@ -317,11 +318,11 @@
 
     PackageSetting getPackageLPw(PackageParser.Package pkg, PackageSetting origPackage,
             String realName, SharedUserSetting sharedUser, File codePath, File resourcePath,
-            String nativeLibraryRoot, String primaryCpuAbi, String secondaryCpuAbi, int pkgFlags,
+            String legacyNativeLibraryPathString, String primaryCpuAbi, String secondaryCpuAbi, int pkgFlags,
             UserHandle user, boolean add) {
         final String name = pkg.packageName;
         PackageSetting p = getPackageLPw(name, origPackage, realName, sharedUser, codePath,
-                resourcePath, nativeLibraryRoot, primaryCpuAbi, secondaryCpuAbi,
+                resourcePath, legacyNativeLibraryPathString, primaryCpuAbi, secondaryCpuAbi,
                 pkg.mVersionCode, pkgFlags, user, add, true /* allowInstall */);
         return p;
     }
@@ -689,24 +690,24 @@
         // pkg.mSetStopped = p.getStopped(userId);
         final String codePath = pkg.applicationInfo.getCodePath();
         final String resourcePath = pkg.applicationInfo.getResourcePath();
+        final String legacyNativeLibraryPath = pkg.applicationInfo.nativeLibraryRootDir;
         // Update code path if needed
-        if (!codePath.equalsIgnoreCase(p.codePathString)) {
+        if (!Objects.equals(codePath, p.codePathString)) {
             Slog.w(PackageManagerService.TAG, "Code path for pkg : " + p.pkg.packageName +
                     " changing from " + p.codePathString + " to " + codePath);
             p.codePath = new File(codePath);
             p.codePathString = codePath;
         }
         //Update resource path if needed
-        if (!resourcePath.equalsIgnoreCase(p.resourcePathString)) {
+        if (!Objects.equals(resourcePath, p.resourcePathString)) {
             Slog.w(PackageManagerService.TAG, "Resource path for pkg : " + p.pkg.packageName +
                     " changing from " + p.resourcePathString + " to " + resourcePath);
             p.resourcePath = new File(resourcePath);
             p.resourcePathString = resourcePath;
         }
         // Update the native library paths if needed
-        final String nativeLibraryRoot = pkg.applicationInfo.legacyNativeLibraryDir;
-        if (nativeLibraryRoot != null && !nativeLibraryRoot.equalsIgnoreCase(p.legacyNativeLibraryPathString)) {
-            p.legacyNativeLibraryPathString = nativeLibraryRoot;
+        if (!Objects.equals(legacyNativeLibraryPath, p.legacyNativeLibraryPathString)) {
+            p.legacyNativeLibraryPathString = legacyNativeLibraryPath;
         }
 
         // Update the required Cpu Abi