Migrating Incremental* APIs to PackageManager APIs.

Step 2, merging Data Loader params.

Test: builds and flashes
Bug: b/136132412

Change-Id: I2102554316dadcdcb49790c133ece110c43c29b3
diff --git a/Android.bp b/Android.bp
index 536f688..742a70e5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -833,6 +833,7 @@
     name: "dataloader_aidl",
     srcs: [
         "core/java/android/content/pm/DataLoaderParamsParcel.aidl",
+        "core/java/android/content/pm/DataLoaderType.aidl",
         "core/java/android/content/pm/FileSystemControlParcel.aidl",
         "core/java/android/content/pm/IDataLoaderStatusListener.aidl",
         "core/java/android/content/pm/IPackageInstallerSessionFileSystemConnector.aidl",
diff --git a/api/system-current.txt b/api/system-current.txt
index 23dd652..dc27f22 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1934,10 +1934,12 @@
   }
 
   public class DataLoaderParams {
-    ctor public DataLoaderParams(@NonNull String, @NonNull String, @Nullable java.util.Map<java.lang.String,android.os.ParcelFileDescriptor>);
+    method @NonNull public static final android.content.pm.DataLoaderParams forIncremental(@NonNull android.content.ComponentName, @NonNull String, @Nullable java.util.Map<java.lang.String,android.os.ParcelFileDescriptor>);
+    method @NonNull public static final android.content.pm.DataLoaderParams forStreaming(@NonNull android.content.ComponentName, @NonNull String);
+    method @NonNull public final String getArguments();
+    method @NonNull public final android.content.ComponentName getComponentName();
     method @NonNull public final java.util.Map<java.lang.String,android.os.ParcelFileDescriptor> getDynamicArgs();
-    method @NonNull public final String getPackageName();
-    method @NonNull public final String getStaticArgs();
+    method @NonNull public final int getType();
   }
 
   public final class InstantAppInfo implements android.os.Parcelable {
@@ -2050,11 +2052,11 @@
   public static class PackageInstaller.SessionParams implements android.os.Parcelable {
     method @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE) public void setAllocateAggressive(boolean);
     method @Deprecated public void setAllowDowngrade(boolean);
+    method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setDataLoaderParams(@NonNull android.content.pm.DataLoaderParams);
     method public void setDontKillApp(boolean);
     method public void setEnableRollback(boolean);
     method public void setEnableRollback(boolean, int);
     method @RequiresPermission(android.Manifest.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS) public void setGrantedRuntimePermissions(String[]);
-    method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setIncrementalParams(@NonNull android.content.pm.DataLoaderParams);
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setInstallAsApex();
     method public void setInstallAsInstantApp(boolean);
     method public void setInstallAsVirtualPreload();
diff --git a/core/java/android/content/pm/DataLoaderParams.java b/core/java/android/content/pm/DataLoaderParams.java
index af4b99a..60d7bb3 100644
--- a/core/java/android/content/pm/DataLoaderParams.java
+++ b/core/java/android/content/pm/DataLoaderParams.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.content.ComponentName;
 import android.os.ParcelFileDescriptor;
 
 import java.util.Arrays;
@@ -26,7 +27,7 @@
 import java.util.stream.Collectors;
 
 /**
- * This class represents the parameters used to configure an Incremental Data Loader.
+ * This class represents the parameters used to configure a Data Loader.
  *
  * WARNING: This is a system API to aid internal development.
  * Use at your own risk. It will change or be removed without warning.
@@ -34,13 +35,41 @@
  */
 @SystemApi
 public class DataLoaderParams {
-    @NonNull private final DataLoaderParamsParcel mData;
+    @NonNull
+    private final DataLoaderParamsParcel mData;
 
-    public DataLoaderParams(@NonNull String url, @NonNull String packageName,
+    /**
+     * Creates and populates set of Data Loader parameters for Streaming installation.
+     *
+     * @param componentName Data Loader component supporting Streaming installation.
+     * @param arguments free form installation arguments
+     */
+    public static final @NonNull DataLoaderParams forStreaming(@NonNull ComponentName componentName,
+            @NonNull String arguments) {
+        return new DataLoaderParams(DataLoaderType.STREAMING, componentName, arguments, null);
+    }
+
+    /**
+     * Creates and populates set of Data Loader parameters for Incremental installation.
+     *
+     * @param componentName Data Loader component supporting Incremental installation.
+     * @param arguments free form installation arguments
+     * @param namedFds TODO(b/146080380) remove
+     */
+    public static final @NonNull DataLoaderParams forIncremental(
+            @NonNull ComponentName componentName, @NonNull String arguments,
             @Nullable Map<String, ParcelFileDescriptor> namedFds) {
+        return new DataLoaderParams(DataLoaderType.INCREMENTAL, componentName, arguments, namedFds);
+    }
+
+    /** @hide */
+    public DataLoaderParams(@NonNull @DataLoaderType int type, @NonNull ComponentName componentName,
+            @NonNull String arguments, @Nullable Map<String, ParcelFileDescriptor> namedFds) {
         DataLoaderParamsParcel data = new DataLoaderParamsParcel();
-        data.staticArgs = url;
-        data.packageName = packageName;
+        data.type = type;
+        data.packageName = componentName.getPackageName();
+        data.className = componentName.getClassName();
+        data.arguments = arguments;
         if (namedFds == null || namedFds.isEmpty()) {
             data.dynamicArgs = new NamedParcelFileDescriptor[0];
         } else {
@@ -56,39 +85,42 @@
         mData = data;
     }
 
-    /**
-     * @hide
-     */
-    public DataLoaderParams(@NonNull DataLoaderParamsParcel data) {
+    /** @hide */
+    DataLoaderParams(@NonNull DataLoaderParamsParcel 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;
-    }
-
-    /**
-     * @hide
-     */
+    /** @hide */
     public final @NonNull DataLoaderParamsParcel getData() {
         return mData;
     }
 
     /**
-     * @return data loader's dynamic arguments such as file descriptors
+     * @return data loader type
+     */
+    public final @NonNull @DataLoaderType int getType() {
+        return mData.type;
+    }
+
+    /**
+     * @return data loader's component name
+     */
+    public final @NonNull ComponentName getComponentName() {
+        return new ComponentName(mData.packageName, mData.className);
+    }
+
+    /**
+     * @return data loader's arguments
+     */
+    public final @NonNull String getArguments() {
+        return mData.arguments;
+    }
+
+    /**
+     * @return data loader's dynamic arguments such as file descriptors TODO: remove
      */
     public final @NonNull Map<String, ParcelFileDescriptor> getDynamicArgs() {
         return Arrays.stream(mData.dynamicArgs).collect(
-            Collectors.toMap(p->p.name, p->p.fd));
+                Collectors.toMap(p -> p.name, p -> p.fd));
     }
 }
diff --git a/core/java/android/content/pm/DataLoaderParamsParcel.aidl b/core/java/android/content/pm/DataLoaderParamsParcel.aidl
index 3316398..e05843b 100644
--- a/core/java/android/content/pm/DataLoaderParamsParcel.aidl
+++ b/core/java/android/content/pm/DataLoaderParamsParcel.aidl
@@ -16,6 +16,7 @@
 
 package android.content.pm;
 
+import android.content.pm.DataLoaderType;
 import android.content.pm.NamedParcelFileDescriptor;
 
 /**
@@ -23,7 +24,9 @@
  * @hide
  */
 parcelable DataLoaderParamsParcel {
+    DataLoaderType type;
     @utf8InCpp String packageName;
-    @utf8InCpp String staticArgs;
+    @utf8InCpp String className;
+    @utf8InCpp String arguments;
     NamedParcelFileDescriptor[] dynamicArgs;
 }
diff --git a/core/java/android/content/pm/DataLoaderType.aidl b/core/java/android/content/pm/DataLoaderType.aidl
new file mode 100644
index 0000000..7d726f5
--- /dev/null
+++ b/core/java/android/content/pm/DataLoaderType.aidl
@@ -0,0 +1,37 @@
+/*
+ * 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.content.pm;
+
+/**
+ * Types of Data Loader for an installation session.
+ * @hide
+ */
+@Backing(type="int")
+enum DataLoaderType {
+    /**
+    * Default value, legacy installation.
+    */
+    NONE = 0,
+    /**
+     * Streaming installation using data loader.
+     */
+    STREAMING = 1,
+    /**
+     * Streaming installation using Incremental FileSystem.
+     */
+    INCREMENTAL = 2,
+}
diff --git a/core/java/android/content/pm/IDataLoader.aidl b/core/java/android/content/pm/IDataLoader.aidl
index c65bd6a..b5baa93 100644
--- a/core/java/android/content/pm/IDataLoader.aidl
+++ b/core/java/android/content/pm/IDataLoader.aidl
@@ -27,7 +27,9 @@
  */
 oneway interface IDataLoader {
    void create(int id, in Bundle params, IDataLoaderStatusListener listener);
-   void start(in List<InstallationFile> fileInfos);
+   void start();
    void stop();
    void destroy();
+
+   void prepareImage(in List<InstallationFile> addedFiles, in List<String> removedFiles);
 }
diff --git a/core/java/android/content/pm/IDataLoaderStatusListener.aidl b/core/java/android/content/pm/IDataLoaderStatusListener.aidl
index a60d6ee..5011faa 100644
--- a/core/java/android/content/pm/IDataLoaderStatusListener.aidl
+++ b/core/java/android/content/pm/IDataLoaderStatusListener.aidl
@@ -22,13 +22,18 @@
  */
 oneway interface IDataLoaderStatusListener {
     /** Data loader status */
-    const int DATA_LOADER_READY = 0;
-    const int DATA_LOADER_NOT_READY = 1;
-    const int DATA_LOADER_RUNNING = 2;
+    const int DATA_LOADER_CREATED = 0;
+    const int DATA_LOADER_DESTROYED = 1;
+
+    const int DATA_LOADER_STARTED = 2;
     const int DATA_LOADER_STOPPED = 3;
-    const int DATA_LOADER_SLOW_CONNECTION = 4;
-    const int DATA_LOADER_NO_CONNECTION = 5;
-    const int DATA_LOADER_CONNECTION_OK = 6;
+
+    const int DATA_LOADER_IMAGE_READY = 4;
+    const int DATA_LOADER_IMAGE_NOT_READY = 5;
+
+    const int DATA_LOADER_SLOW_CONNECTION = 6;
+    const int DATA_LOADER_NO_CONNECTION = 7;
+    const int DATA_LOADER_CONNECTION_OK = 8;
 
     /** Data loader status callback */
     void onStatusChanged(in int dataLoaderId, in int status);
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 3d6d849..e4a0bc0 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1461,10 +1461,7 @@
         /** {@hide} */
         public long requiredInstalledVersionCode = PackageManager.VERSION_CODE_HIGHEST;
         /** {@hide} */
-        public DataLoaderParams incrementalParams;
-        /** TODO(b/146080380): add a class name to make it fully compatible with ComponentName.
-         * {@hide} */
-        public String dataLoaderPackageName;
+        public DataLoaderParams dataLoaderParams;
         /** {@hide} */
         public int rollbackDataPolicy = PackageManager.RollbackDataPolicy.RESTORE;
 
@@ -1503,10 +1500,8 @@
             DataLoaderParamsParcel dataLoaderParamsParcel = source.readParcelable(
                     DataLoaderParamsParcel.class.getClassLoader());
             if (dataLoaderParamsParcel != null) {
-                incrementalParams = new DataLoaderParams(
-                        dataLoaderParamsParcel);
+                dataLoaderParams = new DataLoaderParams(dataLoaderParamsParcel);
             }
-            dataLoaderPackageName = source.readString();
             rollbackDataPolicy = source.readInt();
         }
 
@@ -1531,8 +1526,7 @@
             ret.isMultiPackage = isMultiPackage;
             ret.isStaged = isStaged;
             ret.requiredInstalledVersionCode = requiredInstalledVersionCode;
-            ret.incrementalParams = incrementalParams;
-            ret.dataLoaderPackageName = dataLoaderPackageName;
+            ret.dataLoaderParams = dataLoaderParams;
             ret.rollbackDataPolicy = rollbackDataPolicy;
             return ret;
         }
@@ -1893,29 +1887,18 @@
         }
 
         /**
-         * Set Incremental data loader params.
+         * Set the data loader params for the session.
+         * This also switches installation into data provider mode and disallow direct writes into
+         * staging folder.
+         *
          * WARNING: This is a system API to aid internal development.
          * Use at your own risk. It will change or be removed without warning.
          * {@hide}
          */
         @SystemApi
         @RequiresPermission(Manifest.permission.INSTALL_PACKAGES)
-        public void setIncrementalParams(@NonNull DataLoaderParams incrementalParams) {
-            this.incrementalParams = incrementalParams;
-        }
-
-        /**
-         * Set the data provider params for the session.
-         * This also switches installation into callback mode and disallow direct writes into
-         * staging folder.
-         * TODO(b/146080380): unify dataprovider params with Incremental.
-         *
-         * @param dataLoaderPackageName name of the dataLoader package
-         * {@hide}
-         */
-        @RequiresPermission(Manifest.permission.INSTALL_PACKAGES)
-        public void setDataLoaderPackageName(String dataLoaderPackageName) {
-            this.dataLoaderPackageName = dataLoaderPackageName;
+        public void setDataLoaderParams(@NonNull DataLoaderParams dataLoaderParams) {
+            this.dataLoaderParams = dataLoaderParams;
         }
 
         /** {@hide} */
@@ -1938,7 +1921,7 @@
             pw.printPair("isMultiPackage", isMultiPackage);
             pw.printPair("isStaged", isStaged);
             pw.printPair("requiredInstalledVersionCode", requiredInstalledVersionCode);
-            pw.printPair("dataLoaderPackageName", dataLoaderPackageName);
+            pw.printPair("dataLoaderParams", dataLoaderParams);
             pw.printPair("rollbackDataPolicy", rollbackDataPolicy);
             pw.println();
         }
@@ -1969,12 +1952,11 @@
             dest.writeBoolean(isMultiPackage);
             dest.writeBoolean(isStaged);
             dest.writeLong(requiredInstalledVersionCode);
-            if (incrementalParams != null) {
-                dest.writeParcelable(incrementalParams.getData(), flags);
+            if (dataLoaderParams != null) {
+                dest.writeParcelable(dataLoaderParams.getData(), flags);
             } else {
                 dest.writeParcelable(null, flags);
             }
-            dest.writeString(dataLoaderPackageName);
             dest.writeInt(rollbackDataPolicy);
         }
 
diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java
index fb94fc9..2138d553 100644
--- a/core/java/android/os/incremental/IncrementalFileStorages.java
+++ b/core/java/android/os/incremental/IncrementalFileStorages.java
@@ -87,8 +87,8 @@
         mPackageName = packageName;
         mStageDir = stageDir;
         mIncrementalManager = incrementalManager;
-        if (dataLoaderParams.getPackageName().equals("local")) {
-            final String incrementalPath = dataLoaderParams.getStaticArgs();
+        if (dataLoaderParams.getComponentName().getPackageName().equals("local")) {
+            final String incrementalPath = dataLoaderParams.getArguments();
             mDefaultStorage = mIncrementalManager.openStorage(incrementalPath);
             mDefaultDir = incrementalPath;
             return;
diff --git a/core/java/android/service/dataloader/DataLoaderService.java b/core/java/android/service/dataloader/DataLoaderService.java
index 54a4fa6..75f252e 100644
--- a/core/java/android/service/dataloader/DataLoaderService.java
+++ b/core/java/android/service/dataloader/DataLoaderService.java
@@ -16,7 +16,6 @@
 
 package android.service.dataloader;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -27,19 +26,16 @@
 import android.content.pm.FileSystemControlParcel;
 import android.content.pm.IDataLoader;
 import android.content.pm.IDataLoaderStatusListener;
-import android.content.pm.IPackageInstallerSessionFileSystemConnector;
 import android.content.pm.InstallationFile;
 import android.content.pm.NamedParcelFileDescriptor;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
 import android.util.ExceptionUtils;
 import android.util.Slog;
 
 import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
+import java.util.Collection;
 import java.util.List;
 
 /**
@@ -55,88 +51,35 @@
  */
 @SystemApi
 public abstract class DataLoaderService extends Service {
-    private static final String TAG = "IncrementalDataLoaderService";
+    private static final String TAG = "DataLoaderService";
     private final DataLoaderBinderService mBinder = new DataLoaderBinderService();
 
-    /** @hide */
-    public static final int DATA_LOADER_READY =
-            IDataLoaderStatusListener.DATA_LOADER_READY;
-    /** @hide */
-    public static final int DATA_LOADER_NOT_READY =
-            IDataLoaderStatusListener.DATA_LOADER_NOT_READY;
-    /** @hide */
-    public static final int DATA_LOADER_RUNNING =
-            IDataLoaderStatusListener.DATA_LOADER_RUNNING;
-    /** @hide */
-    public static final int DATA_LOADER_STOPPED =
-            IDataLoaderStatusListener.DATA_LOADER_STOPPED;
-    /** @hide */
-    public static final int DATA_LOADER_SLOW_CONNECTION =
-            IDataLoaderStatusListener.DATA_LOADER_SLOW_CONNECTION;
-    /** @hide */
-    public static final int DATA_LOADER_NO_CONNECTION =
-            IDataLoaderStatusListener.DATA_LOADER_NO_CONNECTION;
-    /** @hide */
-    public static final int DATA_LOADER_CONNECTION_OK =
-            IDataLoaderStatusListener.DATA_LOADER_CONNECTION_OK;
-
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = {"DATA_LOADER_"}, value = {
-            DATA_LOADER_READY,
-            DATA_LOADER_NOT_READY,
-            DATA_LOADER_RUNNING,
-            DATA_LOADER_STOPPED,
-            DATA_LOADER_SLOW_CONNECTION,
-            DATA_LOADER_NO_CONNECTION,
-            DATA_LOADER_CONNECTION_OK
-    })
-    public @interface DataLoaderStatus {
-    }
-
     /**
-     * Managed DataLoader interface. Each instance corresponds to a single Incremental File System
-     * instance.
+     * Managed DataLoader interface. Each instance corresponds to a single installation session.
      * @hide
      */
-    public abstract static class DataLoader {
+    public interface DataLoader {
         /**
-         * A virtual constructor used to do simple initialization. Not ready to serve any data yet.
-         * All heavy-lifting has to be done in onStart.
+         * A virtual constructor.
          *
-         * @param params    Data loader configuration parameters.
-         * @param connector IncFS API wrapper.
-         * @param listener  Used for reporting internal state to IncrementalService.
+         * @param dataLoaderParams parameters set in the installation session
+         * @param connector FS API wrapper
          * @return True if initialization of a Data Loader was successful. False will be reported to
-         * IncrementalService and can cause an unmount of an IFS instance.
+         * PackageManager and fail the installation
          */
-        public abstract boolean onCreate(@NonNull DataLoaderParams params,
-                @NonNull FileSystemConnector connector,
-                @NonNull StatusListener listener);
+        boolean onCreate(@NonNull DataLoaderParams dataLoaderParams,
+                @NonNull FileSystemConnector connector);
 
         /**
-         * Start the data loader. After this method returns data loader is considered to be ready to
-         * receive callbacks from IFS, supply data via connector and send status updates via
-         * callbacks.
+         * Prepare installation image. After this method succeeds installer will validate the files
+         * and continue installation.
          *
-         * @return True if Data Loader was able to start. False will be reported to
-         * IncrementalService and can cause an unmount of an IFS instance.
+         * @param addedFiles   list of files created in this installation session.
+         * @param removedFiles list of files removed in this installation session.
+         * @return false if unable to create and populate all addedFiles.
          */
-        public abstract boolean onStart();
-
-        /**
-         * Stop the data loader. Use to stop any additional threads and free up resources. Data
-         * loader is not longer responsible for supplying data. Start/Stop pair can be called
-         * multiple times e.g. if IFS detects corruption and data needs to be re-loaded.
-         */
-        public abstract void onStop();
-
-        /**
-         * Virtual destructor. Use to cleanup all internal state. After this method returns, the
-         * data loader can no longer use connector or callbacks. For any additional operations with
-         * this instance of IFS a new DataLoader will be created using createDataLoader method.
-         */
-        public abstract void onDestroy();
+        boolean onPrepareImage(Collection<InstallationFile> addedFiles,
+                Collection<String> removedFiles);
     }
 
     /**
@@ -145,7 +88,9 @@
      * @return An instance of a DataLoader.
      * @hide
      */
-    public abstract @Nullable DataLoader onCreateDataLoader();
+    public @Nullable DataLoader onCreateDataLoader() {
+        return null;
+    }
 
     /**
      * @hide
@@ -160,148 +105,125 @@
         @Override
         public void create(int id, @NonNull Bundle options,
                 @NonNull IDataLoaderStatusListener listener)
-                    throws IllegalArgumentException, RuntimeException {
+                throws IllegalArgumentException, RuntimeException {
             mId = id;
-            final DataLoaderParamsParcel params =  options.getParcelable("params");
+            final DataLoaderParamsParcel params = options.getParcelable("params");
             if (params == null) {
-                throw new IllegalArgumentException("Must specify Incremental data loader params");
+                throw new IllegalArgumentException("Must specify data loader params");
             }
-            final FileSystemControlParcel control =
-                    options.getParcelable("control");
+            final FileSystemControlParcel control = options.getParcelable("control");
             if (control == null) {
-                throw new IllegalArgumentException("Must specify Incremental control parcel");
+                throw new IllegalArgumentException("Must specify control parcel");
             }
-            mStatusListener = listener;
             try {
                 if (!nativeCreateDataLoader(id, control, params, listener)) {
                     Slog.e(TAG, "Failed to create native loader for " + mId);
                 }
             } catch (Exception ex) {
+                Slog.e(TAG, "Failed to create native loader for " + mId, ex);
                 destroy();
                 throw new RuntimeException(ex);
             } finally {
                 // Closing FDs.
-                if (control.incremental.cmd != null) {
-                    try {
-                        control.incremental.cmd.close();
-                    } catch (IOException e) {
-                        Slog.e(TAG, "Failed to close IncFs CMD file descriptor " + e);
+                if (control.incremental != null) {
+                    if (control.incremental.cmd != null) {
+                        try {
+                            control.incremental.cmd.close();
+                        } catch (IOException e) {
+                            Slog.e(TAG, "Failed to close IncFs CMD file descriptor " + e);
+                        }
+                    }
+                    if (control.incremental.log != null) {
+                        try {
+                            control.incremental.log.close();
+                        } catch (IOException e) {
+                            Slog.e(TAG, "Failed to close IncFs LOG file descriptor " + e);
+                        }
                     }
                 }
-                if (control.incremental.log != null) {
-                    try {
-                        control.incremental.log.close();
-                    } catch (IOException e) {
-                        Slog.e(TAG, "Failed to close IncFs LOG file descriptor " + e);
-                    }
-                }
-                NamedParcelFileDescriptor[] fds = params.dynamicArgs;
-                for (NamedParcelFileDescriptor nfd : fds) {
-                    try {
-                        nfd.fd.close();
-                    } catch (IOException e) {
-                        Slog.e(TAG,
-                                "Failed to close DynamicArgs parcel file descriptor " + e);
+                if (params.dynamicArgs != null) {
+                    NamedParcelFileDescriptor[] fds = params.dynamicArgs;
+                    for (NamedParcelFileDescriptor nfd : fds) {
+                        try {
+                            nfd.fd.close();
+                        } catch (IOException e) {
+                            Slog.e(TAG, "Failed to close DynamicArgs parcel file descriptor " + e);
+                        }
                     }
                 }
             }
         }
 
         @Override
-        public void start(List<InstallationFile> fileInfos) {
+        public void start() {
             if (!nativeStartDataLoader(mId)) {
-                Slog.e(TAG, "Failed to start loader: loader not found for " + mId);
+                Slog.e(TAG, "Failed to start loader: " + mId);
             }
         }
 
         @Override
         public void stop() {
             if (!nativeStopDataLoader(mId)) {
-                Slog.w(TAG, "Failed to stop loader: loader not found for " + mId);
+                Slog.w(TAG, "Failed to stop loader: " + mId);
             }
         }
 
         @Override
         public void destroy() {
             if (!nativeDestroyDataLoader(mId)) {
-                Slog.w(TAG, "Failed to destroy loader: loader not found for " + mId);
+                Slog.w(TAG, "Failed to destroy loader: " + mId);
+            }
+        }
+
+        @Override
+        public void prepareImage(List<InstallationFile> addedFiles, List<String> removedFiles) {
+            if (!nativePrepareImage(mId, addedFiles, removedFiles)) {
+                Slog.w(TAG, "Failed to destroy loader: " + mId);
             }
         }
     }
 
     /**
-     *
      * Used by the DataLoaderService implementations.
      *
      * @hide
      */
     public static final class FileSystemConnector {
         /**
-         * Creates a wrapper for an installation session connector.
+         * Create a wrapper for a native instance.
+         *
          * @hide
          */
-        FileSystemConnector(IPackageInstallerSessionFileSystemConnector connector) {
-            mConnector = connector;
+        FileSystemConnector(long nativeInstance) {
+            mNativeInstance = nativeInstance;
         }
 
         /**
          * Write data to an installation file from an arbitrary FD.
          *
-         * @param name name of file previously added to the installation session.
-         * @param offsetBytes offset into the file to begin writing at, or 0 to
-         *            start at the beginning of the file.
-         * @param lengthBytes total size of the file being written, used to
-         *            preallocate the underlying disk space, or -1 if unknown.
-         *            The system may clear various caches as needed to allocate
-         *            this space.
-         * @param incomingFd FD to read bytes from.
-         * @throws IOException if trouble opening the file for writing, such as
-         *             lack of disk space or unavailable media.
+         * @param name        name of file previously added to the installation session.
+         * @param offsetBytes offset into the file to begin writing at, or 0 to start at the
+         *                    beginning of the file.
+         * @param lengthBytes total size of the file being written, used to preallocate the
+         *                    underlying disk space, or -1 if unknown. The system may clear various
+         *                    caches as needed to allocate this space.
+         * @param incomingFd  FD to read bytes from.
+         * @throws IOException if trouble opening the file for writing, such as lack of disk space
+         *                     or unavailable media.
          */
         public void writeData(String name, long offsetBytes, long lengthBytes,
                 ParcelFileDescriptor incomingFd) throws IOException {
             try {
-                mConnector.writeData(name, offsetBytes, lengthBytes, incomingFd);
+                nativeWriteData(mNativeInstance, name, offsetBytes, lengthBytes, incomingFd);
             } catch (RuntimeException e) {
                 ExceptionUtils.maybeUnwrapIOException(e);
                 throw e;
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
             }
         }
 
-        private final IPackageInstallerSessionFileSystemConnector mConnector;
-    }
-
-    /**
-     * Wrapper for native reporting DataLoader statuses.
-     * @hide
-     */
-    public static final class StatusListener {
-        /**
-         * Creates a wrapper for a native instance.
-         * @hide
-         */
-        StatusListener(long nativeInstance) {
-            mNativeInstance = nativeInstance;
-        }
-
-        /**
-         * Report the status of DataLoader. Used for system-wide notifications e.g., disabling
-         * applications which rely on this data loader to function properly.
-         *
-         * @param status status to report.
-         * @return True if status was reported successfully.
-         */
-        public boolean onStatusChanged(@DataLoaderStatus int status) {
-            return nativeReportStatus(mNativeInstance, status);
-        }
-
         private final long mNativeInstance;
     }
 
-    private IDataLoaderStatusListener mStatusListener = null;
-
     /* Native methods */
     private native boolean nativeCreateDataLoader(int storageId,
             @NonNull FileSystemControlParcel control,
@@ -314,5 +236,10 @@
 
     private native boolean nativeDestroyDataLoader(int storageId);
 
-    private static native boolean nativeReportStatus(long nativeInstance, int status);
+    private native boolean nativePrepareImage(int storageId,
+            Collection<InstallationFile> addedFiles, Collection<String> removedFiles);
+
+    private static native void nativeWriteData(long nativeInstance, String name, long offsetBytes,
+            long lengthBytes, ParcelFileDescriptor incomingFd);
+
 }
diff --git a/core/jni/android_service_DataLoaderService.cpp b/core/jni/android_service_DataLoaderService.cpp
index 381b386..a62d127 100644
--- a/core/jni/android_service_DataLoaderService.cpp
+++ b/core/jni/android_service_DataLoaderService.cpp
@@ -51,13 +51,19 @@
 }
 
 
-static jboolean nativeReportStatus(JNIEnv* env,
-                                   jobject clazz,
-                                   jlong self,
-                                   jint status) {
-    auto listener = (DataLoaderStatusListenerPtr)self;
-    return DataLoader_StatusListener_reportStatus(listener,
-                     (DataLoaderStatus)status);
+static jboolean nativePrepareImage(JNIEnv* env, jobject thiz, jint storageId, jobject addedFiles, jobject removedFiles) {
+    return DataLoaderService_OnPrepareImage(storageId, addedFiles, removedFiles);
+}
+
+static void nativeWriteData(JNIEnv* env,
+                            jobject clazz,
+                            jlong self,
+                            jstring name,
+                            jlong offsetBytes,
+                            jlong lengthBytes,
+                            jobject incomingFd) {
+    auto connector = (DataLoaderFilesystemConnectorPtr)self;
+    return DataLoader_FilesystemConnector_writeData(connector, name, offsetBytes, lengthBytes, incomingFd);
 }
 
 static const JNINativeMethod dlc_method_table[] = {
@@ -69,7 +75,8 @@
         {"nativeStartDataLoader", "(I)Z", (void*)nativeStartDataLoader},
         {"nativeStopDataLoader", "(I)Z", (void*)nativeStopDataLoader},
         {"nativeDestroyDataLoader", "(I)Z", (void*)nativeDestroyDataLoader},
-        {"nativeReportStatus", "(JI)Z", (void*)nativeReportStatus},
+        {"nativePrepareImage", "(ILjava/util/Collection;Ljava/util/Collection;)Z", (void*)nativePrepareImage},
+        {"nativeWriteData", "(JLjava/lang/String;JJLandroid/os/ParcelFileDescriptor;)V", (void*)nativeWriteData},
 };
 
 }  // namespace
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1165d2d..44a902c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4113,7 +4113,7 @@
     <permission android:name="android.permission.INTENT_FILTER_VERIFICATION_AGENT"
         android:protectionLevel="signature|privileged" />
 
-    <!-- Must be required by intent filter verifier receiver, to ensure that only the
+    <!-- Must be required by intent filter verifier rintent-filtereceiver, to ensure that only the
          system can interact with it.
          @hide
     -->
@@ -5143,6 +5143,12 @@
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
 
-</application>
+        <service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader">
+            <intent-filter>
+                <action android:name="android.intent.action.LOAD_DATA" />
+            </intent-filter>
+        </service>
+
+    </application>
 
 </manifest>
diff --git a/packages/Incremental/NativeAdbDataLoader/jni/com_android_incremental_nativeadb_DataLoaderService.cpp b/packages/Incremental/NativeAdbDataLoader/jni/com_android_incremental_nativeadb_DataLoaderService.cpp
index de92fcd5..4e49302 100644
--- a/packages/Incremental/NativeAdbDataLoader/jni/com_android_incremental_nativeadb_DataLoaderService.cpp
+++ b/packages/Incremental/NativeAdbDataLoader/jni/com_android_incremental_nativeadb_DataLoaderService.cpp
@@ -177,8 +177,7 @@
                   android::dataloader::ServiceParamsPtr) final {
         CHECK(ifs) << "ifs can't be null";
         CHECK(statusListener) << "statusListener can't be null";
-        ALOGE("[AdbDataLoader] onCreate: %s/%s/%d", params.staticArgs().c_str(),
-              params.packageName().c_str(), (int)params.dynamicArgs().size());
+        ALOGE("[AdbDataLoader] onCreate: %d/%s/%s/%s/%d", params.type(), params.packageName().c_str(), params.className().c_str(), params.arguments().c_str(), (int)params.dynamicArgs().size());
 
         if (params.dynamicArgs().empty()) {
             ALOGE("[AdbDataLoader] Invalid DataLoaderParams. Need in/out FDs.");
@@ -204,7 +203,7 @@
         }
 
         std::string logFile;
-        if (const auto packageName = extractPackageName(params.staticArgs()); !packageName.empty()) {
+        if (const auto packageName = extractPackageName(params.arguments()); !packageName.empty()) {
             logFile = android::base::GetProperty("adb.readlog." + packageName, "");
         }
         if (logFile.empty()) {
@@ -288,8 +287,7 @@
                           "inode=%d. Ignore.",
                           static_cast<int>(ino));
                     mRequestedFiles.erase(fileId);
-                    mStatusListener->reportStatus(
-                            INCREMENTAL_DATA_LOADER_NO_CONNECTION);
+                    mStatusListener->reportStatus(DATA_LOADER_NO_CONNECTION);
                 }
             }
             sendRequest(mOutFd, BLOCK_MISSING, fileId, blockIdx);
@@ -337,7 +335,7 @@
             }
             if (res < 0) {
                 ALOGE("[AdbDataLoader] failed to poll. Abort.");
-                mStatusListener->reportStatus(INCREMENTAL_DATA_LOADER_NO_CONNECTION);
+                mStatusListener->reportStatus(DATA_LOADER_NO_CONNECTION);
                 break;
             }
             if (res == mEventFd) {
@@ -346,7 +344,7 @@
             }
             if (!readChunk(mInFd, data)) {
                 ALOGE("[AdbDataLoader] failed to read a message. Abort.");
-                mStatusListener->reportStatus(INCREMENTAL_DATA_LOADER_NO_CONNECTION);
+                mStatusListener->reportStatus(DATA_LOADER_NO_CONNECTION);
                 break;
             }
             auto remainingData = std::span(data);
diff --git a/services/core/java/com/android/server/incremental/IncrementalManagerService.java b/services/core/java/com/android/server/incremental/IncrementalManagerService.java
index 3049522..d673ec8 100644
--- a/services/core/java/com/android/server/incremental/IncrementalManagerService.java
+++ b/services/core/java/com/android/server/incremental/IncrementalManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server.incremental;
 
 import android.annotation.NonNull;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.DataLoaderManager;
 import android.content.pm.DataLoaderParamsParcel;
@@ -85,7 +86,8 @@
             DataLoaderParamsParcel params,
             IDataLoaderStatusListener listener) {
         Bundle dataLoaderParams = new Bundle();
-        dataLoaderParams.putCharSequence("packageName", params.packageName);
+        dataLoaderParams.putParcelable("componentName",
+                new ComponentName(params.packageName, params.className));
         dataLoaderParams.putParcelable("control", control);
         dataLoaderParams.putParcelable("params", params);
         DataLoaderManager dataLoaderManager = mContext.getSystemService(DataLoaderManager.class);
@@ -109,8 +111,7 @@
             return false;
         }
         try {
-            // TODO: fix file list
-            dataLoader.start(null);
+            dataLoader.start();
             return true;
         } catch (RemoteException ex) {
             return false;
diff --git a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
index 6a8434a..a68f777 100644
--- a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
+++ b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.IIntentReceiver;
 import android.content.IIntentSender;
@@ -111,14 +112,14 @@
             pw.println("File names and sizes don't match.");
             return ERROR_DATA_LOADER_INIT;
         }
-        final DataLoaderParams params = new DataLoaderParams(
-                "", LOADER_PACKAGE_NAME, dataLoaderDynamicArgs);
+        final DataLoaderParams params = DataLoaderParams.forIncremental(
+                new ComponentName(LOADER_PACKAGE_NAME, ""), "", dataLoaderDynamicArgs);
         PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
                 PackageInstaller.SessionParams.MODE_FULL_INSTALL);
         sessionParams.installFlags |= PackageManager.INSTALL_ALL_USERS;
         // Replace existing if same package is already installed
         sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
-        sessionParams.setIncrementalParams(params);
+        sessionParams.setDataLoaderParams(params);
 
         try {
             int sessionId = packageInstaller.createSession(sessionParams);
diff --git a/services/core/java/com/android/server/pm/DataLoaderManagerService.java b/services/core/java/com/android/server/pm/DataLoaderManagerService.java
index 0719797..0dfea7f 100644
--- a/services/core/java/com/android/server/pm/DataLoaderManagerService.java
+++ b/services/core/java/com/android/server/pm/DataLoaderManagerService.java
@@ -76,13 +76,12 @@
                     return false;
                 }
             }
-            CharSequence packageNameSeq = params.getCharSequence("packageName");
-            if (packageNameSeq == null) {
-                Slog.e(TAG, "Must specify package name.");
+            ComponentName componentName = params.getParcelable("componentName");
+            if (componentName == null) {
+                Slog.e(TAG, "Must specify component name.");
                 return false;
             }
-            String packageName = packageNameSeq.toString();
-            ComponentName dataLoaderComponent = getDataLoaderServiceName(packageName);
+            ComponentName dataLoaderComponent = resolveDataLoaderComponentName(componentName);
             if (dataLoaderComponent == null) {
                 return false;
             }
@@ -103,22 +102,23 @@
         /**
          * Find the ComponentName of the data loader service provider, given its package name.
          *
-         * @param packageName the package name of the provider.
+         * @param componentName the name of the provider.
          * @return ComponentName of the data loader service provider. Null if provider not found.
          */
-        private @Nullable ComponentName getDataLoaderServiceName(String packageName) {
+        private @Nullable ComponentName resolveDataLoaderComponentName(
+                ComponentName componentName) {
             final PackageManager pm = mContext.getPackageManager();
             if (pm == null) {
                 Slog.e(TAG, "PackageManager is not available.");
                 return null;
             }
             Intent intent = new Intent(Intent.ACTION_LOAD_DATA);
-            intent.setPackage(packageName);
+            intent.setComponent(componentName);
             List<ResolveInfo> services =
                     pm.queryIntentServicesAsUser(intent, 0, UserHandle.getCallingUserId());
             if (services == null || services.isEmpty()) {
                 Slog.e(TAG,
-                        "Failed to find data loader service provider in package " + packageName);
+                        "Failed to find data loader service provider in " + componentName);
                 return null;
             }
 
@@ -128,23 +128,21 @@
             int numServices = services.size();
             for (int i = 0; i < numServices; i++) {
                 ResolveInfo ri = services.get(i);
-                ComponentName componentName = new ComponentName(
+                ComponentName resolved = new ComponentName(
                         ri.serviceInfo.packageName, ri.serviceInfo.name);
                 // There should only be one matching provider inside the given package.
                 // If there's more than one, return the first one found.
                 try {
-                    ApplicationInfo ai = pm.getApplicationInfo(componentName.getPackageName(), 0);
+                    ApplicationInfo ai = pm.getApplicationInfo(resolved.getPackageName(), 0);
                     if (checkLoader && !ai.isPrivilegedApp()) {
                         Slog.w(TAG,
-                                "Data loader: " + componentName.getPackageName()
-                                        + " is not a privileged app, skipping.");
+                                "Data loader: " + resolved + " is not a privileged app, skipping.");
                         continue;
                     }
-                    return componentName;
+                    return resolved;
                 } catch (PackageManager.NameNotFoundException ex) {
                     Slog.w(TAG,
-                            "Privileged data loader: " + componentName.getPackageName()
-                                    + " not found, skipping.");
+                            "Privileged data loader: " + resolved + " not found, skipping.");
                 }
 
             }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index c12395e..ac183dc 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -16,6 +16,8 @@
 
 package com.android.server.pm;
 
+import static android.content.pm.DataLoaderType.INCREMENTAL;
+import static android.content.pm.DataLoaderType.STREAMING;
 import static android.content.pm.PackageManager.INSTALL_FAILED_ABORTED;
 import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_SIGNATURE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
@@ -49,12 +51,18 @@
 import android.annotation.Nullable;
 import android.app.admin.DevicePolicyEventLogger;
 import android.app.admin.DevicePolicyManagerInternal;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.IIntentReceiver;
 import android.content.IIntentSender;
 import android.content.Intent;
 import android.content.IntentSender;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.DataLoaderManager;
+import android.content.pm.DataLoaderParams;
+import android.content.pm.FileSystemControlParcel;
+import android.content.pm.IDataLoader;
+import android.content.pm.IDataLoaderStatusListener;
 import android.content.pm.IPackageInstallObserver2;
 import android.content.pm.IPackageInstallerSession;
 import android.content.pm.IPackageInstallerSessionFileSystemConnector;
@@ -84,6 +92,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.ParcelableException;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.RevocableFileDescriptor;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -127,12 +136,12 @@
 import java.io.FileFilter;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
 
 public class PackageInstallerSession extends IPackageInstallerSession.Stub {
     private static final String TAG = "PackageInstallerSession";
@@ -189,7 +198,11 @@
     private static final String ATTR_VOLUME_UUID = "volumeUuid";
     private static final String ATTR_NAME = "name";
     private static final String ATTR_INSTALL_REASON = "installRason";
-    private static final String ATTR_DATA_LOADER_PACKAGE_NAME = "dataLoaderPackageName";
+    private static final String ATTR_IS_DATALOADER = "isDataLoader";
+    private static final String ATTR_DATALOADER_TYPE = "dataLoaderType";
+    private static final String ATTR_DATALOADER_PACKAGE_NAME = "dataLoaderPackageName";
+    private static final String ATTR_DATALOADER_CLASS_NAME = "dataLoaderClassName";
+    private static final String ATTR_DATALOADER_ARGUMENTS = "dataLoaderArguments";
     private static final String ATTR_LENGTH_BYTES = "lengthBytes";
     private static final String ATTR_METADATA = "metadata";
 
@@ -414,7 +427,15 @@
     };
 
     private boolean isDataLoaderInstallation() {
-        return !TextUtils.isEmpty(params.dataLoaderPackageName);
+        return params.dataLoaderParams != null;
+    }
+
+    private boolean isStreamingInstallation() {
+        return isDataLoaderInstallation() && params.dataLoaderParams.getType() == STREAMING;
+    }
+
+    private boolean isIncrementalInstallation() {
+        return isDataLoaderInstallation() && params.dataLoaderParams.getType() == INCREMENTAL;
     }
 
     /**
@@ -525,14 +546,13 @@
                 stagedSessionErrorMessage != null ? stagedSessionErrorMessage : "";
 
         // TODO(b/136132412): sanity check if session should not be incremental
-        if (!params.isStaged && params.incrementalParams != null
-                && !params.incrementalParams.getPackageName().isEmpty()) {
+        if (!params.isStaged && isIncrementalInstallation()) {
             IncrementalManager incrementalManager = (IncrementalManager) mContext.getSystemService(
                     Context.INCREMENTAL_SERVICE);
             if (incrementalManager != null) {
                 mIncrementalFileStorages =
                         new IncrementalFileStorages(mPackageName, stageDir, incrementalManager,
-                                params.incrementalParams);
+                                params.dataLoaderParams);
             }
         }
     }
@@ -714,7 +734,7 @@
     public void removeSplit(String splitName) {
         if (isDataLoaderInstallation()) {
             throw new IllegalStateException(
-                    "Cannot remove splits in a callback installation session.");
+                    "Cannot remove splits in a data loader installation session.");
         }
         if (TextUtils.isEmpty(params.appPackageName)) {
             throw new IllegalStateException("Must specify package name to remove a split");
@@ -753,7 +773,7 @@
     private void assertCanWrite(boolean reverseMode) {
         if (isDataLoaderInstallation()) {
             throw new IllegalStateException(
-                    "Cannot write regular files in a callback installation session.");
+                    "Cannot write regular files in a data loader installation session.");
         }
         synchronized (mLock) {
             assertCallerIsOwnerOrRootLocked();
@@ -894,7 +914,7 @@
     public ParcelFileDescriptor openRead(String name) {
         if (isDataLoaderInstallation()) {
             throw new IllegalStateException(
-                    "Cannot read regular files in a callback installation session.");
+                    "Cannot read regular files in a data loader installation session.");
         }
         synchronized (mLock) {
             assertCallerIsOwnerOrRootLocked();
@@ -1663,7 +1683,7 @@
                 computeProgressLocked(true);
 
                 // Unpack native libraries for non-incremental installation
-                if (params.incrementalParams == null) {
+                if (isIncrementalInstallation()) {
                     extractNativeLibraries(stageDir, params.abiOverride, mayInheritNativeLibs());
                 }
             }
@@ -2382,7 +2402,7 @@
         }
         if (!isDataLoaderInstallation()) {
             throw new IllegalStateException(
-                    "Cannot add files to non-callback installation session.");
+                    "Cannot add files to non-data loader installation session.");
         }
         // Use installer provided name for now; we always rename later
         if (!FileUtils.isValidExtFilename(name)) {
@@ -2401,7 +2421,7 @@
     public void removeFile(String name) {
         if (!isDataLoaderInstallation()) {
             throw new IllegalStateException(
-                    "Cannot add files to non-callback installation session.");
+                    "Cannot add files to non-data loader installation session.");
         }
         if (TextUtils.isEmpty(params.appPackageName)) {
             throw new IllegalStateException("Must specify package name to remove a split");
@@ -2415,76 +2435,121 @@
         }
     }
 
+    static class Notificator {
+        private int mValue = 0;
+
+        void setValue(int value) {
+            synchronized (this) {
+                mValue = value;
+                this.notify();
+            }
+        }
+        int waitForValue() {
+            synchronized (this) {
+                while (mValue == 0) {
+                    try {
+                        this.wait();
+                    } catch (InterruptedException e) {
+                        // Happens if someone interrupts your thread.
+                    }
+                }
+                return mValue;
+            }
+        }
+    }
+
     /**
      * Makes sure files are present in staging location.
      */
     private void prepareDataLoader()
             throws PackageManagerException, StreamingException {
-        if (!isDataLoaderInstallation()) {
+        if (!isStreamingInstallation()) {
             return;
         }
 
         FileSystemConnector connector = new FileSystemConnector();
 
-        FileInfo[] addedFiles = mFiles.stream().filter(
-                file -> sAddedFilter.accept(new File(file.name))).toArray(FileInfo[]::new);
-        String[] removedFiles = mFiles.stream().filter(
+        List<InstallationFile> addedFiles = mFiles.stream().filter(
+                file -> sAddedFilter.accept(new File(file.name))).map(
+                    file -> new InstallationFile(
+                            file.name, file.lengthBytes, file.metadata)).collect(
+                Collectors.toList());
+        List<String> removedFiles = mFiles.stream().filter(
                 file -> sRemovedFilter.accept(new File(file.name))).map(
-                    file -> file.name.substring(0,
-                        file.name.length() - REMOVE_MARKER_EXTENSION.length())).toArray(
-                String[]::new);
+                    file -> file.name.substring(
+                            0, file.name.length() - REMOVE_MARKER_EXTENSION.length())).collect(
+                Collectors.toList());
 
-        DataLoader dataLoader = new DataLoader();
-        try {
-            dataLoader.onCreate(connector);
-
-            if (!dataLoader.onPrepareImage(addedFiles, removedFiles)) {
-                throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE,
-                        "Failed to prepare image.");
-            }
-        } catch (IOException e) {
-            throw new StreamingException(e);
-        } finally {
-            dataLoader.onDestroy();
-        }
-    }
-
-    static class DataLoader {
-        private ParcelFileDescriptor mInFd = null;
-        private FileSystemConnector mConnector = null;
-
-        void onCreate(FileSystemConnector connector) throws IOException {
-            mConnector = connector;
+        DataLoaderManager dataLoaderManager = mContext.getSystemService(DataLoaderManager.class);
+        if (dataLoaderManager == null) {
+            throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE,
+                    "Failed to find data loader manager service");
         }
 
-        void onDestroy() {
-            IoUtils.closeQuietly(mInFd);
-        }
+        // TODO(b/146080380): make this code async.
+        final Notificator created = new Notificator();
+        final Notificator started = new Notificator();
+        final Notificator imageReady = new Notificator();
 
-        private static final String STDIN_PATH = "-";
-        boolean onPrepareImage(FileInfo[] addedFiles, String[] removedFiles) throws IOException {
-            for (FileInfo fileInfo : addedFiles) {
-                String filePath = new String(fileInfo.metadata, StandardCharsets.UTF_8);
-                if (STDIN_PATH.equals(filePath) || TextUtils.isEmpty(filePath)) {
-                    if (mInFd == null) {
-                        Slog.e(TAG, "Invalid stdin file descriptor.");
-                        return false;
+        IDataLoaderStatusListener listener = new IDataLoaderStatusListener.Stub() {
+            @Override
+            public void onStatusChanged(int dataLoaderId, int status) {
+                switch (status) {
+                    case IDataLoaderStatusListener.DATA_LOADER_CREATED: {
+                        created.setValue(1);
+                        break;
                     }
-                    ParcelFileDescriptor inFd = ParcelFileDescriptor.dup(mInFd.getFileDescriptor());
-                    mConnector.writeData(fileInfo.name, 0, fileInfo.lengthBytes, inFd);
-                } else {
-                    File localFile = new File(filePath);
-                    ParcelFileDescriptor incomingFd = null;
-                    try {
-                        incomingFd = ParcelFileDescriptor.open(localFile,
-                                ParcelFileDescriptor.MODE_READ_ONLY);
-                        mConnector.writeData(fileInfo.name, 0, localFile.length(), incomingFd);
-                    } finally {
-                        IoUtils.closeQuietly(incomingFd);
+                    case IDataLoaderStatusListener.DATA_LOADER_STARTED: {
+                        started.setValue(1);
+                        break;
+                    }
+                    case IDataLoaderStatusListener.DATA_LOADER_IMAGE_READY: {
+                        imageReady.setValue(1);
+                        break;
+                    }
+                    case IDataLoaderStatusListener.DATA_LOADER_IMAGE_NOT_READY: {
+                        imageReady.setValue(2);
+                        break;
                     }
                 }
             }
-            return true;
+        };
+
+        final DataLoaderParams params = this.params.dataLoaderParams;
+
+        final FileSystemControlParcel control = new FileSystemControlParcel();
+        control.callback = connector;
+
+        Bundle dataLoaderParams = new Bundle();
+        dataLoaderParams.putParcelable("componentName", params.getComponentName());
+        dataLoaderParams.putParcelable("control", control);
+        dataLoaderParams.putParcelable("params", params.getData());
+
+        if (!dataLoaderManager.initializeDataLoader(sessionId, dataLoaderParams, listener)) {
+            throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE,
+                    "Failed to initialize data loader");
+        }
+        created.waitForValue();
+
+        IDataLoader dataLoader = dataLoaderManager.getDataLoader(sessionId);
+        if (dataLoader == null) {
+            throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE,
+                    "Failure to obtain data loader");
+        }
+
+        try {
+            dataLoader.start();
+            started.waitForValue();
+
+            dataLoader.prepareImage(addedFiles, removedFiles);
+            if (imageReady.waitForValue() == 2) {
+                throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE,
+                        "Failed to prepare image.");
+            }
+
+            dataLoader.destroy();
+        } catch (RemoteException e) {
+            throw new StreamingException(e);
         }
     }
 
@@ -2846,7 +2911,17 @@
             writeStringAttribute(out, ATTR_VOLUME_UUID, params.volumeUuid);
             writeIntAttribute(out, ATTR_INSTALL_REASON, params.installReason);
 
-            writeStringAttribute(out, ATTR_DATA_LOADER_PACKAGE_NAME, params.dataLoaderPackageName);
+            final boolean isDataLoader = params.dataLoaderParams != null;
+            writeBooleanAttribute(out, ATTR_IS_DATALOADER, isDataLoader);
+            if (isDataLoader) {
+                writeIntAttribute(out, ATTR_DATALOADER_TYPE, params.dataLoaderParams.getType());
+                writeStringAttribute(out, ATTR_DATALOADER_PACKAGE_NAME,
+                        params.dataLoaderParams.getComponentName().getPackageName());
+                writeStringAttribute(out, ATTR_DATALOADER_CLASS_NAME,
+                        params.dataLoaderParams.getComponentName().getClassName());
+                writeStringAttribute(out, ATTR_DATALOADER_ARGUMENTS,
+                        params.dataLoaderParams.getArguments());
+            }
 
             writeGrantedRuntimePermissionsLocked(out, params.grantedRuntimePermissions);
             writeWhitelistedRestrictedPermissionsLocked(out,
@@ -2957,7 +3032,15 @@
         params.volumeUuid = readStringAttribute(in, ATTR_VOLUME_UUID);
         params.installReason = readIntAttribute(in, ATTR_INSTALL_REASON);
 
-        params.dataLoaderPackageName = readStringAttribute(in, ATTR_DATA_LOADER_PACKAGE_NAME);
+        if (readBooleanAttribute(in, ATTR_IS_DATALOADER)) {
+            params.dataLoaderParams = new DataLoaderParams(
+                    readIntAttribute(in, ATTR_DATALOADER_TYPE),
+                    new ComponentName(
+                            readStringAttribute(in, ATTR_DATALOADER_PACKAGE_NAME),
+                            readStringAttribute(in, ATTR_DATALOADER_CLASS_NAME)),
+                    readStringAttribute(in, ATTR_DATALOADER_ARGUMENTS),
+                    null);
+        }
 
         final File appIconFile = buildAppIconFile(sessionId, sessionsDir);
         if (appIconFile.exists()) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index dfffbd6..10e2780 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -34,6 +34,7 @@
 import android.content.Intent;
 import android.content.IntentSender;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.DataLoaderParams;
 import android.content.pm.FeatureInfo;
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageInstaller;
@@ -136,7 +137,9 @@
     private final static String ART_PROFILE_SNAPSHOT_DEBUG_LOCATION = "/data/misc/profman/";
     private static final int DEFAULT_WAIT_MS = 60 * 1000;
 
-    private static final String PM_SHELL_DATALOADER = "com.android.pm.dataloader";
+    private static final String DATA_LOADER_PACKAGE = "android";
+    private static final String DATA_LOADER_CLASS =
+            "com.android.server.pm.PackageManagerShellCommandDataLoader";
 
     final IPackageManager mInterface;
     final IPermissionManager mPermissionManager;
@@ -1159,8 +1162,10 @@
 
     private int runStreamingInstall() throws RemoteException {
         final InstallParams params = makeInstallParams();
-        if (TextUtils.isEmpty(params.sessionParams.dataLoaderPackageName)) {
-            params.sessionParams.setDataLoaderPackageName(PM_SHELL_DATALOADER);
+        if (params.sessionParams.dataLoaderParams == null) {
+            final DataLoaderParams dataLoaderParams = DataLoaderParams.forStreaming(
+                    new ComponentName(DATA_LOADER_PACKAGE, DATA_LOADER_CLASS), "");
+            params.sessionParams.setDataLoaderParams(dataLoaderParams);
         }
         return doRunInstall(params);
     }
@@ -1171,7 +1176,7 @@
 
     private int doRunInstall(final InstallParams params) throws RemoteException {
         final PrintWriter pw = getOutPrintWriter();
-        final boolean streaming = !TextUtils.isEmpty(params.sessionParams.dataLoaderPackageName);
+        final boolean streaming = params.sessionParams.dataLoaderParams != null;
 
         ArrayList<String> inPaths = getRemainingArgs();
         if (inPaths.isEmpty()) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
new file mode 100644
index 0000000..1ee9ab8
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
@@ -0,0 +1,92 @@
+/*
+ * 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 com.android.server.pm;
+
+import android.annotation.NonNull;
+import android.content.pm.DataLoaderParams;
+import android.content.pm.InstallationFile;
+import android.os.ParcelFileDescriptor;
+import android.service.dataloader.DataLoaderService;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+
+/**
+ * Callback data loader for PackageManagerShellCommand installations.
+ */
+public class PackageManagerShellCommandDataLoader extends DataLoaderService {
+    public static final String TAG = "PackageManagerShellCommandDataLoader";
+
+    static class DataLoader implements DataLoaderService.DataLoader {
+        private ParcelFileDescriptor mInFd = null;
+        private FileSystemConnector mConnector = null;
+
+        private static final String STDIN_PATH = "-";
+
+        @Override
+        public boolean onCreate(@NonNull DataLoaderParams dataLoaderParams,
+                @NonNull FileSystemConnector connector) {
+            mConnector = connector;
+            return true;
+        }
+        @Override
+        public boolean onPrepareImage(Collection<InstallationFile> addedFiles,
+                Collection<String> removedFiles) {
+            try {
+                for (InstallationFile fileInfo : addedFiles) {
+                    String filePath = new String(fileInfo.getMetadata(), StandardCharsets.UTF_8);
+                    if (STDIN_PATH.equals(filePath) || TextUtils.isEmpty(filePath)) {
+                        // TODO(b/146080380): add support for STDIN installations.
+                        if (mInFd == null) {
+                            Slog.e(TAG, "Invalid stdin file descriptor.");
+                            return false;
+                        }
+                        ParcelFileDescriptor inFd = ParcelFileDescriptor.dup(
+                                mInFd.getFileDescriptor());
+                        mConnector.writeData(fileInfo.getName(), 0, fileInfo.getSize(), inFd);
+                    } else {
+                        File localFile = new File(filePath);
+                        ParcelFileDescriptor incomingFd = null;
+                        try {
+                            // TODO(b/146080380): open files via callback into shell command.
+                            incomingFd = ParcelFileDescriptor.open(localFile,
+                                    ParcelFileDescriptor.MODE_READ_ONLY);
+                            mConnector.writeData(fileInfo.getName(), 0, localFile.length(),
+                                    incomingFd);
+                        } finally {
+                            IoUtils.closeQuietly(incomingFd);
+                        }
+                    }
+                }
+                return true;
+            } catch (IOException e) {
+                return false;
+            }
+        }
+    }
+
+    @Override
+    public DataLoaderService.DataLoader onCreateDataLoader() {
+        return new DataLoader();
+    }
+}
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index c43328f..afce260 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -351,10 +351,13 @@
     {
         metadata::Mount m;
         m.mutable_storage()->set_id(ifs->mountId);
+        m.mutable_loader()->set_type((int)dataLoaderParams.type);
         m.mutable_loader()->set_package_name(dataLoaderParams.packageName);
-        m.mutable_loader()->set_arguments(dataLoaderParams.staticArgs);
+        m.mutable_loader()->set_class_name(dataLoaderParams.className);
+        m.mutable_loader()->set_arguments(dataLoaderParams.arguments);
         const auto metadata = m.SerializeAsString();
         m.mutable_loader()->release_arguments();
+        m.mutable_loader()->release_class_name();
         m.mutable_loader()->release_package_name();
         if (auto err = mIncFs->makeFile(ifs->control, constants().infoMdName, INCFS_ROOT_INODE, 0,
                                         metadata);
@@ -794,7 +797,7 @@
     }
     bool started = false;
     std::unique_lock l(ifs->lock);
-    if (ifs->dataLoaderStatus != IDataLoaderStatusListener::DATA_LOADER_READY) {
+    if (ifs->dataLoaderStatus != IDataLoaderStatusListener::DATA_LOADER_CREATED) {
         if (ifs->dataLoaderReady.wait_for(l, Seconds(5)) == std::cv_status::timeout) {
             LOG(ERROR) << "Timeout waiting for data loader to be ready";
             return false;
@@ -917,8 +920,10 @@
     }
 
     DataLoaderParamsParcel dlParams;
+    dlParams.type = (DataLoaderType)m.loader().type();
     dlParams.packageName = std::move(*m.mutable_loader()->mutable_package_name());
-    dlParams.staticArgs = std::move(*m.mutable_loader()->mutable_arguments());
+    dlParams.className = std::move(*m.mutable_loader()->mutable_class_name());
+    dlParams.arguments = std::move(*m.mutable_loader()->mutable_arguments());
     if (!prepareDataLoader(*ifs, &dlParams)) {
         deleteStorage(*ifs);
         return false;
@@ -955,7 +960,7 @@
     }
 
     std::unique_lock l(ifs.lock);
-    if (ifs.dataLoaderStatus == IDataLoaderStatusListener::DATA_LOADER_READY) {
+    if (ifs.dataLoaderStatus == IDataLoaderStatusListener::DATA_LOADER_CREATED) {
         LOG(INFO) << "Skipped data loader preparation because it already exists";
         return true;
     }
@@ -1008,20 +1013,20 @@
             }
             break;
         }
-        case IDataLoaderStatusListener::DATA_LOADER_READY: {
+        case IDataLoaderStatusListener::DATA_LOADER_CONNECTION_OK: {
+            ifs->dataLoaderStatus = IDataLoaderStatusListener::DATA_LOADER_STARTED;
+            break;
+        }
+        case IDataLoaderStatusListener::DATA_LOADER_CREATED: {
             ifs->dataLoaderReady.notify_one();
             break;
         }
-        case IDataLoaderStatusListener::DATA_LOADER_NOT_READY: {
+        case IDataLoaderStatusListener::DATA_LOADER_DESTROYED: {
             ifs->dataLoaderStatus = IDataLoaderStatusListener::DATA_LOADER_STOPPED;
             incrementalService.deleteStorageLocked(*ifs, std::move(l));
             break;
         }
-        case IDataLoaderStatusListener::DATA_LOADER_RUNNING: {
-            break;
-        }
-        case IDataLoaderStatusListener::DATA_LOADER_CONNECTION_OK: {
-            ifs->dataLoaderStatus = IDataLoaderStatusListener::DATA_LOADER_RUNNING;
+        case IDataLoaderStatusListener::DATA_LOADER_STARTED: {
             break;
         }
         case IDataLoaderStatusListener::DATA_LOADER_STOPPED: {
diff --git a/services/incremental/Metadata.proto b/services/incremental/Metadata.proto
index 0ff3c32..79f1bf8 100644
--- a/services/incremental/Metadata.proto
+++ b/services/incremental/Metadata.proto
@@ -10,7 +10,9 @@
 
 message DataLoader {
     string package_name = 1;
+    string class_name = 3;
     string arguments = 2;
+    int32 type = 4;
 }
 
 message Storage {
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index f6b123d..ca1e1a9 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -138,10 +138,10 @@
                 .WillByDefault(Invoke(this, &MockIncrementalManager::startDataLoaderOk));
     }
     void setDataLoaderStatusNotReady() {
-        mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_NOT_READY);
+        mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
     }
     void setDataLoaderStatusReady() {
-        mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_READY);
+        mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_CREATED);
     }
 
 private:
@@ -235,7 +235,7 @@
         MockServiceManager serviceManager = MockServiceManager(mVold, mIncrementalManager, mIncFs);
         mIncrementalService = std::make_unique<IncrementalService>(serviceManager, mRootDir.path);
         mDataLoaderParcel.packageName = "com.test";
-        mDataLoaderParcel.staticArgs = "uri";
+        mDataLoaderParcel.arguments = "uri";
         mIncrementalService->onSystemReady();
     }