[incremental] Manager, Storage and changes to Package Manager Service

Checking in basic high-level classes and their interactions with
package manager service.

Incremental Manager manages IncrementalStorage instances. Both are
backed by Incremental Service.

Package Manager Service uses Incremental Manager to handle file
operaions on Incremental File System, such as renaming and cleanup.

Also adding place holders for native library handling.

Test: builds
Change-Id: I78b64f795de480e109aeaffe61272a413a6b4be5
diff --git a/Android.bp b/Android.bp
index c74b458..483570a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -772,7 +772,7 @@
 filegroup {
     name: "incremental_aidl",
     srcs: [
-        "core/java/android/os/incremental/IIncrementalService.aidl",
+        "core/java/android/os/incremental/IIncrementalManager.aidl",
         "core/java/android/os/incremental/IIncrementalServiceProxy.aidl",
         "core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl",
         "core/java/android/os/incremental/IncrementalFileSystemControlParcel.aidl",
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 4a0fc66..c0dedbd 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3384,6 +3384,7 @@
             //@hide: SYSTEM_UPDATE_SERVICE,
             //@hide: TIME_DETECTOR_SERVICE,
             PERMISSION_SERVICE,
+            INCREMENTAL_SERVICE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ServiceName {}
@@ -4917,6 +4918,13 @@
     public static final String APP_INTEGRITY_SERVICE = "app_integrity";
 
     /**
+     * Use with {@link #getSystemService(String)} to retrieve an
+     * {@link android.os.incremental.IncrementalManager}.
+     * @hide
+     */
+    public static final String INCREMENTAL_SERVICE = "incremental";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
diff --git a/core/java/android/os/incremental/IIncrementalService.aidl b/core/java/android/os/incremental/IIncrementalManager.aidl
similarity index 77%
rename from core/java/android/os/incremental/IIncrementalService.aidl
rename to core/java/android/os/incremental/IIncrementalManager.aidl
index 1c832ca..d6446d4 100644
--- a/core/java/android/os/incremental/IIncrementalService.aidl
+++ b/core/java/android/os/incremental/IIncrementalManager.aidl
@@ -19,7 +19,7 @@
 import android.os.incremental.IncrementalDataLoaderParamsParcel;
 
 /** @hide */
-interface IIncrementalService {
+interface IIncrementalManager {
     /**
      * A set of flags for the |createMode| parameters when creating a new Incremental storage.
      */
@@ -53,6 +53,12 @@
     int makeDirectory(int storageId, in @utf8InCpp String pathUnderStorage);
 
     /**
+     * Recursively creates a directory under a storage. The target directory is specified by its relative path under the storage.
+     * All the parent directories of the target directory will be created if they do not exist already.
+     */
+    int makeDirectories(int storageId, in @utf8InCpp String pathUnderStorage);
+
+    /**
      * Creates a file under a storage, specifying its name, size and metadata.
      */
     int makeFile(int storageId, in @utf8InCpp String pathUnderStorage, long size, in byte[] metadata);
@@ -64,10 +70,12 @@
     int makeFileFromRange(int storageId, in @utf8InCpp String targetPathUnderStorage, in @utf8InCpp String sourcePathUnderStorage, long start, long end);
 
     /**
-     * Creates a hard link between two files in a storage.
-     * Both source and destination are specified by relative paths under storage.
+     * Creates a hard link between two files in two storage instances.
+     * Source and dest specified by parent storage IDs and their relative paths under the storage.
+     * The source and dest storage instances should be in the same fs mount.
+     * Note: destStorageId can be the same as sourceStorageId.
      */
-    int makeLink(int storageId, in @utf8InCpp String sourcePathUnderStorage, in @utf8InCpp String destPathUnderStorage);
+    int makeLink(int sourceStorageId, in @utf8InCpp String sourcePathUnderStorage, int destStorageId, in @utf8InCpp String destPathUnderStorage);
 
     /**
      * Deletes a hard link in a storage, specified by the relative path of the link target under storage.
@@ -85,12 +93,12 @@
     byte[] getFileMetadata(int storageId, in @utf8InCpp String pathUnderStorage);
 
     /**
-     * Returns the list of file paths under a storage.
-     */
-    @utf8InCpp String[] getFileList(int storageId);
-
-    /**
      * Starts loading data for a storage.
      */
     boolean startLoading(int storageId);
+
+    /**
+     * Deletes a storage given its ID. Deletes its bind mounts and unmount it. Stop its data loader.
+     */
+    void deleteStorage(int storageId);
 }
diff --git a/core/java/android/os/incremental/IncrementalDataLoaderParams.java b/core/java/android/os/incremental/IncrementalDataLoaderParams.java
new file mode 100644
index 0000000..701f1cc
--- /dev/null
+++ b/core/java/android/os/incremental/IncrementalDataLoaderParams.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.incremental;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.ParcelFileDescriptor;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * This class represents the parameters used to configure an Incremental Data Loader.
+ * Hide for now.
+ * @hide
+ */
+public class IncrementalDataLoaderParams {
+    @NonNull private final IncrementalDataLoaderParamsParcel mData;
+
+    public IncrementalDataLoaderParams(@NonNull String url, @NonNull String packageName,
+            @Nullable Map<String, ParcelFileDescriptor> namedFds) {
+        IncrementalDataLoaderParamsParcel data = new IncrementalDataLoaderParamsParcel();
+        data.staticArgs = url;
+        data.packageName = packageName;
+        if (namedFds == null || namedFds.isEmpty()) {
+            data.dynamicArgs = new NamedParcelFileDescriptor[0];
+        } else {
+            data.dynamicArgs = new NamedParcelFileDescriptor[namedFds.size()];
+            int i = 0;
+            for (Map.Entry<String, ParcelFileDescriptor> namedFd : namedFds.entrySet()) {
+                data.dynamicArgs[i] = new NamedParcelFileDescriptor();
+                data.dynamicArgs[i].name = namedFd.getKey();
+                data.dynamicArgs[i].fd = namedFd.getValue();
+                i += 1;
+            }
+        }
+        mData = data;
+    }
+
+    public IncrementalDataLoaderParams(@NonNull IncrementalDataLoaderParamsParcel data) {
+        mData = data;
+    }
+
+    /**
+     * @return static server's URL
+     */
+    public final @NonNull String getStaticArgs() {
+        return mData.staticArgs;
+    }
+
+    /**
+     * @return data loader's package name
+     */
+    public final @NonNull String getPackageName() {
+        return mData.packageName;
+    }
+
+    public final @NonNull IncrementalDataLoaderParamsParcel getData() {
+        return mData;
+    }
+
+    /**
+     * @return data loader's dynamic arguments such as file descriptors
+     */
+    public final @NonNull Map<String, ParcelFileDescriptor> getDynamicArgs() {
+        return Arrays.stream(mData.dynamicArgs).collect(
+            Collectors.toMap(p->p.name, p->p.fd));
+    }
+}
diff --git a/core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl b/core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl
index 50c28f0..cd988dc 100644
--- a/core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl
+++ b/core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl
@@ -23,7 +23,7 @@
  * @hide
  */
 parcelable IncrementalDataLoaderParamsParcel {
-    @utf8InCpp String staticUri;
     @utf8InCpp String packageName;
+    @utf8InCpp String staticArgs;
     NamedParcelFileDescriptor[] dynamicArgs;
 }
diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java
new file mode 100644
index 0000000..5aabf86
--- /dev/null
+++ b/core/java/android/os/incremental/IncrementalManager.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.incremental;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/**
+ * Provides operations to open or create an IncrementalStorage, using IIncrementalManager service.
+ * Example Usage:
+ *
+ * <blockquote><pre>
+ * IncrementalManager manager = (IncrementalManager) getSystemService(Context.INCREMENTAL_MANAGER);
+ * IncrementalStorage storage = manager.openStorage("/path/to/incremental/dir");
+ * </pre></blockquote>
+ *
+ * @hide
+ */
+@SystemService(Context.INCREMENTAL_SERVICE)
+public class IncrementalManager {
+    private static final String TAG = "IncrementalManager";
+    private final IIncrementalManager mService;
+    @GuardedBy("mStorages")
+    private final SparseArray<IncrementalStorage> mStorages = new SparseArray<>();
+
+    public static final int CREATE_MODE_TEMPORARY_BIND =
+            IIncrementalManager.CREATE_MODE_TEMPORARY_BIND;
+    public static final int CREATE_MODE_PERMANENT_BIND =
+            IIncrementalManager.CREATE_MODE_PERMANENT_BIND;
+    public static final int CREATE_MODE_CREATE =
+            IIncrementalManager.CREATE_MODE_CREATE;
+    public static final int CREATE_MODE_OPEN_EXISTING =
+            IIncrementalManager.CREATE_MODE_OPEN_EXISTING;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CREATE_MODE_"}, value = {
+            CREATE_MODE_TEMPORARY_BIND,
+            CREATE_MODE_PERMANENT_BIND,
+            CREATE_MODE_CREATE,
+            CREATE_MODE_OPEN_EXISTING,
+    })
+    public @interface CreateMode {
+    }
+
+    public IncrementalManager(@NonNull IIncrementalManager is) {
+        mService = is;
+    }
+
+    /**
+     * Returns a storage object given a storage ID.
+     *
+     * @param storageId The storage ID to identify the storage object.
+     * @return IncrementalStorage object corresponding to storage ID.
+     */
+    @Nullable
+    public IncrementalStorage getStorage(int storageId) {
+        synchronized (mStorages) {
+            return mStorages.get(storageId);
+        }
+    }
+
+    /**
+     * Opens or create an Incremental File System mounted directory and returns an
+     * IncrementalStorage object.
+     *
+     * @param path                Absolute path to mount Incremental File System on.
+     * @param params              IncrementalDataLoaderParams object to configure data loading.
+     * @param createMode          Mode for opening an old Incremental File System mount or
+     *                            creating a new mount.
+     * @param autoStartDataLoader Set true to immediately start data loader after creating storage.
+     * @return IncrementalStorage object corresponding to the mounted directory.
+     */
+    @Nullable
+    public IncrementalStorage createStorage(@NonNull String path,
+            @NonNull IncrementalDataLoaderParams params, @CreateMode int createMode,
+            boolean autoStartDataLoader) {
+        try {
+            final int id = mService.createStorage(path, params.getData(), createMode);
+            if (id < 0) {
+                return null;
+            }
+            final IncrementalStorage storage = new IncrementalStorage(mService, id);
+            synchronized (mStorages) {
+                mStorages.put(id, storage);
+            }
+            if (autoStartDataLoader) {
+                storage.startLoading();
+            }
+            return storage;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Opens an existing Incremental File System mounted directory and returns an
+     * IncrementalStorage object.
+     *
+     * @param path Absolute target path that Incremental File System has been mounted on.
+     * @return IncrementalStorage object corresponding to the mounted directory.
+     */
+    @Nullable
+    public IncrementalStorage openStorage(@NonNull String path) {
+        try {
+            final int id = mService.openStorage(path);
+            if (id < 0) {
+                return null;
+            }
+            final IncrementalStorage storage = new IncrementalStorage(mService, id);
+            synchronized (mStorages) {
+                mStorages.put(id, storage);
+            }
+            return storage;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Opens or creates an IncrementalStorage that is linked to another IncrementalStorage.
+     *
+     * @return IncrementalStorage object corresponding to the linked storage.
+     */
+    @Nullable
+    public IncrementalStorage createStorage(@NonNull String path,
+            @NonNull IncrementalStorage linkedStorage, @CreateMode int createMode) {
+        try {
+            final int id = mService.createLinkedStorage(path, linkedStorage.getId(), createMode);
+            if (id < 0) {
+                return null;
+            }
+            final IncrementalStorage storage = new IncrementalStorage(mService, id);
+            synchronized (mStorages) {
+                mStorages.put(id, storage);
+            }
+            return storage;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Iterates through path parents to find the base dir of an Incremental Storage.
+     *
+     * @param file Target file to search storage for.
+     * @return Absolute path which is a bind-mount point of Incremental File System.
+     */
+    private Path getStoragePathForFile(File file) {
+        File currentPath = new File(file.getParent());
+        while (currentPath.getParent() != null) {
+            IncrementalStorage storage = openStorage(currentPath.getAbsolutePath());
+            if (storage != null) {
+                return currentPath.toPath();
+            }
+            currentPath = new File(currentPath.getParent());
+        }
+        return null;
+    }
+
+    /**
+     * Renames an Incremental path to a new path. If source path is a file, make a link from the old
+     * Incremental file to the new one. If source path is a dir, unbind old dir from Incremental
+     * Storage and bind the new one.
+     * <ol>
+     *     <li> For renaming a dir, dest dir will be created if not exists, and does not need to
+     *          be on the same Incremental storage as the source. </li>
+     *     <li> For renaming a file, dest file must be on the same Incremental storage as source.
+     *     </li>
+     * </ol>
+     *
+     * @param sourcePath   Absolute path to the source. Should be the same type as the destPath
+     *                     (file or dir). Expected to already exist and is an Incremental path.
+     * @param destPath     Absolute path to the destination.
+     * @throws IllegalArgumentException when 1) source does not exist,
+     *                     or 2) source and dest type mismatch (one is file and the other is dir),
+     *                     or 3) source path is not on Incremental File System,
+     * @throws IOException when 1) cannot find the root path of the Incremental storage of source,
+     *                     or 2) cannot retrieve the Incremental storage instance of the source,
+     *                     or 3) renaming a file, but dest is not on the same Incremental Storage,
+     *                     or 4) renaming a dir, dest dir does not exist but fails to be created.
+     *
+     * TODO(b/136132412): add unit tests
+     */
+    public void rename(@NonNull String sourcePath, @NonNull String destPath) throws IOException {
+        final File source = new File(sourcePath);
+        final File dest = new File(destPath);
+        if (!source.exists()) {
+            throw new IllegalArgumentException("Path not exist: " + sourcePath);
+        }
+        if (dest.exists()) {
+            throw new IllegalArgumentException("Target path already exists: " + destPath);
+        }
+        if (source.isDirectory() && dest.exists() && dest.isFile()) {
+            throw new IllegalArgumentException(
+                    "Trying to rename a dir but destination is a file: " + destPath);
+        }
+        if (source.isFile() && dest.exists() && dest.isDirectory()) {
+            throw new IllegalArgumentException(
+                    "Trying to rename a file but destination is a dir: " + destPath);
+        }
+        if (!isIncrementalPath(sourcePath)) {
+            throw new IllegalArgumentException("Not an Incremental path: " + sourcePath);
+        }
+
+        Path storagePath = Paths.get(sourcePath);
+        if (source.isFile()) {
+            storagePath = getStoragePathForFile(source);
+        }
+        if (storagePath == null || storagePath.toAbsolutePath() == null) {
+            throw new IOException("Invalid source storage path for: " + sourcePath);
+        }
+        final IncrementalStorage storage = openStorage(storagePath.toAbsolutePath().toString());
+        if (storage == null) {
+            throw new IOException("Failed to retrieve storage from Incremental Service.");
+        }
+        if (source.isFile()) {
+            renameFile(storage, storagePath, source, dest);
+        } else {
+            renameDir(storage, storagePath, source, dest);
+        }
+    }
+
+    private void renameFile(IncrementalStorage storage, Path storagePath,
+            File source, File dest) throws IOException {
+        Path sourcePath = source.toPath();
+        Path destPath = dest.toPath();
+        if (!sourcePath.startsWith(storagePath)) {
+            throw new IOException("Path: " + source.getAbsolutePath() + " is not on storage at: "
+                    + storagePath.toString());
+        }
+        if (!destPath.startsWith(storagePath)) {
+            throw new IOException("Path: " + dest.getAbsolutePath() + " is not on storage at: "
+                    + storagePath.toString());
+        }
+        final Path sourceRelativePath = storagePath.relativize(sourcePath);
+        final Path destRelativePath = storagePath.relativize(destPath);
+        storage.moveFile(sourceRelativePath.toString(), destRelativePath.toString());
+
+    }
+
+    private void renameDir(IncrementalStorage storage, Path storagePath,
+            File source, File dest) throws IOException {
+        Path destPath = dest.toPath();
+        boolean usedMkdir = false;
+        try {
+            Os.mkdir(dest.getAbsolutePath(), 0755);
+            usedMkdir = true;
+        } catch (ErrnoException e) {
+            // Traditional mkdir fails but maybe we can create it on Incremental File System if
+            // the dest path is on the same Incremental storage as the source.
+            if (destPath.startsWith(storagePath)) {
+                storage.makeDirectories(storagePath.relativize(destPath).toString());
+            } else {
+                throw new IOException("Failed to create directory: " + dest.getAbsolutePath(), e);
+            }
+        }
+        try {
+            storage.moveDir(source.getAbsolutePath(), dest.getAbsolutePath());
+        } catch (Exception ex) {
+            if (usedMkdir) {
+                try {
+                    Os.remove(dest.getAbsolutePath());
+                } catch (ErrnoException ignored) {
+                }
+            }
+            throw new IOException(
+                    "Failed to move " + source.getAbsolutePath() + " to " + dest.getAbsolutePath());
+        }
+    }
+
+    /**
+     * Closes a storage specified by the absolute path. If the path is not Incremental, do nothing.
+     * Unbinds the target dir and deletes the corresponding storage instance.
+     */
+    public void closeStorage(@NonNull String path) {
+        try {
+            final int id = mService.openStorage(path);
+            if (id < 0) {
+                return;
+            }
+            mService.deleteStorage(id);
+            synchronized (mStorages) {
+                mStorages.remove(id);
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Checks if path is mounted on Incremental File System.
+     */
+    public static boolean isIncrementalPath(@NonNull String path) {
+        // TODO(b/136132412): implement native method
+        return false;
+    }
+}
diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java
new file mode 100644
index 0000000..2bf89ed
--- /dev/null
+++ b/core/java/android/os/incremental/IncrementalStorage.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.incremental;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.RemoteException;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Provides operations on an Incremental File System directory, using IncrementalService. Example
+ * usage:
+ *
+ * <blockquote><pre>
+ * IncrementalManager manager = (IncrementalManager) getSystemService(Context.INCREMENTAL_MANAGER);
+ * IncrementalStorage storage = manager.openStorage("/path/to/incremental/dir");
+ * storage.makeDirectory("subdir");
+ * </pre></blockquote>
+ *
+ * @hide
+ */
+public final class IncrementalStorage {
+    private static final String TAG = "IncrementalStorage";
+    private final int mId;
+    private final IIncrementalManager mService;
+
+
+    public IncrementalStorage(@NonNull IIncrementalManager is, int id) {
+        mService = is;
+        mId = id;
+    }
+
+    public int getId() {
+        return mId;
+    }
+
+    /**
+     * Temporarily bind-mounts the current storage directory to a target directory. The bind-mount
+     * will NOT be preserved between device reboots.
+     *
+     * @param targetPath Absolute path to the target directory.
+     */
+    public void bind(@NonNull String targetPath) throws IOException {
+        bind("", targetPath);
+    }
+
+    /**
+     * Temporarily bind-mounts a subdir under the current storage directory to a target directory.
+     * The bind-mount will NOT be preserved between device reboots.
+     *
+     * @param sourcePathUnderStorage Source path as a relative path under current storage
+     *                               directory.
+     * @param targetPath             Absolute path to the target directory.
+     */
+    public void bind(@NonNull String sourcePathUnderStorage, @NonNull String targetPath)
+            throws IOException {
+        try {
+            int res = mService.makeBindMount(mId, sourcePathUnderStorage, targetPath,
+                    IIncrementalManager.BIND_TEMPORARY);
+            if (res < 0) {
+                throw new IOException("bind() failed with errno " + -res);
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+
+    /**
+     * Permanently bind-mounts the current storage directory to a target directory. The bind-mount
+     * WILL be preserved between device reboots.
+     *
+     * @param targetPath Absolute path to the target directory.
+     */
+    public void bindPermanent(@NonNull String targetPath) throws IOException {
+        bindPermanent("", targetPath);
+    }
+
+    /**
+     * Permanently bind-mounts a subdir under the current storage directory to a target directory.
+     * The bind-mount WILL be preserved between device reboots.
+     *
+     * @param sourcePathUnderStorage Relative path under the current storage directory.
+     * @param targetPath             Absolute path to the target directory.
+     */
+    public void bindPermanent(@NonNull String sourcePathUnderStorage, @NonNull String targetPath)
+            throws IOException {
+        try {
+            int res = mService.makeBindMount(mId, sourcePathUnderStorage, targetPath,
+                    IIncrementalManager.BIND_PERMANENT);
+            if (res < 0) {
+                throw new IOException("bind() permanent failed with errno " + -res);
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Unbinds a bind mount.
+     *
+     * @param targetPath Absolute path to the target directory.
+     */
+    public void unBind(@NonNull String targetPath) throws IOException {
+        try {
+            int res = mService.deleteBindMount(mId, targetPath);
+            if (res < 0) {
+                throw new IOException("unbind() failed with errno " + -res);
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Creates a sub-directory under the current storage directory.
+     *
+     * @param pathUnderStorage Relative path of the sub-directory, e.g., "subdir"
+     */
+    public void makeDirectory(@NonNull String pathUnderStorage) throws IOException {
+        try {
+            int res = mService.makeDirectory(mId, pathUnderStorage);
+            if (res < 0) {
+                throw new IOException("makeDirectory() failed with errno " + -res);
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Creates a sub-directory under the current storage directory. If its parent dirs do not exist,
+     * create the parent dirs as well.
+     *
+     * @param pathUnderStorage Relative path of the sub-directory, e.g., "subdir/subsubdir"
+     */
+    public void makeDirectories(@NonNull String pathUnderStorage) throws IOException {
+        try {
+            int res = mService.makeDirectories(mId, pathUnderStorage);
+            if (res < 0) {
+                throw new IOException("makeDirectory() failed with errno " + -res);
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Creates a file under the current storage directory.
+     *
+     * @param pathUnderStorage Relative path of the new file.
+     * @param size             Size of the new file in bytes.
+     * @param metadata         Metadata bytes.
+     */
+    public void makeFile(@NonNull String pathUnderStorage, long size,
+            @Nullable byte[] metadata) throws IOException {
+        try {
+            int res = mService.makeFile(mId, pathUnderStorage, size, metadata);
+            if (res < 0) {
+                throw new IOException("makeFile() failed with errno " + -res);
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Creates a file in Incremental storage. The content of the file is mapped from a range inside
+     * a source file in the same storage.
+     *
+     * @param destRelativePath   Target relative path under storage.
+     * @param sourceRelativePath Source relative path under storage.
+     * @param rangeStart         Starting offset (in bytes) in the source file.
+     * @param rangeEnd           Ending offset (in bytes) in the source file.
+     */
+    public void makeFileFromRange(@NonNull String destRelativePath,
+            @NonNull String sourceRelativePath, long rangeStart, long rangeEnd) throws IOException {
+        try {
+            int res = mService.makeFileFromRange(mId, destRelativePath, sourceRelativePath,
+                    rangeStart, rangeEnd);
+            if (res < 0) {
+                throw new IOException("makeFileFromRange() failed, errno " + -res);
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Creates a hard-link between two paths, which can be under different storages but in the same
+     * Incremental File System.
+     *
+     * @param sourcePathUnderStorage The relative path of the source.
+     * @param destStorage            The target storage of the link target.
+     * @param destPathUnderStorage   The relative path of the target.
+     */
+    public void makeLink(@NonNull String sourcePathUnderStorage, IncrementalStorage destStorage,
+            @NonNull String destPathUnderStorage) throws IOException {
+        try {
+            int res = mService.makeLink(mId, sourcePathUnderStorage, destStorage.getId(),
+                    destPathUnderStorage);
+            if (res < 0) {
+                throw new IOException("makeLink() failed with errno " + -res);
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Deletes a hard-link under the current storage directory.
+     *
+     * @param pathUnderStorage The relative path of the target.
+     */
+    public void unlink(@NonNull String pathUnderStorage) throws IOException {
+        try {
+            int res = mService.unlink(mId, pathUnderStorage);
+            if (res < 0) {
+                throw new IOException("unlink() failed with errno " + -res);
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Rename an old file name to a new file name under the current storage directory.
+     *
+     * @param sourcePathUnderStorage Old file path as a relative path to the storage directory.
+     * @param destPathUnderStorage   New file path as a relative path to the storage directory.
+     */
+    public void moveFile(@NonNull String sourcePathUnderStorage,
+            @NonNull String destPathUnderStorage) throws IOException {
+        try {
+            int res = mService.makeLink(mId, sourcePathUnderStorage, mId, destPathUnderStorage);
+            if (res < 0) {
+                throw new IOException("moveFile() failed at makeLink(), errno " + -res);
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        try {
+            mService.unlink(mId, sourcePathUnderStorage);
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Move a directory, which is bind-mounted to a given storage, to a new location. The bind mount
+     * will be persistent between reboots.
+     *
+     * @param sourcePath The old path of the directory as an absolute path.
+     * @param destPath   The new path of the directory as an absolute path, expected to already
+     *                   exist.
+     */
+    public void moveDir(@NonNull String sourcePath, @NonNull String destPath) throws IOException {
+        if (!new File(destPath).exists()) {
+            throw new IOException("moveDir() requires that destination dir already exists.");
+        }
+        try {
+            int res = mService.makeBindMount(mId, "", destPath, IIncrementalManager.BIND_PERMANENT);
+            if (res < 0) {
+                throw new IOException("moveDir() failed at making bind mount, errno " + -res);
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        try {
+            mService.deleteBindMount(mId, sourcePath);
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Checks whether a file under the current storage directory is fully loaded.
+     *
+     * @param pathUnderStorage The relative path of the file.
+     * @return True if the file is fully loaded.
+     */
+    public boolean isFileFullyLoaded(@NonNull String pathUnderStorage) {
+        return isFileRangeLoaded(pathUnderStorage, 0, -1);
+    }
+
+    /**
+     * Checks whether a range in a file if loaded.
+     *
+     * @param pathUnderStorage The relative path of the file.
+     * @param start            The starting offset of the range.
+     * @param end              The ending offset of the range.
+     * @return True if the file is fully loaded.
+     */
+    public boolean isFileRangeLoaded(@NonNull String pathUnderStorage, long start, long end) {
+        try {
+            return mService.isFileRangeLoaded(mId, pathUnderStorage, start, end);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+            return false;
+        }
+    }
+
+    /**
+     * Returns the metadata object of an IncFs File.
+     *
+     * @param pathUnderStorage The relative path of the file.
+     * @return Byte array that contains metadata bytes.
+     */
+    @Nullable
+    public byte[] getFileMetadata(@NonNull String pathUnderStorage) {
+        try {
+            return mService.getFileMetadata(mId, pathUnderStorage);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+            return null;
+        }
+    }
+
+    /**
+     * Informs the data loader service associated with the current storage to start data loader
+     *
+     * @return True if data loader is successfully started.
+     */
+    public boolean startLoading() {
+        try {
+            return mService.startLoading(mId);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+            return false;
+        }
+    }
+}
diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java
index fee8345..0847fbd 100644
--- a/core/java/com/android/internal/content/NativeLibraryHelper.java
+++ b/core/java/com/android/internal/content/NativeLibraryHelper.java
@@ -33,7 +33,6 @@
 import android.content.pm.PackageParser.PackageParserException;
 import android.os.Build;
 import android.os.SELinux;
-import android.os.SystemProperties;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.Slog;
@@ -444,6 +443,24 @@
         return sum;
     }
 
+     /**
+     * Configure the native library files managed by Incremental Service. Makes sure Incremental
+     * Service will create native library directories and set up native library binary files in the
+     * same structure as they are in non-incremental installations.
+     *
+     * @param pkg The package to be installed, including all the APK files.
+     * @param handle The pointer to an zip archive.
+     * @param libraryRoot The root directory of the native library files, e.g., lib/
+     * @param abiList The list of ABIs that are supported by the current device.
+     * @param useIsaSubdir Whether or not to set up a sub dir for the ISA.
+     * @return ABI code if installation succeeds or error code if installation fails.
+     */
+    public static int configureNativeBinariesForSupportedAbi(Package pkg, Handle handle,
+            File libraryRoot, String[] abiList, boolean useIsaSubdir) {
+        // TODO(b/136132412): Implement this.
+        return -1;
+    }
+
     // We don't care about the other return values for now.
     private static final int BITCODE_PRESENT = 1;
 
diff --git a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
index 259200b..7e47800 100644
--- a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
+++ b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
@@ -19,6 +19,7 @@
 import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
 import static android.content.pm.PackageParser.isApkFile;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+import static android.os.incremental.IncrementalManager.isIncrementalPath;
 
 import static com.android.internal.content.NativeLibraryHelper.LIB64_DIR_NAME;
 import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
@@ -309,6 +310,7 @@
 
         final String nativeLibraryRootStr = initialLibraryPaths.nativeLibraryRootDir;
         final boolean useIsaSpecificSubdirs = initialLibraryPaths.nativeLibraryRootRequiresIsa;
+        final boolean onIncremental = isIncrementalPath(pkg.codePath);
 
         String primaryCpuAbi = null;
         String secondaryCpuAbi = null;
@@ -341,10 +343,18 @@
                 int abi64 = PackageManager.NO_NATIVE_LIBRARIES;
                 if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
                     if (extractLibs) {
-                        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries");
-                        abi32 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
-                                nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS,
-                                useIsaSpecificSubdirs);
+                        if (onIncremental) {
+                            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER,
+                                    "incrementalNativeBinaries");
+                            abi32 = NativeLibraryHelper.configureNativeBinariesForSupportedAbi(pkg,
+                                    handle, nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS,
+                                    useIsaSpecificSubdirs);
+                        } else {
+                            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries");
+                            abi32 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
+                                    nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS,
+                                    useIsaSpecificSubdirs);
+                        }
                     } else {
                         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "findSupportedAbi");
                         abi32 = NativeLibraryHelper.findSupportedAbi(
@@ -364,10 +374,18 @@
 
                 if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
                     if (extractLibs) {
-                        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries");
-                        abi64 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
-                                nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS,
-                                useIsaSpecificSubdirs);
+                        if (onIncremental) {
+                            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER,
+                                    "incrementalNativeBinaries");
+                            abi64 = NativeLibraryHelper.configureNativeBinariesForSupportedAbi(pkg,
+                                    handle, nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS,
+                                    useIsaSpecificSubdirs);
+                        } else {
+                            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries");
+                            abi64 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
+                                    nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS,
+                                    useIsaSpecificSubdirs);
+                        }
                     } else {
                         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "findSupportedAbi");
                         abi64 = NativeLibraryHelper.findSupportedAbi(
@@ -418,9 +436,15 @@
 
                 final int copyRet;
                 if (extractLibs) {
-                    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries");
-                    copyRet = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
-                            nativeLibraryRoot, abiList, useIsaSpecificSubdirs);
+                    if (onIncremental) {
+                        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "incrementalNativeBinaries");
+                        copyRet = NativeLibraryHelper.configureNativeBinariesForSupportedAbi(pkg,
+                                handle, nativeLibraryRoot, abiList, useIsaSpecificSubdirs);
+                    } else {
+                        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries");
+                        copyRet = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
+                                nativeLibraryRoot, abiList, useIsaSpecificSubdirs);
+                    }
                 } else {
                     Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "findSupportedAbi");
                     copyRet = NativeLibraryHelper.findSupportedAbi(handle, abiList);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 0e075b1..b7b54e9 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -87,6 +87,7 @@
 import static android.content.pm.PackageManager.RESTRICTION_NONE;
 import static android.content.pm.PackageParser.isApkFile;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+import static android.os.incremental.IncrementalManager.isIncrementalPath;
 import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
 import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
 import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
@@ -226,6 +227,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.UserManagerInternal;
+import android.os.incremental.IncrementalManager;
 import android.os.storage.DiskInfo;
 import android.os.storage.IStorageManager;
 import android.os.storage.StorageEventListener;
@@ -1074,6 +1076,8 @@
 
     private Future<?> mPrepareAppDataFuture;
 
+    private final IncrementalManager mIncrementalManager;
+
     private static class IFVerificationParams {
         PackageParser.Package pkg;
         boolean replacing;
@@ -2509,6 +2513,8 @@
         mPermissionManager = injector.getPermissionManagerServiceInternal();
         mSettings = injector.getSettings();
         mPermissionManagerService = (IPermissionManager) ServiceManager.getService("permissionmgr");
+        mIncrementalManager =
+                (IncrementalManager) mContext.getSystemService(Context.INCREMENTAL_SERVICE);
 
         // CHECKSTYLE:ON IndentationCheck
         t.traceEnd();
@@ -8798,6 +8804,7 @@
         // Full APK verification can be skipped during certificate collection, only if the file is
         // in verified partition, or can be verified on access (when apk verity is enabled). In both
         // cases, only data in Signing Block is verified instead of the whole file.
+        // TODO(b/136132412): skip for Incremental installation
         final boolean skipVerify = scanSystemPartition
                 || (forceCollect && canSkipForcedPackageVerification(pkg));
         collectCertificatesLI(pkgSetting, pkg, forceCollect, skipVerify);
@@ -14675,9 +14682,16 @@
             final File afterCodeFile = getNextCodePath(targetDir, pkg.packageName);
 
             if (DEBUG_INSTALL) Slog.d(TAG, "Renaming " + beforeCodeFile + " to " + afterCodeFile);
+            final boolean onIncremental = mIncrementalManager != null
+                    && isIncrementalPath(beforeCodeFile.getAbsolutePath());
             try {
-                Os.rename(beforeCodeFile.getAbsolutePath(), afterCodeFile.getAbsolutePath());
-            } catch (ErrnoException e) {
+                if (onIncremental) {
+                    mIncrementalManager.rename(beforeCodeFile.getAbsolutePath(),
+                            afterCodeFile.getAbsolutePath());
+                } else {
+                    Os.rename(beforeCodeFile.getAbsolutePath(), afterCodeFile.getAbsolutePath());
+                }
+            } catch (IOException | ErrnoException e) {
                 Slog.w(TAG, "Failed to rename", e);
                 return false;
             }
@@ -14737,6 +14751,11 @@
                 return false;
             }
 
+            String codePath = codeFile.getAbsolutePath();
+            if (mIncrementalManager != null && isIncrementalPath(codePath)) {
+                mIncrementalManager.closeStorage(codePath);
+            }
+
             removeCodePathLI(codeFile);
 
             if (resourceFile != null && !FileUtils.contains(codeFile, resourceFile)) {
@@ -15928,6 +15947,8 @@
                             & PackageManagerService.SCAN_AS_INSTANT_APP) != 0);
             final PackageParser.Package pkg = reconciledPkg.pkgSetting.pkg;
             final String packageName = pkg.packageName;
+            final boolean onIncremental = mIncrementalManager != null
+                    && isIncrementalPath(pkg.codePath);
             prepareAppDataAfterInstallLIF(pkg);
             if (reconciledPkg.prepareResult.clearCodeCache) {
                 clearAppDataLIF(pkg, UserHandle.USER_ALL, FLAG_STORAGE_DE | FLAG_STORAGE_CE
@@ -15958,6 +15979,7 @@
             // We only need to dexopt if the package meets ALL of the following conditions:
             //   1) it is not an instant app or if it is then dexopt is enabled via gservices.
             //   2) it is not debuggable.
+            //   3) it is not on Incremental File System.
             //
             // Note that we do not dexopt instant apps by default. dexopt can take some time to
             // complete, so we skip this step during installation. Instead, we'll take extra time
@@ -15968,7 +15990,8 @@
             final boolean performDexopt =
                     (!instantApp || Global.getInt(mContext.getContentResolver(),
                     Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0)
-                    && ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0);
+                    && ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0)
+                    && (!onIncremental);
 
             if (performDexopt) {
                 // Compile the layout resources.
@@ -16200,6 +16223,7 @@
             if (args.signingDetails != PackageParser.SigningDetails.UNKNOWN) {
                 pkg.setSigningDetails(args.signingDetails);
             } else {
+                // TODO(b/136132412): skip for Incremental installation
                 PackageParser.collectCertificates(pkg, false /* skipVerify */);
             }
         } catch (PackageParserException e) {