In place split install native support

This change makes it possible for apps installed with the DONT_KILL flag
set to obtain access to newly installed native libraries while avoiding
double load of existing native libraries prior to the install when
loaded using System.loadLibrary.

Bug: 72047376
Test: manual - used sample app to verify availability of native libs
Change-Id: I331eaa48da1f8dee424584911317ec3fba92f873
diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt
index 35a2689..76aa853 100644
--- a/config/hiddenapi-light-greylist.txt
+++ b/config/hiddenapi-light-greylist.txt
@@ -3723,6 +3723,7 @@
 Ldalvik/system/DexPathList$NativeLibraryElement;->path:Ljava/io/File;
 Ldalvik/system/DexPathList;-><init>(Ljava/lang/ClassLoader;Ljava/lang/String;Ljava/lang/String;Ljava/io/File;)V
 Ldalvik/system/DexPathList;->addDexPath(Ljava/lang/String;Ljava/io/File;)V
+Ldalvik/system/DexPathList;->addNativePath(Ljava/util/Collection;)V
 Ldalvik/system/DexPathList;->definingContext:Ljava/lang/ClassLoader;
 Ldalvik/system/DexPathList;->dexElements:[Ldalvik/system/DexPathList$Element;
 Ldalvik/system/DexPathList;->loadDexFile(Ljava/io/File;Ljava/io/File;Ljava/lang/ClassLoader;[Ldalvik/system/DexPathList$Element;)Ldalvik/system/DexFile;
diff --git a/core/java/android/app/ApplicationLoaders.java b/core/java/android/app/ApplicationLoaders.java
index 7257044..0ed50f2 100644
--- a/core/java/android/app/ApplicationLoaders.java
+++ b/core/java/android/app/ApplicationLoaders.java
@@ -25,6 +25,8 @@
 
 import dalvik.system.PathClassLoader;
 
+import java.util.Collection;
+
 /** @hide */
 public class ApplicationLoaders {
     public static ApplicationLoaders getDefault() {
@@ -121,6 +123,17 @@
         baseDexClassLoader.addDexPath(dexPath);
     }
 
+    /**
+     * @hide
+     */
+    void addNative(ClassLoader classLoader, Collection<String> libPaths) {
+        if (!(classLoader instanceof PathClassLoader)) {
+            throw new IllegalStateException("class loader is not a PathClassLoader");
+        }
+        final PathClassLoader baseDexClassLoader = (PathClassLoader) classLoader;
+        baseDexClassLoader.addNativePath(libPaths);
+    }
+
     private final ArrayMap<String, ClassLoader> mLoaders = new ArrayMap<>();
 
     private static final ApplicationLoaders gApplicationLoaders = new ApplicationLoaders();
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index fc7d9a5..8c0cd23 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -90,6 +90,7 @@
 public final class LoadedApk {
     static final String TAG = "LoadedApk";
     static final boolean DEBUG = false;
+    private static final String PROPERTY_NAME_APPEND_NATIVE = "pi.append_native_lib_paths";
 
     private final ActivityThread mActivityThread;
     final String mPackageName;
@@ -723,6 +724,10 @@
             needToSetupJitProfiles = true;
         }
 
+        if (!libPaths.isEmpty() && SystemProperties.getBoolean(PROPERTY_NAME_APPEND_NATIVE, true)) {
+            ApplicationLoaders.getDefault().addNative(mClassLoader, libPaths);
+        }
+
         if (addedPaths != null && addedPaths.size() > 0) {
             final String add = TextUtils.join(File.pathSeparator, addedPaths);
             ApplicationLoaders.getDefault().addPath(mClassLoader, add);
diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java
index a1e6fd8..c388148 100644
--- a/core/java/com/android/internal/content/NativeLibraryHelper.java
+++ b/core/java/com/android/internal/content/NativeLibraryHelper.java
@@ -282,7 +282,10 @@
         }
     }
 
-    private static void createNativeLibrarySubdir(File path) throws IOException {
+    /**
+     * @hide
+     */
+    public static void createNativeLibrarySubdir(File path) throws IOException {
         if (!path.isDirectory()) {
             path.delete();
 
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index f7a0215..fa934fe 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -17,7 +17,6 @@
 package com.android.server.pm;
 
 import static android.content.pm.PackageManager.INSTALL_FAILED_ABORTED;
-import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_DEX_METADATA;
 import static android.content.pm.PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
 import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
@@ -74,6 +73,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.RevocableFileDescriptor;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
 import android.system.ErrnoException;
@@ -111,9 +111,9 @@
 import java.io.FileFilter;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.security.cert.CertificateException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -154,6 +154,8 @@
     private static final String ATTR_NAME = "name";
     private static final String ATTR_INSTALL_REASON = "installRason";
 
+    private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill";
+
     // TODO: enforce INSTALL_ALLOW_TEST
     // TODO: enforce INSTALL_ALLOW_DOWNGRADE
 
@@ -255,6 +257,8 @@
     @GuardedBy("mLock")
     private final List<String> mResolvedInstructionSets = new ArrayList<>();
     @GuardedBy("mLock")
+    private final List<String> mResolvedNativeLibPaths = new ArrayList<>();
+    @GuardedBy("mLock")
     private File mInheritedFilesBase;
 
     private static final FileFilter sAddedFilter = new FileFilter() {
@@ -971,6 +975,26 @@
                         final File oatDir = new File(toDir, "oat");
                         createOatDirs(mResolvedInstructionSets, oatDir);
                     }
+                    // pre-create lib dirs for linking if necessary
+                    if (!mResolvedNativeLibPaths.isEmpty()) {
+                        for (String libPath : mResolvedNativeLibPaths) {
+                            // "/lib/arm64" -> ["lib", "arm64"]
+                            final int splitIndex = libPath.lastIndexOf('/');
+                            if (splitIndex < 0 || splitIndex >= libPath.length() - 1) {
+                                Slog.e(TAG, "Skipping native library creation for linking due to "
+                                        + "invalid path: " + libPath);
+                                continue;
+                            }
+                            final String libDirPath = libPath.substring(1, splitIndex);
+                            final File libDir = new File(toDir, libDirPath);
+                            if (!libDir.exists()) {
+                                NativeLibraryHelper.createNativeLibrarySubdir(libDir);
+                            }
+                            final String archDirPath = libPath.substring(splitIndex + 1);
+                            NativeLibraryHelper.createNativeLibrarySubdir(
+                                    new File(libDir, archDirPath));
+                        }
+                    }
                     linkFiles(fromFiles, toDir, mInheritedFilesBase);
                 } else {
                     // TODO: this should delegate to DCS so the system process
@@ -988,7 +1012,7 @@
         computeProgressLocked(true);
 
         // Unpack native libraries
-        extractNativeLibraries(mResolvedStageDir, params.abiOverride);
+        extractNativeLibraries(mResolvedStageDir, params.abiOverride, mayInheritNativeLibs());
 
         // We've reached point of no return; call into PMS to install the stage.
         // Regardless of success or failure we always destroy session.
@@ -1028,6 +1052,17 @@
     }
 
     /**
+     * Returns true if the session should attempt to inherit any existing native libraries already
+     * extracted at the current install location. This is necessary to prevent double loading of
+     * native libraries already loaded by the running app.
+     */
+    private boolean mayInheritNativeLibs() {
+        return SystemProperties.getBoolean(PROPERTY_NAME_INHERIT_NATIVE, true) &&
+                params.mode == SessionParams.MODE_INHERIT_EXISTING &&
+                (params.installFlags & PackageManager.DONT_KILL_APP) != 0;
+    }
+
+    /**
      * Validate install by confirming that all application packages are have
      * consistent package name, version code, and signing certificates.
      * <p>
@@ -1249,6 +1284,38 @@
                     }
                 }
             }
+
+            // Inherit native libraries for DONT_KILL sessions.
+            if (mayInheritNativeLibs() && removeSplitList.isEmpty()) {
+                File[] libDirs = new File[]{
+                        new File(packageInstallDir, NativeLibraryHelper.LIB_DIR_NAME),
+                        new File(packageInstallDir, NativeLibraryHelper.LIB64_DIR_NAME)};
+                for (File libDir : libDirs) {
+                    if (!libDir.exists() || !libDir.isDirectory()) {
+                        continue;
+                    }
+                    final List<File> libDirsToInherit = new LinkedList<>();
+                    for (File archSubDir : libDir.listFiles()) {
+                        if (!archSubDir.isDirectory()) {
+                            continue;
+                        }
+                        String relLibPath;
+                        try {
+                            relLibPath = getRelativePath(archSubDir, packageInstallDir);
+                        } catch (IOException e) {
+                            Slog.e(TAG, "Skipping linking of native library directory!", e);
+                            // shouldn't be possible, but let's avoid inheriting these to be safe
+                            libDirsToInherit.clear();
+                            break;
+                        }
+                        if (!mResolvedNativeLibPaths.contains(relLibPath)) {
+                            mResolvedNativeLibPaths.add(relLibPath);
+                        }
+                        libDirsToInherit.addAll(Arrays.asList(archSubDir.listFiles()));
+                    }
+                    mResolvedInheritedFiles.addAll(libDirsToInherit);
+                }
+            }
         }
     }
 
@@ -1374,11 +1441,13 @@
         Slog.d(TAG, "Copied " + fromFiles.size() + " files into " + toDir);
     }
 
-    private static void extractNativeLibraries(File packageDir, String abiOverride)
+    private static void extractNativeLibraries(File packageDir, String abiOverride, boolean inherit)
             throws PackageManagerException {
-        // Always start from a clean slate
         final File libDir = new File(packageDir, NativeLibraryHelper.LIB_DIR_NAME);
-        NativeLibraryHelper.removeNativeBinariesFromDirLI(libDir, true);
+        if (!inherit) {
+            // Start from a clean slate
+            NativeLibraryHelper.removeNativeBinariesFromDirLI(libDir, true);
+        }
 
         NativeLibraryHelper.Handle handle = null;
         try {