Merge "AudioService: fix A2DP device check when making device unavailable"
diff --git a/Android.bp b/Android.bp
index b349ce2..787b48b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -807,9 +807,11 @@
 filegroup {
     name: "dataloader_aidl",
     srcs: [
+        "core/java/android/content/pm/DataLoaderParamsParcel.aidl",
+        "core/java/android/content/pm/FileSystemControlParcel.aidl",
         "core/java/android/content/pm/IDataLoaderStatusListener.aidl",
-        "core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl",
-        "core/java/android/os/incremental/NamedParcelFileDescriptor.aidl",
+        "core/java/android/content/pm/IPackageInstallerSessionFileSystemConnector.aidl",
+        "core/java/android/content/pm/NamedParcelFileDescriptor.aidl",
     ],
     path: "core/java",
 }
diff --git a/CleanSpec.mk b/CleanSpec.mk
index f94de29..b84e715 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -257,6 +257,8 @@
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/telephony/java/com/google/android/mms)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/framework/*-service.jar)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/framework/service-statsd.jar)
+$(call add-clean-step, rm -rf $(SOONG_OUT_DIR)/.intermediates/frameworks/base/libincremental_aidl-cpp-source/)
+$(call add-clean-step, rm -rf $(SOONG_OUT_DIR)/.intermediates/frameworks/base/libincremental_manager_aidl-cpp-source/)
 # ******************************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
 # ******************************************************************
diff --git a/cmds/svc/src/com/android/commands/svc/DataCommand.java b/cmds/svc/src/com/android/commands/svc/DataCommand.java
index b4dbd1d..35510cf 100644
--- a/cmds/svc/src/com/android/commands/svc/DataCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/DataCommand.java
@@ -16,16 +16,12 @@
 
 package com.android.commands.svc;
 
-/**
- * @deprecated Please use adb shell cmd phone data enabled/disable instead.
- */
-@Deprecated
+import android.os.ServiceManager;
+import android.os.RemoteException;
+import android.content.Context;
+import com.android.internal.telephony.ITelephony;
+
 public class DataCommand extends Svc.Command {
-
-    private static final String DECPRECATED_MESSAGE =
-            "adb shell svc data enable/disable is deprecated;"
-            + "please use adb shell cmd phone data enable/disable instead.";
-
     public DataCommand() {
         super("data");
     }
@@ -37,10 +33,36 @@
     public String longHelp() {
         return shortHelp() + "\n"
                 + "\n"
-                + DECPRECATED_MESSAGE;
+                + "usage: svc data [enable|disable]\n"
+                + "         Turn mobile data on or off.\n\n";
     }
 
     public void run(String[] args) {
-        System.err.println(DECPRECATED_MESSAGE);
+        boolean validCommand = false;
+        if (args.length >= 2) {
+            boolean flag = false;
+            if ("enable".equals(args[1])) {
+                flag = true;
+                validCommand = true;
+            } else if ("disable".equals(args[1])) {
+                flag = false;
+                validCommand = true;
+            }
+            if (validCommand) {
+                ITelephony phoneMgr
+                        = ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));
+                try {
+                    if (flag) {
+                        phoneMgr.enableDataConnectivity();
+                    } else
+                        phoneMgr.disableDataConnectivity();
+                }
+                catch (RemoteException e) {
+                    System.err.println("Mobile data operation failed: " + e);
+                }
+                return;
+            }
+        }
+        System.err.println(longHelp());
     }
 }
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 795f51a..d74802c 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -2097,6 +2097,113 @@
                 return new TaskSnapshot[size];
             }
         };
+
+        /** Builder for a {@link TaskSnapshot} object */
+        public static final class Builder {
+            private long mId;
+            private ComponentName mTopActivity;
+            private GraphicBuffer mSnapshot;
+            private ColorSpace mColorSpace;
+            private int mOrientation;
+            private Rect mContentInsets;
+            private boolean mReducedResolution;
+            private float mScaleFraction;
+            private boolean mIsRealSnapshot;
+            private int mWindowingMode;
+            private int mSystemUiVisibility;
+            private boolean mIsTranslucent;
+            private int mPixelFormat;
+
+            public Builder setId(long id) {
+                mId = id;
+                return this;
+            }
+
+            public Builder setTopActivityComponent(ComponentName name) {
+                mTopActivity = name;
+                return this;
+            }
+
+            public Builder setSnapshot(GraphicBuffer buffer) {
+                mSnapshot = buffer;
+                return this;
+            }
+
+            public Builder setColorSpace(ColorSpace colorSpace) {
+                mColorSpace = colorSpace;
+                return this;
+            }
+
+            public Builder setOrientation(int orientation) {
+                mOrientation = orientation;
+                return this;
+            }
+
+            public Builder setContentInsets(Rect contentInsets) {
+                mContentInsets = contentInsets;
+                return this;
+            }
+
+            public Builder setReducedResolution(boolean reducedResolution) {
+                mReducedResolution = reducedResolution;
+                return this;
+            }
+
+            public float getScaleFraction() {
+                return mScaleFraction;
+            }
+
+            public Builder setScaleFraction(float scaleFraction) {
+                mScaleFraction = scaleFraction;
+                return this;
+            }
+
+            public Builder setIsRealSnapshot(boolean realSnapshot) {
+                mIsRealSnapshot = realSnapshot;
+                return this;
+            }
+
+            public Builder setWindowingMode(int windowingMode) {
+                mWindowingMode = windowingMode;
+                return this;
+            }
+
+            public Builder setSystemUiVisibility(int systemUiVisibility) {
+                mSystemUiVisibility = systemUiVisibility;
+                return this;
+            }
+
+            public Builder setIsTranslucent(boolean isTranslucent) {
+                mIsTranslucent = isTranslucent;
+                return this;
+            }
+
+            public int getPixelFormat() {
+                return mPixelFormat;
+            }
+
+            public Builder setPixelFormat(int pixelFormat) {
+                mPixelFormat = pixelFormat;
+                return this;
+            }
+
+            public TaskSnapshot build() {
+                return new TaskSnapshot(
+                        mId,
+                        mTopActivity,
+                        mSnapshot,
+                        mColorSpace,
+                        mOrientation,
+                        mContentInsets,
+                        mReducedResolution,
+                        mScaleFraction,
+                        mIsRealSnapshot,
+                        mWindowingMode,
+                        mSystemUiVisibility,
+                        mIsTranslucent);
+
+            }
+        }
     }
 
     /** @hide */
diff --git a/core/java/android/os/incremental/IncrementalDataLoaderParams.java b/core/java/android/content/pm/DataLoaderParams.java
similarity index 82%
rename from core/java/android/os/incremental/IncrementalDataLoaderParams.java
rename to core/java/android/content/pm/DataLoaderParams.java
index 701f1cc..b163861 100644
--- a/core/java/android/os/incremental/IncrementalDataLoaderParams.java
+++ b/core/java/android/content/pm/DataLoaderParams.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.os.incremental;
+package android.content.pm;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -29,12 +29,12 @@
  * Hide for now.
  * @hide
  */
-public class IncrementalDataLoaderParams {
-    @NonNull private final IncrementalDataLoaderParamsParcel mData;
+public class DataLoaderParams {
+    @NonNull private final DataLoaderParamsParcel mData;
 
-    public IncrementalDataLoaderParams(@NonNull String url, @NonNull String packageName,
+    public DataLoaderParams(@NonNull String url, @NonNull String packageName,
             @Nullable Map<String, ParcelFileDescriptor> namedFds) {
-        IncrementalDataLoaderParamsParcel data = new IncrementalDataLoaderParamsParcel();
+        DataLoaderParamsParcel data = new DataLoaderParamsParcel();
         data.staticArgs = url;
         data.packageName = packageName;
         if (namedFds == null || namedFds.isEmpty()) {
@@ -52,7 +52,7 @@
         mData = data;
     }
 
-    public IncrementalDataLoaderParams(@NonNull IncrementalDataLoaderParamsParcel data) {
+    public DataLoaderParams(@NonNull DataLoaderParamsParcel data) {
         mData = data;
     }
 
@@ -70,7 +70,7 @@
         return mData.packageName;
     }
 
-    public final @NonNull IncrementalDataLoaderParamsParcel getData() {
+    public final @NonNull DataLoaderParamsParcel getData() {
         return mData;
     }
 
diff --git a/core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl b/core/java/android/content/pm/DataLoaderParamsParcel.aidl
similarity index 85%
rename from core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl
rename to core/java/android/content/pm/DataLoaderParamsParcel.aidl
index cd988dc..3316398 100644
--- a/core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl
+++ b/core/java/android/content/pm/DataLoaderParamsParcel.aidl
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-package android.os.incremental;
+package android.content.pm;
 
-import android.os.incremental.NamedParcelFileDescriptor;
+import android.content.pm.NamedParcelFileDescriptor;
 
 /**
  * Class for holding data loader configuration parameters.
  * @hide
  */
-parcelable IncrementalDataLoaderParamsParcel {
+parcelable DataLoaderParamsParcel {
     @utf8InCpp String packageName;
     @utf8InCpp String staticArgs;
     NamedParcelFileDescriptor[] dynamicArgs;
diff --git a/core/java/android/content/pm/FileSystemControlParcel.aidl b/core/java/android/content/pm/FileSystemControlParcel.aidl
new file mode 100644
index 0000000..f00feae
--- /dev/null
+++ b/core/java/android/content/pm/FileSystemControlParcel.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+import android.content.pm.IPackageInstallerSessionFileSystemConnector;
+import android.os.incremental.IncrementalFileSystemControlParcel;
+
+/**
+ * Wraps info needed for DataLoader to provide data.
+ * @hide
+ */
+parcelable FileSystemControlParcel {
+    // Incremental FS control descriptors.
+    @nullable IncrementalFileSystemControlParcel incremental;
+    // Callback-based installation connector.
+    @nullable IPackageInstallerSessionFileSystemConnector callback;
+}
diff --git a/core/java/android/content/pm/IDataLoader.aidl b/core/java/android/content/pm/IDataLoader.aidl
index 60cc9ba9..c65bd6a 100644
--- a/core/java/android/content/pm/IDataLoader.aidl
+++ b/core/java/android/content/pm/IDataLoader.aidl
@@ -30,5 +30,4 @@
    void start(in List<InstallationFile> fileInfos);
    void stop();
    void destroy();
-   void onFileCreated(long inode, in byte[] metadata);
 }
diff --git a/core/java/android/os/incremental/NamedParcelFileDescriptor.aidl b/core/java/android/content/pm/IPackageInstallerSessionFileSystemConnector.aidl
similarity index 76%
copy from core/java/android/os/incremental/NamedParcelFileDescriptor.aidl
copy to core/java/android/content/pm/IPackageInstallerSessionFileSystemConnector.aidl
index 038ced1..4b2f29e 100644
--- a/core/java/android/os/incremental/NamedParcelFileDescriptor.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSessionFileSystemConnector.aidl
@@ -14,15 +14,11 @@
  * limitations under the License.
  */
 
-package android.os.incremental;
+package android.content.pm;
 
 import android.os.ParcelFileDescriptor;
 
-/**
- * A named ParcelFileDescriptor.
- * @hide
- */
-parcelable NamedParcelFileDescriptor {
-    @utf8InCpp String name;
-    ParcelFileDescriptor fd;
+/** {@hide} */
+interface IPackageInstallerSessionFileSystemConnector {
+    void writeData(String name, long offsetBytes, long lengthBytes, in ParcelFileDescriptor fd);
 }
diff --git a/core/java/android/os/incremental/NamedParcelFileDescriptor.aidl b/core/java/android/content/pm/NamedParcelFileDescriptor.aidl
similarity index 95%
rename from core/java/android/os/incremental/NamedParcelFileDescriptor.aidl
rename to core/java/android/content/pm/NamedParcelFileDescriptor.aidl
index 038ced1..68dd5f5 100644
--- a/core/java/android/os/incremental/NamedParcelFileDescriptor.aidl
+++ b/core/java/android/content/pm/NamedParcelFileDescriptor.aidl
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.os.incremental;
+package android.content.pm;
 
 import android.os.ParcelFileDescriptor;
 
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 898631e..218c876 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -50,8 +50,6 @@
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
-import android.os.incremental.IncrementalDataLoaderParams;
-import android.os.incremental.IncrementalDataLoaderParamsParcel;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.ArraySet;
@@ -1459,7 +1457,7 @@
         /** {@hide} */
         public long requiredInstalledVersionCode = PackageManager.VERSION_CODE_HIGHEST;
         /** {@hide} */
-        public IncrementalDataLoaderParams incrementalParams;
+        public DataLoaderParams incrementalParams;
         /** TODO(b/146080380): add a class name to make it fully compatible with ComponentName.
          * {@hide} */
         public String dataLoaderPackageName;
@@ -1496,10 +1494,10 @@
             isMultiPackage = source.readBoolean();
             isStaged = source.readBoolean();
             requiredInstalledVersionCode = source.readLong();
-            IncrementalDataLoaderParamsParcel dataLoaderParamsParcel = source.readParcelable(
-                    IncrementalDataLoaderParamsParcel.class.getClassLoader());
+            DataLoaderParamsParcel dataLoaderParamsParcel = source.readParcelable(
+                    DataLoaderParamsParcel.class.getClassLoader());
             if (dataLoaderParamsParcel != null) {
-                incrementalParams = new IncrementalDataLoaderParams(
+                incrementalParams = new DataLoaderParams(
                         dataLoaderParamsParcel);
             }
             dataLoaderPackageName = source.readString();
@@ -1863,7 +1861,7 @@
          * {@hide}
          */
         @RequiresPermission(Manifest.permission.INSTALL_PACKAGES)
-        public void setIncrementalParams(@NonNull IncrementalDataLoaderParams incrementalParams) {
+        public void setIncrementalParams(@NonNull DataLoaderParams incrementalParams) {
             this.incrementalParams = incrementalParams;
         }
 
diff --git a/core/java/android/os/incremental/IIncrementalManager.aidl b/core/java/android/os/incremental/IIncrementalManager.aidl
index f84d7ef..17a310a 100644
--- a/core/java/android/os/incremental/IIncrementalManager.aidl
+++ b/core/java/android/os/incremental/IIncrementalManager.aidl
@@ -16,8 +16,8 @@
 
 package android.os.incremental;
 
-import android.os.incremental.IncrementalFileSystemControlParcel;
-import android.os.incremental.IncrementalDataLoaderParamsParcel;
+import android.content.pm.FileSystemControlParcel;
+import android.content.pm.DataLoaderParamsParcel;
 import android.content.pm.IDataLoaderStatusListener;
 
 /**
@@ -27,8 +27,8 @@
  */
 interface IIncrementalManager {
     boolean prepareDataLoader(int mountId,
-        in IncrementalFileSystemControlParcel control,
-        in IncrementalDataLoaderParamsParcel params,
+        in FileSystemControlParcel control,
+        in DataLoaderParamsParcel params,
         in IDataLoaderStatusListener listener);
     boolean startDataLoader(int mountId);
     void showHealthBlockedUI(int mountId);
diff --git a/core/java/android/os/incremental/IIncrementalManagerNative.aidl b/core/java/android/os/incremental/IIncrementalManagerNative.aidl
index d9c7c6b..14215b1 100644
--- a/core/java/android/os/incremental/IIncrementalManagerNative.aidl
+++ b/core/java/android/os/incremental/IIncrementalManagerNative.aidl
@@ -16,7 +16,7 @@
 
 package android.os.incremental;
 
-import android.os.incremental.IncrementalDataLoaderParamsParcel;
+import android.content.pm.DataLoaderParamsParcel;
 
 /** @hide */
 interface IIncrementalManagerNative {
@@ -32,7 +32,7 @@
      * Opens or creates a storage given a target path and data loader params. Returns the storage ID.
      */
     int openStorage(in @utf8InCpp String path);
-    int createStorage(in @utf8InCpp String path, in IncrementalDataLoaderParamsParcel params, int createMode);
+    int createStorage(in @utf8InCpp String path, in DataLoaderParamsParcel params, int createMode);
     int createLinkedStorage(in @utf8InCpp String path, int otherStorageId, int createMode);
 
     /**
diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java
index 5bd0748..7987efd 100644
--- a/core/java/android/os/incremental/IncrementalFileStorages.java
+++ b/core/java/android/os/incremental/IncrementalFileStorages.java
@@ -35,6 +35,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.pm.DataLoaderParams;
 import android.content.pm.InstallationFile;
 import android.os.IVold;
 import android.os.RemoteException;
@@ -82,12 +83,12 @@
     public IncrementalFileStorages(@NonNull String packageName,
             @NonNull File stageDir,
             @NonNull IncrementalManager incrementalManager,
-            @NonNull IncrementalDataLoaderParams incrementalDataLoaderParams) {
+            @NonNull DataLoaderParams dataLoaderParams) {
         mPackageName = packageName;
         mStageDir = stageDir;
         mIncrementalManager = incrementalManager;
-        if (incrementalDataLoaderParams.getPackageName().equals("local")) {
-            final String incrementalPath = incrementalDataLoaderParams.getStaticArgs();
+        if (dataLoaderParams.getPackageName().equals("local")) {
+            final String incrementalPath = dataLoaderParams.getStaticArgs();
             mDefaultStorage = mIncrementalManager.openStorage(incrementalPath);
             mDefaultDir = incrementalPath;
             return;
@@ -97,7 +98,7 @@
             return;
         }
         mDefaultStorage = mIncrementalManager.createStorage(mDefaultDir,
-                incrementalDataLoaderParams,
+                dataLoaderParams,
                 IncrementalManager.CREATE_MODE_CREATE
                         | IncrementalManager.CREATE_MODE_TEMPORARY_BIND, false);
     }
diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java
index c30f558..c722287 100644
--- a/core/java/android/os/incremental/IncrementalManager.java
+++ b/core/java/android/os/incremental/IncrementalManager.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.annotation.SystemService;
 import android.content.Context;
+import android.content.pm.DataLoaderParams;
 import android.os.RemoteException;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -104,7 +105,7 @@
      */
     @Nullable
     public IncrementalStorage createStorage(@NonNull String path,
-            @NonNull IncrementalDataLoaderParams params, @CreateMode int createMode,
+            @NonNull DataLoaderParams params, @CreateMode int createMode,
             boolean autoStartDataLoader) {
         try {
             final int id = mNativeService.createStorage(path, params.getData(), createMode);
diff --git a/core/java/android/service/dataloader/DataLoaderService.java b/core/java/android/service/dataloader/DataLoaderService.java
new file mode 100644
index 0000000..373e1e5
--- /dev/null
+++ b/core/java/android/service/dataloader/DataLoaderService.java
@@ -0,0 +1,307 @@
+/*
+ * 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.service.dataloader;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Service;
+import android.content.Intent;
+import android.content.pm.DataLoaderParams;
+import android.content.pm.DataLoaderParamsParcel;
+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.List;
+
+/**
+ * The base class for implementing data loader service to control data loaders. Expecting
+ * Incremental Service to bind to a children class of this.
+ *
+ * @hide
+ *
+ * Hide for now, should be @SystemApi
+ * TODO(b/136132412): update with latest API design
+ */
+public abstract class DataLoaderService extends Service {
+    private static final String TAG = "IncrementalDataLoaderService";
+    private final DataLoaderBinderService mBinder = new DataLoaderBinderService();
+
+    public static final int DATA_LOADER_READY =
+            IDataLoaderStatusListener.DATA_LOADER_READY;
+    public static final int DATA_LOADER_NOT_READY =
+            IDataLoaderStatusListener.DATA_LOADER_NOT_READY;
+    public static final int DATA_LOADER_RUNNING =
+            IDataLoaderStatusListener.DATA_LOADER_RUNNING;
+    public static final int DATA_LOADER_STOPPED =
+            IDataLoaderStatusListener.DATA_LOADER_STOPPED;
+    public static final int DATA_LOADER_SLOW_CONNECTION =
+            IDataLoaderStatusListener.DATA_LOADER_SLOW_CONNECTION;
+    public static final int DATA_LOADER_NO_CONNECTION =
+            IDataLoaderStatusListener.DATA_LOADER_NO_CONNECTION;
+    public static final int DATA_LOADER_CONNECTION_OK =
+            IDataLoaderStatusListener.DATA_LOADER_CONNECTION_OK;
+
+    @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.
+     */
+    public abstract static class 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.
+         *
+         * @param params    Data loader configuration parameters.
+         * @param connector IncFS API wrapper.
+         * @param listener  Used for reporting internal state to IncrementalService.
+         * @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.
+         */
+        public abstract boolean onCreate(@NonNull DataLoaderParams params,
+                @NonNull FileSystemConnector connector,
+                @NonNull StatusListener listener);
+
+        /**
+         * 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.
+         *
+         * @return True if Data Loader was able to start. False will be reported to
+         * IncrementalService and can cause an unmount of an IFS instance.
+         */
+        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();
+    }
+
+    /**
+     * DataLoader factory method.
+     *
+     * @return An instance of a DataLoader.
+     */
+    public abstract @Nullable DataLoader onCreateDataLoader();
+
+    /**
+     * @hide
+     */
+    public final @NonNull IBinder onBind(@NonNull Intent intent) {
+        return (IBinder) mBinder;
+    }
+
+    private class DataLoaderBinderService extends IDataLoader.Stub {
+        private int mId;
+
+        @Override
+        public void create(int id, @NonNull Bundle options,
+                @NonNull IDataLoaderStatusListener listener)
+                    throws IllegalArgumentException, RuntimeException {
+            mId = id;
+            final DataLoaderParamsParcel params =  options.getParcelable("params");
+            if (params == null) {
+                throw new IllegalArgumentException("Must specify Incremental data loader params");
+            }
+            final FileSystemControlParcel control =
+                    options.getParcelable("control");
+            if (control == null) {
+                throw new IllegalArgumentException("Must specify Incremental control parcel");
+            }
+            mStatusListener = listener;
+            try {
+                if (!nativeCreateDataLoader(id, control, params, listener)) {
+                    Slog.e(TAG, "Failed to create native loader for " + mId);
+                }
+            } catch (Exception 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.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);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void start(List<InstallationFile> fileInfos) {
+            if (!nativeStartDataLoader(mId)) {
+                Slog.e(TAG, "Failed to start loader: loader not found for " + mId);
+            }
+        }
+
+        @Override
+        public void stop() {
+            if (!nativeStopDataLoader(mId)) {
+                Slog.w(TAG, "Failed to stop loader: loader not found for " + mId);
+            }
+        }
+
+        @Override
+        public void destroy() {
+            if (!nativeDestroyDataLoader(mId)) {
+                Slog.w(TAG, "Failed to destroy loader: loader not found for " + mId);
+            }
+        }
+    }
+
+    /**
+     *
+     * Used by the DataLoaderService implementations.
+     *
+     * @hide
+     *
+     * TODO(b/136132412) Should be @SystemApi
+     */
+    public static final class FileSystemConnector {
+        /**
+         * Creates a wrapper for an installation session connector.
+         * @hide
+         */
+        FileSystemConnector(IPackageInstallerSessionFileSystemConnector connector) {
+            mConnector = connector;
+        }
+
+        /**
+         * 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.
+         */
+        public void writeData(String name, long offsetBytes, long lengthBytes,
+                ParcelFileDescriptor incomingFd) throws IOException {
+            try {
+                mConnector.writeData(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
+     * TODO(b/136132412) Should be @SystemApi
+     */
+    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,
+            @NonNull DataLoaderParamsParcel params,
+            IDataLoaderStatusListener listener);
+
+    private native boolean nativeStartDataLoader(int storageId);
+
+    private native boolean nativeStopDataLoader(int storageId);
+
+    private native boolean nativeDestroyDataLoader(int storageId);
+
+    private static native boolean nativeReportStatus(long nativeInstance, int status);
+}
diff --git a/core/java/android/service/incremental/IncrementalDataLoaderService.java b/core/java/android/service/incremental/IncrementalDataLoaderService.java
deleted file mode 100644
index c4a06c8..0000000
--- a/core/java/android/service/incremental/IncrementalDataLoaderService.java
+++ /dev/null
@@ -1,563 +0,0 @@
-/*
- * 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.service.incremental;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Service;
-import android.content.Intent;
-import android.content.pm.IDataLoader;
-import android.content.pm.IDataLoaderStatusListener;
-import android.content.pm.InstallationFile;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.incremental.IncrementalDataLoaderParams;
-import android.os.incremental.IncrementalDataLoaderParamsParcel;
-import android.os.incremental.IncrementalFileSystemControlParcel;
-import android.os.incremental.NamedParcelFileDescriptor;
-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;
-
-
-/**
- * The base class for implementing data loader service to control data loaders. Expecting
- * Incremental Service to bind to a children class of this.
- *
- * @hide
- *
- * Hide for now, should be @SystemApi
- * TODO(b/136132412): update with latest API design
- */
-public abstract class IncrementalDataLoaderService extends Service {
-    private static final String TAG = "IncrementalDataLoaderService";
-    private final DataLoaderBinderService mBinder = new DataLoaderBinderService();
-
-    public static final int DATA_LOADER_READY =
-            IDataLoaderStatusListener.DATA_LOADER_READY;
-    public static final int DATA_LOADER_NOT_READY =
-            IDataLoaderStatusListener.DATA_LOADER_NOT_READY;
-    public static final int DATA_LOADER_RUNNING =
-            IDataLoaderStatusListener.DATA_LOADER_RUNNING;
-    public static final int DATA_LOADER_STOPPED =
-            IDataLoaderStatusListener.DATA_LOADER_STOPPED;
-    public static final int DATA_LOADER_SLOW_CONNECTION =
-            IDataLoaderStatusListener.DATA_LOADER_SLOW_CONNECTION;
-    public static final int DATA_LOADER_NO_CONNECTION =
-            IDataLoaderStatusListener.DATA_LOADER_NO_CONNECTION;
-    public static final int DATA_LOADER_CONNECTION_OK =
-            IDataLoaderStatusListener.DATA_LOADER_CONNECTION_OK;
-
-    @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 {
-    }
-
-    /**
-     * Incremental FileSystem block size.
-     **/
-    public static final int BLOCK_SIZE = 4096;
-
-    /**
-     * Data compression types
-     */
-    public static final int COMPRESSION_NONE = 0;
-    public static final int COMPRESSION_LZ4 = 1;
-
-    /**
-     * @hide
-     */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({COMPRESSION_NONE, COMPRESSION_LZ4})
-    public @interface CompressionType {
-    }
-
-    /**
-     * Managed DataLoader interface. Each instance corresponds to a single Incremental File System
-     * instance.
-     */
-    public abstract static class 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.
-         *
-         * @param params    Data loader configuration parameters.
-         * @param connector IncFS API wrapper.
-         * @param listener  Used for reporting internal state to IncrementalService.
-         * @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.
-         */
-        public abstract boolean onCreate(@NonNull IncrementalDataLoaderParams params,
-                @NonNull FileSystemConnector connector,
-                @NonNull StatusListener listener);
-
-        /**
-         * 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.
-         *
-         * @return True if Data Loader was able to start. False will be reported to
-         * IncrementalService and can cause an unmount of an IFS instance.
-         */
-        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();
-
-        /**
-         * IFS reports a pending read each time the page needs to be loaded, e.g. missing.
-         *
-         * @param pendingReads array of blocks to load.
-         *
-         * TODO(b/136132412): avoid using collections
-         */
-        public abstract void onPendingReads(
-                @NonNull Collection<FileSystemConnector.PendingReadInfo> pendingReads);
-
-        /**
-         * IFS tracks all reads and reports them using onPageReads.
-         *
-         * @param reads array of blocks.
-         *
-         * TODO(b/136132412): avoid using collections
-         */
-        public abstract void onPageReads(@NonNull Collection<FileSystemConnector.ReadInfo> reads);
-
-        /**
-         * IFS informs data loader that a new file has been created.
-         * <p>
-         * This can be used to prepare the data loader before it starts loading data. For example,
-         * the data loader can keep a list of newly created files, so that it knows what files to
-         * download from the server.
-         *
-         * @param inode    The inode value of the new file.
-         * @param metadata The metadata of the new file.
-         */
-        public abstract void onFileCreated(long inode, byte[] metadata);
-    }
-
-    /**
-     * DataLoader factory method.
-     *
-     * @return An instance of a DataLoader.
-     */
-    public abstract @Nullable DataLoader onCreateDataLoader();
-
-    /**
-     * @hide
-     */
-    public final @NonNull IBinder onBind(@NonNull Intent intent) {
-        return (IBinder) mBinder;
-    }
-
-    private class DataLoaderBinderService extends IDataLoader.Stub {
-        private int mId;
-
-        @Override
-        public void create(int id, @NonNull Bundle options,
-                @NonNull IDataLoaderStatusListener listener)
-                    throws IllegalArgumentException, RuntimeException {
-            mId = id;
-            final IncrementalDataLoaderParamsParcel params =  options.getParcelable("params");
-            if (params == null) {
-                throw new IllegalArgumentException("Must specify Incremental data loader params");
-            }
-            final IncrementalFileSystemControlParcel control =
-                    options.getParcelable("control");
-            if (control == null) {
-                throw new IllegalArgumentException("Must specify Incremental control parcel");
-            }
-            mStatusListener = listener;
-            try {
-                if (!nativeCreateDataLoader(id, control, params, listener)) {
-                    Slog.e(TAG, "Failed to create native loader for " + mId);
-                }
-            } catch (Exception ex) {
-                destroy();
-                throw new RuntimeException(ex);
-            } finally {
-                // Closing FDs.
-                if (control.cmd != null) {
-                    try {
-                        control.cmd.close();
-                    } catch (IOException e) {
-                        Slog.e(TAG, "Failed to close IncFs CMD file descriptor " + e);
-                    }
-                }
-                if (control.log != null) {
-                    try {
-                        control.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);
-                    }
-                }
-            }
-        }
-
-        @Override
-        public void start(List<InstallationFile> fileInfos) {
-            if (!nativeStartDataLoader(mId)) {
-                Slog.e(TAG, "Failed to start loader: loader not found for " + mId);
-            }
-        }
-
-        @Override
-        public void stop() {
-            if (!nativeStopDataLoader(mId)) {
-                Slog.w(TAG, "Failed to stop loader: loader not found for " + mId);
-            }
-        }
-
-        @Override
-        public void destroy() {
-            if (!nativeDestroyDataLoader(mId)) {
-                Slog.w(TAG, "Failed to destroy loader: loader not found for " + mId);
-            }
-        }
-
-        @Override
-        // TODO(b/136132412): remove this
-        public void onFileCreated(long inode, byte[] metadata) {
-            if (!nativeOnFileCreated(mId, inode, metadata)) {
-                Slog.w(TAG, "Failed to handle onFileCreated for storage:" + mId
-                        + " inode:" + inode);
-            }
-        }
-    }
-
-    /**
-     * IncFs API wrapper for writing pages and getting page missing info. Non-hidden methods are
-     * expected to be called by the IncrementalDataLoaderService implemented by developers.
-     *
-     * @hide
-     *
-     * TODO(b/136132412) Should be @SystemApi
-     */
-    public static final class FileSystemConnector {
-        /**
-         * Defines a block address. A block is the unit of data chunk that IncFs operates with.
-         *
-         * @hide
-         */
-        public static class BlockAddress {
-            /**
-             * Linux inode uniquely identifies file within a single IFS instance.
-             */
-            private final long mFileIno;
-            /**
-             * Index of a 4K block within a file.
-             */
-            private final int mBlockIndex;
-
-            public BlockAddress(long fileIno, int blockIndex) {
-                this.mFileIno = fileIno;
-                this.mBlockIndex = blockIndex;
-            }
-
-            public long getFileIno() {
-                return mFileIno;
-            }
-
-            public int getBlockIndex() {
-                return mBlockIndex;
-            }
-        }
-
-        /**
-         * A block is the unit of data chunk that IncFs operates with.
-         *
-         * @hide
-         */
-        public static class Block extends BlockAddress {
-            /**
-             * Data content of the block.
-             */
-            private final @NonNull byte[] mDataBytes;
-
-            public Block(long fileIno, int blockIndex, @NonNull byte[] dataBytes) {
-                super(fileIno, blockIndex);
-                this.mDataBytes = dataBytes;
-            }
-        }
-
-        /**
-         * Defines a page/block inside a file.
-         */
-        public static class DataBlock extends Block {
-            /**
-             * Compression type of the data block.
-             */
-            private final @CompressionType int mCompressionType;
-
-            public DataBlock(long fileIno, int blockIndex, @NonNull byte[] dataBytes,
-                    @CompressionType int compressionType) {
-                super(fileIno, blockIndex, dataBytes);
-                this.mCompressionType = compressionType;
-            }
-        }
-
-        /**
-         * Defines a hash block for a certain file. A hash block index is the index in an array of
-         * hashes which is the 1-d representation of the hash tree. One DataBlock might be
-         * associated with multiple HashBlocks.
-         */
-        public static class HashBlock extends Block {
-            public HashBlock(long fileIno, int blockIndex, @NonNull byte[] dataBytes) {
-                super(fileIno, blockIndex, dataBytes);
-            }
-        }
-
-        /**
-         * Information about a page that is pending to be read.
-         */
-        public static class PendingReadInfo extends BlockAddress {
-            PendingReadInfo(long fileIno, int blockIndex) {
-                super(fileIno, blockIndex);
-            }
-        }
-
-        /**
-         * Information about a page that is read.
-         */
-        public static class ReadInfo extends BlockAddress {
-            /**
-             * A monotonically increasing read timestamp.
-             */
-            private final long mTimePoint;
-            /**
-             * Number of blocks read starting from blockIndex.
-             */
-            private final int mBlockCount;
-
-            ReadInfo(long timePoint, long fileIno, int firstBlockIndex, int blockCount) {
-                super(fileIno, firstBlockIndex);
-                this.mTimePoint = timePoint;
-                this.mBlockCount = blockCount;
-            }
-
-            public long getTimePoint() {
-                return mTimePoint;
-            }
-
-            public int getBlockCount() {
-                return mBlockCount;
-            }
-        }
-
-        /**
-         * Defines the dynamic information about an IncFs file.
-         */
-        public static class FileInfo {
-            /**
-             * BitSet to show if any block is available at each block index.
-             */
-            private final @NonNull
-            byte[] mBlockBitmap;
-
-            /**
-             * @hide
-             */
-            public FileInfo(@NonNull byte[] blockBitmap) {
-                this.mBlockBitmap = blockBitmap;
-            }
-        }
-
-        /**
-         * Creates a wrapper for a native instance.
-         */
-        FileSystemConnector(long nativeInstance) {
-            mNativeInstance = nativeInstance;
-        }
-
-        /**
-         * Checks whether a range in a file if loaded.
-         *
-         * @param node  inode 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(long node, long start, long end) {
-            return nativeIsFileRangeLoadedNode(mNativeInstance, node, start, end);
-        }
-
-        /**
-         * Gets the metadata of a file.
-         *
-         * @param node inode of the file.
-         * @return The metadata object.
-         */
-        @NonNull
-        public byte[] getFileMetadata(long node) throws IOException {
-            final byte[] metadata = nativeGetFileMetadataNode(mNativeInstance, node);
-            if (metadata == null || metadata.length == 0) {
-                throw new IOException(
-                        "IncrementalFileSystem failed to obtain metadata for node: " + node);
-            }
-            return metadata;
-        }
-
-        /**
-         * Gets the dynamic information of a file, such as page bitmaps. Can be used to get missing
-         * page indices by the FileSystemConnector.
-         *
-         * @param node inode of the file.
-         * @return Dynamic file info.
-         */
-        @NonNull
-        public FileInfo getDynamicFileInfo(long node) throws IOException {
-            final byte[] blockBitmap = nativeGetFileInfoNode(mNativeInstance, node);
-            if (blockBitmap == null || blockBitmap.length == 0) {
-                throw new IOException(
-                        "IncrementalFileSystem failed to obtain dynamic file info for node: "
-                                + node);
-            }
-            return new FileInfo(blockBitmap);
-        }
-
-        /**
-         * Writes a page's data and/or hashes.
-         *
-         * @param dataBlocks the DataBlock objects that contain data block index and data bytes.
-         * @param hashBlocks the HashBlock objects that contain hash indices and hash bytes.
-         *
-         * TODO(b/136132412): change API to avoid dynamic allocation of data block objects
-         */
-        public void writeMissingData(@NonNull DataBlock[] dataBlocks,
-                @Nullable HashBlock[] hashBlocks) throws IOException {
-            if (!nativeWriteMissingData(mNativeInstance, dataBlocks, hashBlocks)) {
-                throw new IOException("IncrementalFileSystem failed to write missing data.");
-            }
-        }
-
-        /**
-         * Writes the signer block of a file. Expecting the connector to call this when it got
-         * signing data from data loader.
-         *
-         * @param node       the file to be written to.
-         * @param signerData the raw signer data byte array.
-         */
-        public void writeSignerData(long node, @NonNull byte[] signerData)
-                throws IOException {
-            if (!nativeWriteSignerDataNode(mNativeInstance, node, signerData)) {
-                throw new IOException(
-                        "IncrementalFileSystem failed to write signer data of node " + node);
-            }
-        }
-
-        private final long mNativeInstance;
-    }
-
-    /**
-     * Wrapper for native reporting DataLoader statuses.
-     *
-     * @hide
-     *
-     * TODO(b/136132412) Should be @SystemApi
-     */
-    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 IncrementalFileSystemControlParcel control,
-            @NonNull IncrementalDataLoaderParamsParcel params,
-            IDataLoaderStatusListener listener);
-
-    private native boolean nativeStartDataLoader(int storageId);
-
-    private native boolean nativeStopDataLoader(int storageId);
-
-    private native boolean nativeDestroyDataLoader(int storageId);
-
-    private static native boolean nativeOnFileCreated(int storageId,
-            long inode, byte[] metadata);
-
-    private static native boolean nativeIsFileRangeLoadedNode(
-            long nativeInstance, long node, long start, long end);
-
-    private static native boolean nativeWriteMissingData(
-            long nativeInstance, FileSystemConnector.DataBlock[] dataBlocks,
-            FileSystemConnector.HashBlock[] hashBlocks);
-
-    private static native boolean nativeWriteSignerDataNode(
-            long nativeInstance, long node, byte[] signerData);
-
-    private static native byte[] nativeGetFileMetadataNode(
-            long nativeInstance, long node);
-
-    private static native byte[] nativeGetFileInfoNode(
-            long nativeInstance, long node);
-
-    private static native boolean nativeReportStatus(long nativeInstance, int status);
-}
diff --git a/core/jni/android_service_DataLoaderService.cpp b/core/jni/android_service_DataLoaderService.cpp
index 4c0f55f..381b386 100644
--- a/core/jni/android_service_DataLoaderService.cpp
+++ b/core/jni/android_service_DataLoaderService.cpp
@@ -16,83 +16,18 @@
 
 #define LOG_TAG "dataloader-jni"
 
-#include <vector>
-
 #include "core_jni_helpers.h"
 #include "dataloader_ndk.h"
-#include "jni.h"
 
 namespace android {
 namespace {
 
-struct JniIds {
-    jfieldID dataBlockFileIno;
-    jfieldID dataBlockBlockIndex;
-    jfieldID dataBlockDataBytes;
-    jfieldID dataBlockCompressionType;
-
-    JniIds(JNIEnv* env) {
-        const auto dataBlock =
-                FindClassOrDie(env,
-                               "android/service/incremental/"
-                               "IncrementalDataLoaderService$FileSystemConnector$DataBlock");
-        dataBlockFileIno = GetFieldIDOrDie(env, dataBlock, "mFileIno", "J");
-        dataBlockBlockIndex =
-                GetFieldIDOrDie(env, dataBlock, "mBlockIndex", "I");
-        dataBlockDataBytes = GetFieldIDOrDie(env, dataBlock, "mDataBytes", "[B");
-        dataBlockCompressionType =
-                GetFieldIDOrDie(env, dataBlock, "mCompressionType", "I");
-    }
-};
-
-const JniIds& jniIds(JNIEnv* env) {
-    static const JniIds ids(env);
-    return ids;
-}
-
-class ScopedJniArrayCritical {
-public:
-    ScopedJniArrayCritical(JNIEnv* env, jarray array) : mEnv(env), mArr(array) {
-        mPtr = array ? env->GetPrimitiveArrayCritical(array, nullptr) : nullptr;
-    }
-    ~ScopedJniArrayCritical() {
-        if (mPtr) {
-            mEnv->ReleasePrimitiveArrayCritical(mArr, mPtr, 0);
-            mPtr = nullptr;
-        }
-    }
-
-    ScopedJniArrayCritical(const ScopedJniArrayCritical&) = delete;
-    void operator=(const ScopedJniArrayCritical&) = delete;
-
-    ScopedJniArrayCritical(ScopedJniArrayCritical&& other)
-        : mEnv(other.mEnv),
-          mArr(std::exchange(mArr, nullptr)),
-          mPtr(std::exchange(mPtr, nullptr)) {}
-    ScopedJniArrayCritical& operator=(ScopedJniArrayCritical&& other) {
-        mEnv = other.mEnv;
-        mArr = std::exchange(other.mArr, nullptr);
-        mPtr = std::exchange(other.mPtr, nullptr);
-        return *this;
-    }
-
-    void* ptr() const { return mPtr; }
-    jsize size() const { return mArr ? mEnv->GetArrayLength(mArr) : 0; }
-
-private:
-    JNIEnv* mEnv;
-    jarray mArr;
-    void* mPtr;
-};
-
 static jboolean nativeCreateDataLoader(JNIEnv* env,
                                        jobject thiz,
                                        jint storageId,
                                        jobject control,
                                        jobject params,
                                        jobject callback) {
-    ALOGE("nativeCreateDataLoader: %p/%d, %d, %p, %p, %p", thiz,
-          env->GetObjectRefType(thiz), storageId, params, control, callback);
     return DataLoaderService_OnCreate(env, thiz,
                      storageId, control, params, callback);
 }
@@ -100,130 +35,22 @@
 static jboolean nativeStartDataLoader(JNIEnv* env,
                                       jobject thiz,
                                       jint storageId) {
-    ALOGE("nativeStartDataLoader: %p/%d, %d", thiz, env->GetObjectRefType(thiz),
-          storageId);
     return DataLoaderService_OnStart(storageId);
 }
 
 static jboolean nativeStopDataLoader(JNIEnv* env,
                                      jobject thiz,
                                      jint storageId) {
-    ALOGE("nativeStopDataLoader: %p/%d, %d", thiz, env->GetObjectRefType(thiz),
-          storageId);
     return DataLoaderService_OnStop(storageId);
 }
 
 static jboolean nativeDestroyDataLoader(JNIEnv* env,
                                         jobject thiz,
                                         jint storageId) {
-    ALOGE("nativeDestroyDataLoader: %p/%d, %d", thiz,
-          env->GetObjectRefType(thiz), storageId);
     return DataLoaderService_OnDestroy(storageId);
 }
 
 
-static jboolean nativeOnFileCreated(JNIEnv* env,
-                                   jobject thiz,
-                                   jint storageId,
-                                   jlong inode,
-                                   jbyteArray metadata) {
-    ALOGE("nativeOnFileCreated: %p/%d, %d", thiz,
-          env->GetObjectRefType(thiz), storageId);
-    return DataLoaderService_OnFileCreated(storageId, inode, metadata);
-}
-
-static jboolean nativeIsFileRangeLoadedNode(JNIEnv* env,
-                                            jobject clazz,
-                                            jlong self,
-                                            jlong node,
-                                            jlong start,
-                                            jlong end) {
-    // TODO(b/136132412): implement this
-    return JNI_FALSE;
-}
-
-static jboolean nativeWriteMissingData(JNIEnv* env,
-                                       jobject clazz,
-                                       jlong self,
-                                       jobjectArray data_block,
-                                       jobjectArray hash_blocks) {
-    const auto& jni = jniIds(env);
-    auto length = env->GetArrayLength(data_block);
-    std::vector<incfs_new_data_block> instructions(length);
-
-    // May not call back into Java after even a single jniArrayCritical, so
-    // let's collect the Java pointers to byte buffers first and lock them in
-    // memory later.
-
-    std::vector<jbyteArray> blockBuffers(length);
-    for (int i = 0; i != length; ++i) {
-        auto& inst = instructions[i];
-        auto jniBlock = env->GetObjectArrayElement(data_block, i);
-        inst.file_ino = env->GetLongField(jniBlock, jni.dataBlockFileIno);
-        inst.block_index = env->GetIntField(jniBlock, jni.dataBlockBlockIndex);
-        blockBuffers[i] = (jbyteArray)env->GetObjectField(
-                jniBlock, jni.dataBlockDataBytes);
-        inst.compression = (incfs_compression_alg)env->GetIntField(
-                jniBlock, jni.dataBlockCompressionType);
-    }
-
-    std::vector<ScopedJniArrayCritical> jniScopedArrays;
-    jniScopedArrays.reserve(length);
-    for (int i = 0; i != length; ++i) {
-        auto buffer = blockBuffers[i];
-        jniScopedArrays.emplace_back(env, buffer);
-        auto& inst = instructions[i];
-        inst.data = (uint64_t)jniScopedArrays.back().ptr();
-        inst.data_len = jniScopedArrays.back().size();
-    }
-
-    auto connector = (DataLoaderFilesystemConnectorPtr)self;
-    if (auto err = DataLoader_FilesystemConnector_writeBlocks(
-                             connector, instructions.data(), length);
-        err < 0) {
-        jniScopedArrays.clear();
-        return JNI_FALSE;
-    }
-
-    return JNI_TRUE;
-}
-
-static jboolean nativeWriteSignerDataNode(JNIEnv* env,
-                                          jobject clazz,
-                                          jlong self,
-                                          jstring relative_path,
-                                          jbyteArray signer_data) {
-    // TODO(b/136132412): implement this
-    return JNI_TRUE;
-}
-
-static jbyteArray nativeGetFileMetadataNode(JNIEnv* env,
-                                            jobject clazz,
-                                            jlong self,
-                                            jlong inode) {
-    auto connector = (DataLoaderFilesystemConnectorPtr)self;
-    std::vector<char> metadata(INCFS_MAX_FILE_ATTR_SIZE);
-    size_t size = metadata.size();
-    if (DataLoader_FilesystemConnector_getRawMetadata(connector, inode,
-                  metadata.data(), &size) < 0) {
-        size = 0;
-    }
-    metadata.resize(size);
-
-    auto buffer = env->NewByteArray(metadata.size());
-    env->SetByteArrayRegion(buffer, 0, metadata.size(),
-                            (jbyte*)metadata.data());
-    return buffer;
-}
-
-static jbyteArray nativeGetFileInfoNode(JNIEnv* env,
-                                        jobject clazz,
-                                        jlong self,
-                                        jlong inode) {
-    // TODO(b/136132412): implement this
-    return nullptr;
-}
-
 static jboolean nativeReportStatus(JNIEnv* env,
                                    jobject clazz,
                                    jlong self,
@@ -235,34 +62,21 @@
 
 static const JNINativeMethod dlc_method_table[] = {
         {"nativeCreateDataLoader",
-         "(ILandroid/os/incremental/IncrementalFileSystemControlParcel;"
-         "Landroid/os/incremental/IncrementalDataLoaderParamsParcel;"
+         "(ILandroid/content/pm/FileSystemControlParcel;"
+         "Landroid/content/pm/DataLoaderParamsParcel;"
          "Landroid/content/pm/IDataLoaderStatusListener;)Z",
          (void*)nativeCreateDataLoader},
         {"nativeStartDataLoader", "(I)Z", (void*)nativeStartDataLoader},
         {"nativeStopDataLoader", "(I)Z", (void*)nativeStopDataLoader},
         {"nativeDestroyDataLoader", "(I)Z", (void*)nativeDestroyDataLoader},
-        {"nativeIsFileRangeLoadedNode", "(JJJJ)Z",
-         (void*)nativeIsFileRangeLoadedNode},
-        {"nativeWriteMissingData",
-         "(J[Landroid/service/incremental/"
-         "IncrementalDataLoaderService$FileSystemConnector$DataBlock;[Landroid/service/incremental/"
-         "IncrementalDataLoaderService$FileSystemConnector$HashBlock;)Z",
-         (void*)nativeWriteMissingData},
-        {"nativeWriteSignerDataNode", "(JJ[B)Z",
-         (void*)nativeWriteSignerDataNode},
-        {"nativeGetFileMetadataNode", "(JJ)[B",
-         (void*)nativeGetFileMetadataNode},
-        {"nativeGetFileInfoNode", "(JJ)[B", (void*)nativeGetFileInfoNode},
         {"nativeReportStatus", "(JI)Z", (void*)nativeReportStatus},
-        {"nativeOnFileCreated", "(IJ[B)Z", (void*)nativeOnFileCreated},
 };
 
 }  // namespace
 
 int register_android_service_DataLoaderService(JNIEnv* env) {
     return jniRegisterNativeMethods(env,
-                                    "android/service/incremental/IncrementalDataLoaderService",
+                                    "android/service/dataloader/DataLoaderService",
                                     dlc_method_table, NELEM(dlc_method_table));
 }
 
diff --git a/media/jni/android_media_MediaMetricsJNI.cpp b/media/jni/android_media_MediaMetricsJNI.cpp
index e17a617..37aca08 100644
--- a/media/jni/android_media_MediaMetricsJNI.cpp
+++ b/media/jni/android_media_MediaMetricsJNI.cpp
@@ -20,6 +20,7 @@
 #include <jni.h>
 #include <media/MediaMetricsItem.h>
 #include <nativehelper/JNIHelp.h>
+#include <variant>
 
 #include "android_media_MediaMetricsJNI.h"
 #include "android_os_Parcel.h"
@@ -74,6 +75,23 @@
     }
 
     template<>
+    void put(jstring keyName, const std::string& value) {
+        env->CallVoidMethod(bundle, putStringID, keyName, env->NewStringUTF(value.c_str()));
+    }
+
+    template<>
+    void put(jstring keyName, const std::pair<int64_t, int64_t>& value) {
+        ; // rate is currently ignored
+    }
+
+    template<>
+    void put(jstring keyName, const std::monostate& value) {
+        ; // none is currently ignored
+    }
+
+    // string char * helpers
+
+    template<>
     void put(jstring keyName, const char * const& value) {
         env->CallVoidMethod(bundle, putStringID, keyName, env->NewStringUTF(value));
     }
@@ -83,11 +101,6 @@
         env->CallVoidMethod(bundle, putStringID, keyName, env->NewStringUTF(value));
     }
 
-    template<>
-    void put(jstring keyName, const std::pair<int64_t, int64_t>& value) {
-        ; // rate is currently ignored
-    }
-
     // We allow both jstring and non-jstring variants.
     template<typename T>
     void put(const char *keyName, const T& value) {
diff --git a/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java b/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java
index 1f88114..bd5b795 100644
--- a/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java
+++ b/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java
@@ -16,10 +16,10 @@
 
 package com.android.incremental.nativeadb;
 
-import android.service.incremental.IncrementalDataLoaderService;
+import android.service.dataloader.DataLoaderService;
 
 /** This code is used for testing only. */
-public class NativeAdbDataLoaderService extends IncrementalDataLoaderService {
+public class NativeAdbDataLoaderService extends DataLoaderService {
     public static final String TAG = "NativeAdbDataLoaderService";
     static {
         System.loadLibrary("nativeadbdataloaderservice_jni");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java
index a1cfb54..f0a003f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java
@@ -20,9 +20,12 @@
 import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpList;
 import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_BUILD_PENDING;
 import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_BUILD_STARTED;
-import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_FILTERING;
 import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_FINALIZING;
+import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_GROUPING;
 import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_IDLE;
+import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_PRE_GROUP_FILTERING;
+import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_PRE_RENDER_FILTERING;
+import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_RESETTING;
 import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_SORTING;
 import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_TRANSFORMING;
 
@@ -63,16 +66,17 @@
     private final SystemClock mSystemClock;
     private final NotifLog mNotifLog;
 
-    private final List<ListEntry> mNotifList = new ArrayList<>();
+    private List<ListEntry> mNotifList = new ArrayList<>();
+    private List<ListEntry> mNewNotifList = new ArrayList<>();
 
     private final PipelineState mPipelineState = new PipelineState();
     private final Map<String, GroupEntry> mGroups = new ArrayMap<>();
     private Collection<NotificationEntry> mAllEntries = Collections.emptyList();
-    private final List<ListEntry> mNewEntries = new ArrayList<>();
     private int mIterationCount = 0;
 
-    private final List<NotifFilter> mNotifFilters = new ArrayList<>();
+    private final List<NotifFilter> mNotifPreGroupFilters = new ArrayList<>();
     private final List<NotifPromoter> mNotifPromoters = new ArrayList<>();
+    private final List<NotifFilter> mNotifPreRenderFilters = new ArrayList<>();
     private final List<NotifComparator> mNotifComparators = new ArrayList<>();
     private SectionsProvider mSectionsProvider = new DefaultSectionsProvider();
 
@@ -138,12 +142,21 @@
     }
 
     @Override
-    public void addFilter(NotifFilter filter) {
+    public void addPreGroupFilter(NotifFilter filter) {
         Assert.isMainThread();
         mPipelineState.requireState(STATE_IDLE);
 
-        mNotifFilters.add(filter);
-        filter.setInvalidationListener(this::onFilterInvalidated);
+        mNotifPreGroupFilters.add(filter);
+        filter.setInvalidationListener(this::onPreGroupFilterInvalidated);
+    }
+
+    @Override
+    public void addPreRenderFilter(NotifFilter filter) {
+        Assert.isMainThread();
+        mPipelineState.requireState(STATE_IDLE);
+
+        mNotifPreRenderFilters.add(filter);
+        filter.setInvalidationListener(this::onPreRenderFilterInvalidated);
     }
 
     @Override
@@ -202,15 +215,15 @@
                 }
             };
 
-    private void onFilterInvalidated(NotifFilter filter) {
+    private void onPreGroupFilterInvalidated(NotifFilter filter) {
         Assert.isMainThread();
 
-        mNotifLog.log(NotifEvent.FILTER_INVALIDATED, String.format(
+        mNotifLog.log(NotifEvent.PRE_GROUP_FILTER_INVALIDATED, String.format(
                 "Filter \"%s\" invalidated; pipeline state is %d",
                 filter.getName(),
                 mPipelineState.getState()));
 
-        rebuildListIfBefore(STATE_FILTERING);
+        rebuildListIfBefore(STATE_PRE_GROUP_FILTERING);
     }
 
     private void onPromoterInvalidated(NotifPromoter filter) {
@@ -235,6 +248,17 @@
         rebuildListIfBefore(STATE_SORTING);
     }
 
+    private void onPreRenderFilterInvalidated(NotifFilter filter) {
+        Assert.isMainThread();
+
+        mNotifLog.log(NotifEvent.PRE_RENDER_FILTER_INVALIDATED, String.format(
+                "Filter \"%s\" invalidated; pipeline state is %d",
+                filter.getName(),
+                mPipelineState.getState()));
+
+        rebuildListIfBefore(STATE_PRE_RENDER_FILTERING);
+    }
+
     private void onNotifComparatorInvalidated(NotifComparator comparator) {
         Assert.isMainThread();
 
@@ -247,6 +271,17 @@
     }
 
     /**
+     * Points mNotifList to the list stored in mNewNotifList.
+     * Reuses the (emptied) mNotifList as mNewNotifList.
+     */
+    private void applyNewNotifList() {
+        mNotifList.clear();
+        List<ListEntry> emptyList = mNotifList;
+        mNotifList = mNewNotifList;
+        mNewNotifList = emptyList;
+    }
+
+    /**
      * The core algorithm of the pipeline. See the top comment in {@link NotifListBuilder} for
      * details on our contracts with other code.
      *
@@ -261,35 +296,47 @@
         mPipelineState.requireIsBefore(STATE_BUILD_STARTED);
         mPipelineState.setState(STATE_BUILD_STARTED);
 
-        // Step 1: Filtering and initial grouping
-        // Filter out any notifs that shouldn't be shown right now and cluster any that are part of
-        // a group
-        mPipelineState.incrementTo(STATE_FILTERING);
-        mNotifList.clear();
-        mNewEntries.clear();
-        filterAndGroup(mAllEntries, mNotifList, mNewEntries);
-        pruneIncompleteGroups(mNotifList, mNewEntries);
+        // Step 1: Reset notification states
+        mPipelineState.incrementTo(STATE_RESETTING);
+        resetNotifs();
 
-        // Step 2: Group transforming
+        // Step 2: Filter out any notifications that shouldn't be shown right now
+        mPipelineState.incrementTo(STATE_PRE_GROUP_FILTERING);
+        filterNotifs(mAllEntries, mNotifList, mNotifPreGroupFilters);
+
+        // Step 3: Group notifications with the same group key and set summaries
+        mPipelineState.incrementTo(STATE_GROUPING);
+        groupNotifs(mNotifList, mNewNotifList);
+        applyNewNotifList();
+        pruneIncompleteGroups(mNotifList);
+
+        // Step 4: Group transforming
         // Move some notifs out of their groups and up to top-level (mostly used for heads-upping)
-        dispatchOnBeforeTransformGroups(mReadOnlyNotifList, mNewEntries);
+        dispatchOnBeforeTransformGroups(mReadOnlyNotifList);
         mPipelineState.incrementTo(STATE_TRANSFORMING);
         promoteNotifs(mNotifList);
-        pruneIncompleteGroups(mNotifList, mNewEntries);
+        pruneIncompleteGroups(mNotifList);
 
-        // Step 3: Sort
+        // Step 5: Sort
         // Assign each top-level entry a section, then sort the list by section and then within
         // section by our list of custom comparators
         dispatchOnBeforeSort(mReadOnlyNotifList);
         mPipelineState.incrementTo(STATE_SORTING);
         sortList();
 
-        // Step 4: Lock in our group structure and log anything that's changed since the last run
+        // Step 6: Filter out entries after pre-group filtering, grouping, promoting and sorting
+        // Now filters can see grouping information to determine whether to filter or not
+        mPipelineState.incrementTo(STATE_PRE_RENDER_FILTERING);
+        filterNotifs(mNotifList, mNewNotifList, mNotifPreRenderFilters);
+        applyNewNotifList();
+        pruneIncompleteGroups(mNotifList);
+
+        // Step 7: Lock in our group structure and log anything that's changed since the last run
         mPipelineState.incrementTo(STATE_FINALIZING);
         logParentingChanges();
         freeEmptyGroups();
 
-        // Step 5: Dispatch the new list, first to any listeners and then to the view layer
+        // Step 6: Dispatch the new list, first to any listeners and then to the view layer
         mNotifLog.log(NotifEvent.DISPATCH_FINAL_LIST, "List finalized, is:\n"
                 + dumpList(mNotifList));
         dispatchOnBeforeRenderList(mReadOnlyNotifList);
@@ -297,20 +344,14 @@
             mOnRenderListListener.onRenderList(mReadOnlyNotifList);
         }
 
-        // Step 6: We're done!
+        // Step 7: We're done!
         mNotifLog.log(NotifEvent.LIST_BUILD_COMPLETE,
                 "Notif list build #" + mIterationCount + " completed");
         mPipelineState.setState(STATE_IDLE);
         mIterationCount++;
     }
 
-    private void filterAndGroup(
-            Collection<NotificationEntry> entries,
-            List<ListEntry> out,
-            List<ListEntry> newlyVisibleEntries) {
-
-        long now = mSystemClock.uptimeMillis();
-
+    private void resetNotifs() {
         for (GroupEntry group : mGroups.values()) {
             group.setPreviousParent(group.getParent());
             group.setParent(null);
@@ -318,22 +359,57 @@
             group.setSummary(null);
         }
 
-        for (NotificationEntry entry : entries) {
+        for (NotificationEntry entry : mAllEntries) {
             entry.setPreviousParent(entry.getParent());
             entry.setParent(null);
 
-            // See if we should filter out this notification
-            boolean shouldFilterOut = applyFilters(entry, now);
-            if (shouldFilterOut) {
-                continue;
-            }
-
             if (entry.mFirstAddedIteration == -1) {
                 entry.mFirstAddedIteration = mIterationCount;
-                newlyVisibleEntries.add(entry);
             }
+        }
 
-            // Otherwise, group it
+        mNotifList.clear();
+    }
+
+    private void filterNotifs(Collection<? extends ListEntry> entries,
+            List<ListEntry> out, List<NotifFilter> filters) {
+        final long now = mSystemClock.uptimeMillis();
+        for (ListEntry entry : entries)  {
+            if (entry instanceof GroupEntry) {
+                final GroupEntry groupEntry = (GroupEntry) entry;
+
+                // apply filter on its summary
+                final NotificationEntry summary = groupEntry.getRepresentativeEntry();
+                if (applyFilters(summary, now, filters)) {
+                    groupEntry.setSummary(null);
+                    annulAddition(summary);
+                }
+
+                // apply filter on its children
+                final List<NotificationEntry> children = groupEntry.getRawChildren();
+                for (int j = children.size() - 1; j >= 0; j--) {
+                    final NotificationEntry child = children.get(j);
+                    if (applyFilters(child, now, filters)) {
+                        children.remove(child);
+                        annulAddition(child);
+                    }
+                }
+
+                out.add(groupEntry);
+            } else {
+                if (applyFilters((NotificationEntry) entry, now, filters)) {
+                    annulAddition(entry);
+                } else {
+                    out.add(entry);
+                }
+            }
+        }
+    }
+
+    private void groupNotifs(List<ListEntry> entries, List<ListEntry> out) {
+        for (ListEntry listEntry : entries) {
+            // since grouping hasn't happened yet, all notifs are NotificationEntries
+            NotificationEntry entry = (NotificationEntry) listEntry;
             if (entry.getSbn().isGroup()) {
                 final String topLevelKey = entry.getSbn().getGroupKey();
 
@@ -341,7 +417,6 @@
                 if (group == null) {
                     group = new GroupEntry(topLevelKey);
                     group.mFirstAddedIteration = mIterationCount;
-                    newlyVisibleEntries.add(group);
                     mGroups.put(topLevelKey, group);
                 }
                 if (group.getParent() == null) {
@@ -367,9 +442,9 @@
                         if (entry.getSbn().getPostTime()
                                 > existingSummary.getSbn().getPostTime()) {
                             group.setSummary(entry);
-                            annulAddition(existingSummary, out, newlyVisibleEntries);
+                            annulAddition(existingSummary, out);
                         } else {
-                            annulAddition(entry, out, newlyVisibleEntries);
+                            annulAddition(entry, out);
                         }
                     }
                 } else {
@@ -411,10 +486,7 @@
         }
     }
 
-    private void pruneIncompleteGroups(
-            List<ListEntry> shadeList,
-            List<ListEntry> newlyVisibleEntries) {
-
+    private void pruneIncompleteGroups(List<ListEntry> shadeList) {
         for (int i = 0; i < shadeList.size(); i++) {
             final ListEntry tle = shadeList.get(i);
 
@@ -431,7 +503,7 @@
                     shadeList.add(summary);
 
                     group.setSummary(null);
-                    annulAddition(group, shadeList, newlyVisibleEntries);
+                    annulAddition(group, shadeList);
 
                 } else if (group.getSummary() == null
                         || children.size() < MIN_CHILDREN_FOR_GROUP) {
@@ -444,7 +516,7 @@
                     if (group.getSummary() != null) {
                         final NotificationEntry summary = group.getSummary();
                         group.setSummary(null);
-                        annulAddition(summary, shadeList, newlyVisibleEntries);
+                        annulAddition(summary, shadeList);
                     }
 
                     for (int j = 0; j < children.size(); j++) {
@@ -454,7 +526,7 @@
                     }
                     children.clear();
 
-                    annulAddition(group, shadeList, newlyVisibleEntries);
+                    annulAddition(group, shadeList);
                 }
             }
         }
@@ -468,10 +540,7 @@
      * Before calling this method, the entry must already have been removed from its parent. If
      * it's a group, its summary must be null and its children must be empty.
      */
-    private void annulAddition(
-            ListEntry entry,
-            List<ListEntry> shadeList,
-            List<ListEntry> newlyVisibleEntries) {
+    private void annulAddition(ListEntry entry, List<ListEntry> shadeList) {
 
         // This function does very little, but if any of its assumptions are violated (and it has a
         // lot of them), it will put the system into an inconsistent state. So we check all of them
@@ -508,13 +577,18 @@
             }
         }
 
+        annulAddition(entry);
+
+    }
+
+    /**
+     * Erases bookkeeping traces stored on an entry when it is removed from the notif list.
+     * This can happen if the entry is removed from a group that was broken up or if the entry was
+     * filtered out during any of the filtering steps.
+     */
+    private void annulAddition(ListEntry entry) {
         entry.setParent(null);
         if (entry.mFirstAddedIteration == mIterationCount) {
-            if (!newlyVisibleEntries.remove(entry)) {
-                throw new IllegalStateException("Cannot late-filter entry " + entry.getKey() + " "
-                        + entry + " from " + newlyVisibleEntries + " "
-                        + entry.mFirstAddedIteration);
-            }
             entry.mFirstAddedIteration = -1;
         }
     }
@@ -606,8 +680,8 @@
         return cmp;
     };
 
-    private boolean applyFilters(NotificationEntry entry, long now) {
-        NotifFilter filter = findRejectingFilter(entry, now);
+    private boolean applyFilters(NotificationEntry entry, long now, List<NotifFilter> filters) {
+        NotifFilter filter = findRejectingFilter(entry, now, filters);
 
         if (filter != entry.mExcludingFilter) {
             if (entry.mExcludingFilter == null) {
@@ -637,9 +711,12 @@
         return filter != null;
     }
 
-    @Nullable private NotifFilter findRejectingFilter(NotificationEntry entry, long now) {
-        for (int i = 0; i < mNotifFilters.size(); i++) {
-            NotifFilter filter = mNotifFilters.get(i);
+    @Nullable private static NotifFilter findRejectingFilter(NotificationEntry entry, long now,
+            List<NotifFilter> filters) {
+        final int size = filters.size();
+
+        for (int i = 0; i < size; i++) {
+            NotifFilter filter = filters.get(i);
             if (filter.shouldFilterOut(entry, now)) {
                 return filter;
             }
@@ -691,12 +768,9 @@
         }
     }
 
-    private void dispatchOnBeforeTransformGroups(
-            List<ListEntry> entries,
-            List<ListEntry> newlyVisibleEntries) {
+    private void dispatchOnBeforeTransformGroups(List<ListEntry> entries) {
         for (int i = 0; i < mOnBeforeTransformGroupsListeners.size(); i++) {
-            mOnBeforeTransformGroupsListeners.get(i)
-                    .onBeforeTransformGroups(entries, newlyVisibleEntries);
+            mOnBeforeTransformGroupsListeners.get(i).onBeforeTransformGroups(entries);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
index b68cb0d..5e7dd98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
@@ -55,7 +55,7 @@
     public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
         mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
 
-        notifListBuilder.addFilter(mNotifFilter);
+        notifListBuilder.addPreGroupFilter(mNotifFilter);
     }
 
     private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java
index 378599b..ee841c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java
@@ -83,7 +83,7 @@
         mAppOpsController.addCallback(ForegroundServiceController.APP_OPS, this::onAppOpsChanged);
 
         // filter out foreground service notifications that aren't necessary anymore
-        notifListBuilder.addFilter(mNotifFilter);
+        notifListBuilder.addPreGroupFilter(mNotifFilter);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
index 232246e..9312c22 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
@@ -85,7 +85,7 @@
     @Override
     public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
         setupInvalidateNotifListCallbacks();
-        notifListBuilder.addFilter(mNotifFilter);
+        notifListBuilder.addPreRenderFilter(mNotifFilter);
     }
 
     private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
@@ -131,9 +131,8 @@
                     }
                 }
 
-                // ... neither this notification nor its summary have high enough priority
+                // ... neither this notification nor its group have high enough priority
                 // to be shown on the lockscreen
-                // TODO: grouping hasn't happened yet (b/145134683)
                 if (entry.getParent() != null) {
                     final GroupEntry parent = entry.getParent();
                     if (priorityExceedsLockscreenShowingThreshold(parent)) {
@@ -152,11 +151,10 @@
         }
         if (NotificationUtils.useNewInterruptionModel(mContext)
                 && hideSilentNotificationsOnLockscreen()) {
-            // TODO: make sure in the NewNotifPipeline that entry.isHighPriority() has been
-            //  correctly updated before reaching this point (b/145134683)
             return entry.isHighPriority();
         } else {
-            return !entry.getRepresentativeEntry().getRanking().isAmbient();
+            return entry.getRepresentativeEntry() != null
+                    && !entry.getRepresentativeEntry().getRanking().isAmbient();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
index 24e7a79..0751aa8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
@@ -46,7 +46,7 @@
     public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
         mStatusBarStateController.addCallback(mStatusBarStateCallback);
 
-        notifListBuilder.addFilter(mNotifFilter);
+        notifListBuilder.addPreGroupFilter(mNotifFilter);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifListBuilder.java
index 15d3b92..7580924 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifListBuilder.java
@@ -59,11 +59,12 @@
 public interface NotifListBuilder {
 
     /**
-     * Registers a filter with the pipeline. Filters are called on each notification in the order
-     * that they were registered. If any filter returns true, the notification is removed from the
-     * pipeline (and no other filters are called on that notif).
+     * Registers a filter with the pipeline before grouping, promoting and sorting occurs. Filters
+     * are called on each notification in the order that they were registered. If any filter
+     * returns true, the notification is removed from the pipeline (and no other filters are
+     * called on that notif).
      */
-    void addFilter(NotifFilter filter);
+    void addPreGroupFilter(NotifFilter filter);
 
     /**
      * Registers a promoter with the pipeline. Promoters are able to promote child notifications to
@@ -91,6 +92,15 @@
     void setComparators(List<NotifComparator> comparators);
 
     /**
+     * Registers a filter with the pipeline to filter right before rendering the list (after
+     * pre-group filtering, grouping, promoting and sorting occurs). Filters are
+     * called on each notification in the order that they were registered. If any filter returns
+     * true, the notification is removed from the pipeline (and no other filters are called on that
+     * notif).
+     */
+    void addPreRenderFilter(NotifFilter filter);
+
+    /**
      * Called after notifications have been filtered and after the initial grouping has been
      * performed but before NotifPromoters have had a chance to promote children out of groups.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java
index 170ff48..d7a0815 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java
@@ -33,8 +33,6 @@
      * @param list The current filtered and grouped list of (top-level) entries. Note that this is
      *             a live view into the current notif list and will change as the list moves through
      *             the pipeline.
-     * @param newlyVisibleEntries The list of all entries (both top-level and children) who have
-     *                            been added to the list for the first time.
      */
-    void onBeforeTransformGroups(List<ListEntry> list, List<ListEntry> newlyVisibleEntries);
+    void onBeforeTransformGroups(List<ListEntry> list);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
index ad4bbd9..85f828d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
@@ -78,18 +78,24 @@
     public static final int STATE_IDLE = 0;
     public static final int STATE_BUILD_PENDING = 1;
     public static final int STATE_BUILD_STARTED = 2;
-    public static final int STATE_FILTERING = 3;
-    public static final int STATE_TRANSFORMING = 4;
-    public static final int STATE_SORTING = 5;
-    public static final int STATE_FINALIZING = 6;
+    public static final int STATE_RESETTING = 3;
+    public static final int STATE_PRE_GROUP_FILTERING = 4;
+    public static final int STATE_GROUPING = 5;
+    public static final int STATE_TRANSFORMING = 6;
+    public static final int STATE_SORTING = 7;
+    public static final int STATE_PRE_RENDER_FILTERING = 8;
+    public static final int STATE_FINALIZING = 9;
 
     @IntDef(prefix = { "STATE_" }, value = {
             STATE_IDLE,
             STATE_BUILD_PENDING,
             STATE_BUILD_STARTED,
-            STATE_FILTERING,
+            STATE_RESETTING,
+            STATE_PRE_GROUP_FILTERING,
+            STATE_GROUPING,
             STATE_TRANSFORMING,
             STATE_SORTING,
+            STATE_PRE_RENDER_FILTERING,
             STATE_FINALIZING,
     })
     @Retention(RetentionPolicy.SOURCE)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java
index 685eac8..e6189ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java
@@ -20,8 +20,8 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
 
 /**
- * Pluggable for participating in notif filtering. See
- * {@link NotifListBuilder#addFilter(NotifFilter)}.
+ * Pluggable for participating in notif filtering.
+ * See {@link NotifListBuilder#addPreGroupFilter} and {@link NotifListBuilder#addPreRenderFilter}.
  */
 public abstract class NotifFilter extends Pluggable<NotifFilter> {
     protected NotifFilter(String name) {
@@ -34,7 +34,11 @@
      * This doesn't necessarily mean that your filter will get called on every notification,
      * however. If another filter returns true before yours, we'll skip straight to the next notif.
      *
-     * @param entry The entry in question
+     * @param entry The entry in question.
+     *              If this filter is registered via {@link NotifListBuilder#addPreGroupFilter},
+     *              this entry will not have any grouping nor sorting information.
+     *              If this filter is registered via {@link NotifListBuilder#addPreRenderFilter},
+     *              this entry will have grouping and sorting information.
      * @param now A timestamp in SystemClock.uptimeMillis that represents "now" for the purposes of
      *            pipeline execution. This value will be the same for all pluggable calls made
      *            during this pipeline run, giving pluggables a stable concept of "now" to compare
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
index 3b06220..c18af80 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
@@ -88,13 +88,14 @@
             START_BUILD_LIST,
             DISPATCH_FINAL_LIST,
             LIST_BUILD_COMPLETE,
-            FILTER_INVALIDATED,
+            PRE_GROUP_FILTER_INVALIDATED,
             PROMOTER_INVALIDATED,
             SECTIONS_PROVIDER_INVALIDATED,
             COMPARATOR_INVALIDATED,
             PARENT_CHANGED,
             FILTER_CHANGED,
             PROMOTER_CHANGED,
+            PRE_RENDER_FILTER_INVALIDATED,
 
             // NotificationEntryManager events:
             NOTIF_ADDED,
@@ -127,6 +128,7 @@
                     "ParentChanged",
                     "FilterChanged",
                     "PromoterChanged",
+                    "FinalFilterInvalidated",
 
                     // NEM event labels:
                     "NotifAdded",
@@ -152,14 +154,15 @@
     public static final int START_BUILD_LIST = 2;
     public static final int DISPATCH_FINAL_LIST = 3;
     public static final int LIST_BUILD_COMPLETE = 4;
-    public static final int FILTER_INVALIDATED = 5;
+    public static final int PRE_GROUP_FILTER_INVALIDATED = 5;
     public static final int PROMOTER_INVALIDATED = 6;
     public static final int SECTIONS_PROVIDER_INVALIDATED = 7;
     public static final int COMPARATOR_INVALIDATED = 8;
     public static final int PARENT_CHANGED = 9;
     public static final int FILTER_CHANGED = 10;
     public static final int PROMOTER_CHANGED = 11;
-    private static final int TOTAL_LIST_BUILDER_EVENT_TYPES = 12;
+    public static final int PRE_RENDER_FILTER_INVALIDATED = 12;
+    private static final int TOTAL_LIST_BUILDER_EVENT_TYPES = 13;
 
     /**
      * Events related to {@link NotificationEntryManager}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java
index f751f30..9f90396 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java
@@ -28,6 +28,7 @@
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
@@ -335,7 +336,7 @@
         // GIVEN a notification that is initially added to the list
         PackageFilter filter = new PackageFilter(PACKAGE_2);
         filter.setEnabled(false);
-        mListBuilder.addFilter(filter);
+        mListBuilder.addPreGroupFilter(filter);
 
         addNotif(0, PACKAGE_1);
         addNotif(1, PACKAGE_2);
@@ -372,24 +373,54 @@
                 notif(2)
         );
 
-        // THEN the list of newly visible entries doesn't contain the summary or the group
-        assertEquals(
-                Arrays.asList(
-                        mEntrySet.get(0),
-                        mEntrySet.get(2)),
-                listener.newlyVisibleEntries
-        );
-
         // THEN the summary has a null parent and an unset firstAddedIteration
         assertNull(mEntrySet.get(1).getParent());
         assertEquals(-1, mEntrySet.get(1).mFirstAddedIteration);
     }
 
     @Test
-    public void testNotifsAreFiltered() {
+    public void testPreGroupNotifsAreFiltered() {
+        // GIVEN a PreGroupNotifFilter and PreRenderFilter that filters out the same package
+        NotifFilter preGroupFilter = spy(new PackageFilter(PACKAGE_2));
+        NotifFilter preRenderFilter = spy(new PackageFilter(PACKAGE_2));
+        mListBuilder.addPreGroupFilter(preGroupFilter);
+        mListBuilder.addPreRenderFilter(preRenderFilter);
+
+        // WHEN the pipeline is kicked off on a list of notifs
+        addNotif(0, PACKAGE_1);
+        addNotif(1, PACKAGE_2);
+        addNotif(2, PACKAGE_3);
+        addNotif(3, PACKAGE_2);
+        dispatchBuild();
+
+        // THEN the preGroupFilter is called on each notif in the original set
+        verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(0)), anyLong());
+        verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(1)), anyLong());
+        verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(2)), anyLong());
+        verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(3)), anyLong());
+
+        // THEN the preRenderFilter is only called on the notifications not already filtered out
+        verify(preRenderFilter).shouldFilterOut(eq(mEntrySet.get(0)), anyLong());
+        verify(preRenderFilter, never()).shouldFilterOut(eq(mEntrySet.get(1)), anyLong());
+        verify(preRenderFilter).shouldFilterOut(eq(mEntrySet.get(2)), anyLong());
+        verify(preRenderFilter, never()).shouldFilterOut(eq(mEntrySet.get(3)), anyLong());
+
+        // THEN the final list doesn't contain any filtered-out notifs
+        verifyBuiltList(
+                notif(0),
+                notif(2)
+        );
+
+        // THEN each filtered notif records the NotifFilter that did it
+        assertEquals(preGroupFilter, mEntrySet.get(1).mExcludingFilter);
+        assertEquals(preGroupFilter, mEntrySet.get(3).mExcludingFilter);
+    }
+
+    @Test
+    public void testPreRenderNotifsAreFiltered() {
         // GIVEN a NotifFilter that filters out a specific package
         NotifFilter filter1 = spy(new PackageFilter(PACKAGE_2));
-        mListBuilder.addFilter(filter1);
+        mListBuilder.addPreRenderFilter(filter1);
 
         // WHEN the pipeline is kicked off on a list of notifs
         addNotif(0, PACKAGE_1);
@@ -420,8 +451,8 @@
         // GIVEN two notif filters
         NotifFilter filter1 = spy(new PackageFilter(PACKAGE_2));
         NotifFilter filter2 = spy(new PackageFilter(PACKAGE_5));
-        mListBuilder.addFilter(filter1);
-        mListBuilder.addFilter(filter2);
+        mListBuilder.addPreGroupFilter(filter1);
+        mListBuilder.addPreGroupFilter(filter2);
 
         // WHEN the pipeline is kicked off on a list of notifs
         addNotif(0, PACKAGE_1);
@@ -521,7 +552,7 @@
     public void testNotifsAreSectioned() {
         // GIVEN a filter that removes all PACKAGE_4 notifs and a SectionsProvider that divides
         // notifs based on package name
-        mListBuilder.addFilter(new PackageFilter(PACKAGE_4));
+        mListBuilder.addPreGroupFilter(new PackageFilter(PACKAGE_4));
         final SectionsProvider sectionsProvider = spy(new PackageSectioner());
         mListBuilder.setSectionsProvider(sectionsProvider);
 
@@ -595,17 +626,19 @@
     @Test
     public void testListenersAndPluggablesAreFiredInOrder() {
         // GIVEN a bunch of registered listeners and pluggables
-        NotifFilter filter = spy(new PackageFilter(PACKAGE_1));
+        NotifFilter preGroupFilter = spy(new PackageFilter(PACKAGE_1));
         NotifPromoter promoter = spy(new IdPromoter(3));
         PackageSectioner sectioner = spy(new PackageSectioner());
         NotifComparator comparator = spy(new HypeComparator(PACKAGE_4));
-        mListBuilder.addFilter(filter);
+        NotifFilter preRenderFilter = spy(new PackageFilter(PACKAGE_5));
+        mListBuilder.addPreGroupFilter(preGroupFilter);
         mListBuilder.addOnBeforeTransformGroupsListener(mOnBeforeTransformGroupsListener);
         mListBuilder.addPromoter(promoter);
         mListBuilder.addOnBeforeSortListener(mOnBeforeSortListener);
         mListBuilder.setComparators(Collections.singletonList(comparator));
         mListBuilder.setSectionsProvider(sectioner);
         mListBuilder.addOnBeforeRenderListListener(mOnBeforeRenderListListener);
+        mListBuilder.addPreRenderFilter(preRenderFilter);
 
         // WHEN a few new notifs are added
         addNotif(0, PACKAGE_1);
@@ -619,25 +652,28 @@
 
         // THEN the pluggables and listeners are called in order
         InOrder inOrder = inOrder(
-                filter,
+                preGroupFilter,
                 mOnBeforeTransformGroupsListener,
                 promoter,
                 mOnBeforeSortListener,
                 sectioner,
                 comparator,
+                preRenderFilter,
                 mOnBeforeRenderListListener,
                 mOnRenderListListener);
 
-        inOrder.verify(filter, atLeastOnce())
+        inOrder.verify(preGroupFilter, atLeastOnce())
                 .shouldFilterOut(any(NotificationEntry.class), anyLong());
         inOrder.verify(mOnBeforeTransformGroupsListener)
-                .onBeforeTransformGroups(anyList(), anyList());
+                .onBeforeTransformGroups(anyList());
         inOrder.verify(promoter, atLeastOnce())
                 .shouldPromoteToTopLevel(any(NotificationEntry.class));
         inOrder.verify(mOnBeforeSortListener).onBeforeSort(anyList());
         inOrder.verify(sectioner, atLeastOnce()).getSection(any(ListEntry.class));
         inOrder.verify(comparator, atLeastOnce())
                 .compare(any(ListEntry.class), any(ListEntry.class));
+        inOrder.verify(preRenderFilter, atLeastOnce())
+                .shouldFilterOut(any(NotificationEntry.class), anyLong());
         inOrder.verify(mOnBeforeRenderListListener).onBeforeRenderList(anyList());
         inOrder.verify(mOnRenderListListener).onRenderList(anyList());
     }
@@ -650,7 +686,7 @@
         SectionsProvider sectionsProvider = new PackageSectioner();
         NotifComparator hypeComparator = new HypeComparator(PACKAGE_2);
 
-        mListBuilder.addFilter(packageFilter);
+        mListBuilder.addPreGroupFilter(packageFilter);
         mListBuilder.addPromoter(idPromoter);
         mListBuilder.setSectionsProvider(sectionsProvider);
         mListBuilder.setComparators(Collections.singletonList(hypeComparator));
@@ -686,9 +722,9 @@
         NotifFilter filter1 = spy(new PackageFilter(PACKAGE_5));
         NotifFilter filter2 = spy(new PackageFilter(PACKAGE_5));
         NotifFilter filter3 = spy(new PackageFilter(PACKAGE_5));
-        mListBuilder.addFilter(filter1);
-        mListBuilder.addFilter(filter2);
-        mListBuilder.addFilter(filter3);
+        mListBuilder.addPreGroupFilter(filter1);
+        mListBuilder.addPreGroupFilter(filter2);
+        mListBuilder.addPreGroupFilter(filter3);
 
         // GIVEN the SystemClock is set to a particular time:
         mSystemClock.setUptimeMillis(47);
@@ -708,13 +744,13 @@
     }
 
     @Test
-    public void testNewlyAddedEntries() {
+    public void testGroupTransformEntries() {
         // GIVEN a registered OnBeforeTransformGroupsListener
         RecordingOnBeforeTransformGroupsListener listener =
                 spy(new RecordingOnBeforeTransformGroupsListener());
         mListBuilder.addOnBeforeTransformGroupsListener(listener);
 
-        // Given some new notifs
+        // GIVEN some new notifs
         addNotif(0, PACKAGE_1);
         addGroupChild(1, PACKAGE_2, GROUP_1);
         addGroupSummary(2, PACKAGE_2, GROUP_1);
@@ -742,27 +778,18 @@
                         mEntrySet.get(0),
                         mBuiltList.get(1),
                         mEntrySet.get(4)
-                ),
-                Arrays.asList(
-                        mEntrySet.get(0),
-                        mEntrySet.get(1),
-                        mBuiltList.get(1),
-                        mEntrySet.get(2),
-                        mEntrySet.get(3),
-                        mEntrySet.get(4),
-                        mEntrySet.get(5)
                 )
         );
     }
 
     @Test
-    public void testNewlyAddedEntriesOnSecondRun() {
+    public void testGroupTransformEntriesOnSecondRun() {
         // GIVEN a registered OnBeforeTransformGroupsListener
         RecordingOnBeforeTransformGroupsListener listener =
                 spy(new RecordingOnBeforeTransformGroupsListener());
         mListBuilder.addOnBeforeTransformGroupsListener(listener);
 
-        // Given some notifs that have already been added (two of which are in malformed groups)
+        // GIVEN some notifs that have already been added (two of which are in malformed groups)
         addNotif(0, PACKAGE_1);
         addGroupChild(1, PACKAGE_2, GROUP_1);
         addGroupChild(2, PACKAGE_3, GROUP_2);
@@ -798,13 +825,6 @@
                         mEntrySet.get(1),
                         mBuiltList.get(2),
                         mEntrySet.get(7)
-                ),
-                Arrays.asList(
-                        mBuiltList.get(2),
-                        mEntrySet.get(4),
-                        mEntrySet.get(5),
-                        mEntrySet.get(6),
-                        mEntrySet.get(7)
                 )
         );
     }
@@ -841,19 +861,18 @@
     }
 
     @Test(expected = IllegalStateException.class)
-    public void testOutOfOrderFilterInvalidationThrows() {
-        // GIVEN a NotifFilter that gets invalidated during the grouping stage
+    public void testOutOfOrderPreGroupFilterInvalidationThrows() {
+        // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage
         NotifFilter filter = new PackageFilter(PACKAGE_5);
-        OnBeforeTransformGroupsListener listener =
-                (list, newlyVisibleEntries) -> filter.invalidateList();
-        mListBuilder.addFilter(filter);
+        OnBeforeTransformGroupsListener listener = (list) -> filter.invalidateList();
+        mListBuilder.addPreGroupFilter(filter);
         mListBuilder.addOnBeforeTransformGroupsListener(listener);
 
         // WHEN we try to run the pipeline and the filter is invalidated
         addNotif(0, PACKAGE_1);
         dispatchBuild();
 
-        // Then an exception is thrown
+        // THEN an exception is thrown
     }
 
     @Test(expected = IllegalStateException.class)
@@ -869,7 +888,7 @@
         addNotif(0, PACKAGE_1);
         dispatchBuild();
 
-        // Then an exception is thrown
+        // THEN an exception is thrown
     }
 
     @Test(expected = IllegalStateException.class)
@@ -885,7 +904,37 @@
         addNotif(0, PACKAGE_1);
         dispatchBuild();
 
-        // Then an exception is thrown
+        // THEN an exception is thrown
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testOutOfOrderPreRenderFilterInvalidationThrows() {
+        // GIVEN a PreRenderNotifFilter that gets invalidated during the finalizing stage
+        NotifFilter filter = new PackageFilter(PACKAGE_5);
+        OnBeforeRenderListListener listener = (list) -> filter.invalidateList();
+        mListBuilder.addPreRenderFilter(filter);
+        mListBuilder.addOnBeforeRenderListListener(listener);
+
+        // WHEN we try to run the pipeline and the PreRenderFilter is invalidated
+        addNotif(0, PACKAGE_1);
+        dispatchBuild();
+
+        // THEN an exception is thrown
+    }
+
+    @Test
+    public void testInOrderPreRenderFilter() {
+        // GIVEN a PreRenderFilter that gets invalidated during the grouping stage
+        NotifFilter filter = new PackageFilter(PACKAGE_5);
+        OnBeforeTransformGroupsListener listener = (list) -> filter.invalidateList();
+        mListBuilder.addPreRenderFilter(filter);
+        mListBuilder.addOnBeforeTransformGroupsListener(listener);
+
+        // WHEN we try to run the pipeline and the filter is invalidated
+        addNotif(0, PACKAGE_1);
+        dispatchBuild();
+
+        // THEN no exception thrown
     }
 
     /**
@@ -1177,13 +1226,9 @@
 
     private static class RecordingOnBeforeTransformGroupsListener
             implements OnBeforeTransformGroupsListener {
-        public List<ListEntry> newlyVisibleEntries;
 
         @Override
-        public void onBeforeTransformGroups(List<ListEntry> list,
-                List<ListEntry> newlyVisibleEntries) {
-            this.newlyVisibleEntries = newlyVisibleEntries;
-        }
+        public void onBeforeTransformGroups(List<ListEntry> list) { }
     }
 
     private static final String PACKAGE_1 = "com.test1";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
index 2c4361f..ea6c70a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
@@ -85,7 +85,7 @@
 
         ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
         mDeviceProvisionedCoordinator.attach(null, mNotifListBuilder);
-        verify(mNotifListBuilder, times(1)).addFilter(filterCaptor.capture());
+        verify(mNotifListBuilder, times(1)).addPreGroupFilter(filterCaptor.capture());
         mDeviceProvisionedFilter = filterCaptor.getValue();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
index b046b30..01bca0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
@@ -85,7 +85,7 @@
                 ArgumentCaptor.forClass(NotifLifetimeExtender.class);
 
         mForegroundCoordinator.attach(mNotifCollection, mNotifListBuilder);
-        verify(mNotifListBuilder, times(1)).addFilter(filterCaptor.capture());
+        verify(mNotifListBuilder, times(1)).addPreGroupFilter(filterCaptor.capture());
         verify(mNotifCollection, times(1)).addNotificationLifetimeExtender(
                 lifetimeExtenderCaptor.capture());
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
index f385178..979b8a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
@@ -86,7 +86,7 @@
 
         ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
         mKeyguardCoordinator.attach(null, mNotifListBuilder);
-        verify(mNotifListBuilder, times(1)).addFilter(filterCaptor.capture());
+        verify(mNotifListBuilder, times(1)).addPreRenderFilter(filterCaptor.capture());
         mKeyguardFilter = filterCaptor.getValue();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
index 6b46045..d3b16c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
@@ -63,7 +63,7 @@
 
         ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
         mRankingCoordinator.attach(null, mNotifListBuilder);
-        verify(mNotifListBuilder, times(1)).addFilter(filterCaptor.capture());
+        verify(mNotifListBuilder, times(1)).addPreGroupFilter(filterCaptor.capture());
         mRankingFilter = filterCaptor.getValue();
     }
 
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index 5e659b6..c5409f8 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -29,6 +29,10 @@
 
 import com.android.server.pm.UserManagerService;
 
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * The base class for services running in the system process. Override and implement
  * the lifecycle event callback methods as needed.
@@ -164,6 +168,25 @@
     }
 
     /**
+     * Helper method used to dump which users are {@link #onStartUser(UserInfo) supported}.
+     */
+    protected void dumpSupportedUsers(@NonNull PrintWriter pw, @NonNull String prefix) {
+        final List<UserInfo> allUsers = UserManager.get(mContext).getUsers();
+        final List<Integer> supportedUsers = new ArrayList<>(allUsers.size());
+        for (UserInfo user : allUsers) {
+            supportedUsers.add(user.id);
+        }
+        if (allUsers.isEmpty()) {
+            pw.print(prefix); pw.println("No supported users");
+        } else {
+            final int size = supportedUsers.size();
+            pw.print(prefix); pw.print(size); pw.print(" supported user");
+            if (size > 1) pw.print("s");
+            pw.print(": "); pw.println(supportedUsers);
+        }
+    }
+
+    /**
      * @deprecated subclasses should extend {@link #onStartUser(UserInfo)} instead (which by default
      * calls this method).
      */
diff --git a/services/core/java/com/android/server/incremental/IncrementalManagerService.java b/services/core/java/com/android/server/incremental/IncrementalManagerService.java
index 1b1a292..789551b 100644
--- a/services/core/java/com/android/server/incremental/IncrementalManagerService.java
+++ b/services/core/java/com/android/server/incremental/IncrementalManagerService.java
@@ -19,6 +19,8 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.content.pm.DataLoaderManager;
+import android.content.pm.DataLoaderParamsParcel;
+import android.content.pm.FileSystemControlParcel;
 import android.content.pm.IDataLoader;
 import android.content.pm.IDataLoaderStatusListener;
 import android.os.Bundle;
@@ -27,8 +29,6 @@
 import android.os.ServiceManager;
 import android.os.ShellCallback;
 import android.os.incremental.IIncrementalManager;
-import android.os.incremental.IncrementalDataLoaderParamsParcel;
-import android.os.incremental.IncrementalFileSystemControlParcel;
 import android.util.Slog;
 
 import java.io.FileDescriptor;
@@ -80,8 +80,8 @@
      * Finds data loader service provider and binds to it. This requires PackageManager.
      */
     @Override
-    public boolean prepareDataLoader(int mountId, IncrementalFileSystemControlParcel control,
-            IncrementalDataLoaderParamsParcel params,
+    public boolean prepareDataLoader(int mountId, FileSystemControlParcel control,
+            DataLoaderParamsParcel params,
             IDataLoaderStatusListener listener) {
         Bundle dataLoaderParams = new Bundle();
         dataLoaderParams.putCharSequence("packageName", params.packageName);
@@ -138,10 +138,6 @@
             Slog.e(TAG, "Failed to retrieve data loader for ID=" + mountId);
             return;
         }
-        try {
-            dataLoader.onFileCreated(inode, metadata);
-        } catch (RemoteException ex) {
-        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
index d35e806..6a8434a 100644
--- a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
+++ b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
@@ -24,6 +24,7 @@
 import android.content.IIntentSender;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.pm.DataLoaderParams;
 import android.content.pm.InstallationFile;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
@@ -31,7 +32,6 @@
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.ShellCommand;
-import android.os.incremental.IncrementalDataLoaderParams;
 import android.util.Slog;
 
 import java.io.FileDescriptor;
@@ -111,7 +111,7 @@
             pw.println("File names and sizes don't match.");
             return ERROR_DATA_LOADER_INIT;
         }
-        final IncrementalDataLoaderParams params = new IncrementalDataLoaderParams(
+        final DataLoaderParams params = new DataLoaderParams(
                 "", LOADER_PACKAGE_NAME, dataLoaderDynamicArgs);
         PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
                 PackageInstaller.SessionParams.MODE_FULL_INSTALL);
diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
index d71ffb7..58f6ba2 100644
--- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
@@ -762,6 +762,7 @@
             if (mUpdatingPackageNames != null) {
                 pw.print("Packages being updated: "); pw.println(mUpdatingPackageNames);
             }
+            dumpSupportedUsers(pw, prefix);
             if (mServiceNameResolver != null) {
                 pw.print(prefix); pw.print("Name resolver: ");
                 mServiceNameResolver.dumpShort(pw); pw.println();
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 286d291..dfdc29c 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -57,6 +57,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageInstallObserver2;
 import android.content.pm.IPackageInstallerSession;
+import android.content.pm.IPackageInstallerSessionFileSystemConnector;
 import android.content.pm.InstallationFile;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
@@ -1003,6 +1004,21 @@
         mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
     }
 
+    private class FileSystemConnector extends IPackageInstallerSessionFileSystemConnector.Stub {
+        @Override
+        public void writeData(String name, long offsetBytes, long lengthBytes,
+                ParcelFileDescriptor incomingFd) {
+            if (incomingFd == null) {
+                throw new IllegalArgumentException("incomingFd can't be null");
+            }
+            try {
+                doWriteInternal(name, offsetBytes, lengthBytes, incomingFd);
+            } catch (IOException e) {
+                throw ExceptionUtils.wrap(e);
+            }
+        }
+    }
+
     private class ChildStatusIntentReceiver {
         private final SparseIntArray mChildSessionsRemaining;
         private final IntentSender mStatusReceiver;
@@ -2405,7 +2421,7 @@
             return;
         }
 
-        FilesystemConnector connector = new FilesystemConnector();
+        FileSystemConnector connector = new FileSystemConnector();
 
         FileInfo[] addedFiles = mFiles.stream().filter(
                 file -> sAddedFilter.accept(new File(file.name))).toArray(FileInfo[]::new);
@@ -2430,19 +2446,11 @@
         }
     }
 
-    // TODO(b/146080380): implement DataLoader using Incremental infrastructure.
-    class FilesystemConnector {
-        void writeData(FileInfo fileInfo, long offset, long lengthBytes,
-                ParcelFileDescriptor incomingFd) throws IOException {
-            doWriteInternal(fileInfo.name, offset, lengthBytes, incomingFd);
-        }
-    }
-
     static class DataLoader {
         private ParcelFileDescriptor mInFd = null;
-        private FilesystemConnector mConnector = null;
+        private FileSystemConnector mConnector = null;
 
-        void onCreate(FilesystemConnector connector) throws IOException {
+        void onCreate(FileSystemConnector connector) throws IOException {
             mConnector = connector;
         }
 
@@ -2460,14 +2468,14 @@
                         return false;
                     }
                     ParcelFileDescriptor inFd = ParcelFileDescriptor.dup(mInFd.getFileDescriptor());
-                    mConnector.writeData(fileInfo, 0, fileInfo.lengthBytes, inFd);
+                    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, 0, localFile.length(), incomingFd);
+                        mConnector.writeData(fileInfo.name, 0, localFile.length(), incomingFd);
                     } finally {
                         IoUtils.closeQuietly(incomingFd);
                     }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 5fabdb6..3515cb7 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -84,7 +84,6 @@
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.IntArray;
-import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -1510,7 +1509,7 @@
     public void setUserIcon(@UserIdInt int userId, Bitmap bitmap) {
         checkManageUsersPermission("update users");
         if (hasUserRestriction(UserManager.DISALLOW_SET_USER_ICON, userId)) {
-            Log.w(LOG_TAG, "Cannot set user icon. DISALLOW_SET_USER_ICON is enabled.");
+            Slog.w(LOG_TAG, "Cannot set user icon. DISALLOW_SET_USER_ICON is enabled.");
             return;
         }
         mLocalService.setUserIcon(userId, bitmap);
@@ -1558,7 +1557,7 @@
             return ParcelFileDescriptor.open(
                     new File(iconPath), ParcelFileDescriptor.MODE_READ_ONLY);
         } catch (FileNotFoundException e) {
-            Log.e(LOG_TAG, "Couldn't find icon file", e);
+            Slog.e(LOG_TAG, "Couldn't find icon file", e);
         }
         return null;
     }
@@ -1656,7 +1655,7 @@
             }
         }
         if (DBG) {
-            Log.d(LOG_TAG, "setDevicePolicyUserRestrictions: "
+            Slog.d(LOG_TAG, "setDevicePolicyUserRestrictions: "
                     + " originatingUserId=" + originatingUserId
                     + " global=" + global + (globalChanged ? " (changed)" : "")
                     + " local=" + local + (localChanged ? " (changed)" : "")
@@ -1718,7 +1717,7 @@
     @GuardedBy("mRestrictionsLock")
     private void invalidateEffectiveUserRestrictionsLR(@UserIdInt int userId) {
         if (DBG) {
-            Log.d(LOG_TAG, "invalidateEffectiveUserRestrictions userId=" + userId);
+            Slog.d(LOG_TAG, "invalidateEffectiveUserRestrictions userId=" + userId);
         }
         mCachedEffectiveUserRestrictions.remove(userId);
     }
@@ -1940,7 +1939,7 @@
                     try {
                         mAppOpsService.setUserRestrictions(effective, mUserRestriconToken, userId);
                     } catch (RemoteException e) {
-                        Log.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
+                        Slog.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
                     }
                 }
             });
@@ -2012,7 +2011,7 @@
                 try {
                     runningUsers = ActivityManager.getService().getRunningUserIds();
                 } catch (RemoteException e) {
-                    Log.w(LOG_TAG, "Unable to access ActivityManagerService");
+                    Slog.w(LOG_TAG, "Unable to access ActivityManagerService");
                     return;
                 }
                 // Then re-calculate the effective restrictions and apply, only for running users.
@@ -2596,7 +2595,7 @@
                 }
             }
         } catch (Resources.NotFoundException e) {
-            Log.e(LOG_TAG, "Couldn't find resource: config_defaultFirstUserRestrictions", e);
+            Slog.e(LOG_TAG, "Couldn't find resource: config_defaultFirstUserRestrictions", e);
         }
 
         if (!restrictions.isEmpty()) {
@@ -3056,7 +3055,7 @@
                 ? UserManager.DISALLOW_ADD_MANAGED_PROFILE
                 : UserManager.DISALLOW_ADD_USER;
         if (hasUserRestriction(restriction, UserHandle.getCallingUserId())) {
-            Log.w(LOG_TAG, "Cannot add user. " + restriction + " is enabled.");
+            Slog.w(LOG_TAG, "Cannot add user. " + restriction + " is enabled.");
             return null;
         }
         return createUserInternalUnchecked(name, userType, flags, parentId,
@@ -3115,7 +3114,7 @@
         DeviceStorageMonitorInternal dsm = LocalServices
                 .getService(DeviceStorageMonitorInternal.class);
         if (dsm.isMemoryLow()) {
-            Log.w(LOG_TAG, "Cannot add user. Not enough space on disk.");
+            Slog.w(LOG_TAG, "Cannot add user. Not enough space on disk.");
             return null;
         }
 
@@ -3138,36 +3137,36 @@
                     if (parent == null) return null;
                 }
                 if (!preCreate && !canAddMoreUsersOfType(userTypeDetails)) {
-                    Log.e(LOG_TAG, "Cannot add more users of type " + userType
+                    Slog.e(LOG_TAG, "Cannot add more users of type " + userType
                             + ". Maximum number of that type already exists.");
                     return null;
                 }
                 // TODO(b/142482943): Perhaps let the following code apply to restricted users too.
                 if (isProfile && !canAddMoreProfilesToUser(userType, parentId, false)) {
-                    Log.e(LOG_TAG, "Cannot add more profiles of type " + userType
+                    Slog.e(LOG_TAG, "Cannot add more profiles of type " + userType
                             + " for user " + parentId);
                     return null;
                 }
                 if (!isGuest && !isProfile && !isDemo && isUserLimitReached()) {
                     // If we're not adding a guest/demo user or a profile and the 'user limit' has
                     // been reached, cannot add a user.
-                    Log.e(LOG_TAG, "Cannot add user. Maximum user limit is reached.");
+                    Slog.e(LOG_TAG, "Cannot add user. Maximum user limit is reached.");
                     return null;
                 }
                 // In legacy mode, restricted profile's parent can only be the owner user
                 if (isRestricted && !UserManager.isSplitSystemUser()
                         && (parentId != UserHandle.USER_SYSTEM)) {
-                    Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be owner");
+                    Slog.w(LOG_TAG, "Cannot add restricted profile - parent user must be owner");
                     return null;
                 }
                 if (isRestricted && UserManager.isSplitSystemUser()) {
                     if (parent == null) {
-                        Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be "
+                        Slog.w(LOG_TAG, "Cannot add restricted profile - parent user must be "
                                 + "specified");
                         return null;
                     }
                     if (!parent.info.canHaveProfile()) {
-                        Log.w(LOG_TAG, "Cannot add restricted profile - profiles cannot be "
+                        Slog.w(LOG_TAG, "Cannot add restricted profile - profiles cannot be "
                                 + "created for the specified parent user id " + parentId);
                         return null;
                     }
@@ -3318,7 +3317,7 @@
                     + Integer.toHexString(preCreatedUser.flags) + ").");
             return null;
         }
-        Log.i(LOG_TAG, "Reusing pre-created user " + preCreatedUser.id + " of type "
+        Slog.i(LOG_TAG, "Reusing pre-created user " + preCreatedUser.id + " of type "
                 + userType + " and bestowing on it flags " + UserInfo.flagsToString(flags));
         preCreatedUser.name = name;
         preCreatedUser.flags = newFlags;
@@ -3483,7 +3482,7 @@
         checkManageUsersPermission("Only the system can remove users");
         if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(
                 UserManager.DISALLOW_REMOVE_USER, false)) {
-            Log.w(LOG_TAG, "Cannot remove user. DISALLOW_REMOVE_USER is enabled.");
+            Slog.w(LOG_TAG, "Cannot remove user. DISALLOW_REMOVE_USER is enabled.");
             return false;
         }
 
@@ -3535,7 +3534,7 @@
         String restriction = isManagedProfile
                 ? UserManager.DISALLOW_REMOVE_MANAGED_PROFILE : UserManager.DISALLOW_REMOVE_USER;
         if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(restriction, false)) {
-            Log.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled.");
+            Slog.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled.");
             return false;
         }
         return removeUserUnchecked(userId);
@@ -3553,25 +3552,25 @@
             final UserData userData;
             int currentUser = ActivityManager.getCurrentUser();
             if (currentUser == userId) {
-                Log.w(LOG_TAG, "Current user cannot be removed.");
+                Slog.w(LOG_TAG, "Current user cannot be removed.");
                 return false;
             }
             synchronized (mPackagesLock) {
                 synchronized (mUsersLock) {
                     userData = mUsers.get(userId);
                     if (userId == UserHandle.USER_SYSTEM) {
-                        Log.e(LOG_TAG, "System user cannot be removed.");
+                        Slog.e(LOG_TAG, "System user cannot be removed.");
                         return false;
                     }
 
                     if (userData == null) {
-                        Log.e(LOG_TAG, String.format(
+                        Slog.e(LOG_TAG, String.format(
                                 "Cannot remove user %d, invalid user id provided.", userId));
                         return false;
                     }
 
                     if (mRemovingUserIds.get(userId)) {
-                        Log.e(LOG_TAG, String.format(
+                        Slog.e(LOG_TAG, String.format(
                                 "User %d is already scheduled for removal.", userId));
                         return false;
                     }
@@ -3591,7 +3590,7 @@
             try {
                 mAppOpsService.removeUser(userId);
             } catch (RemoteException e) {
-                Log.w(LOG_TAG, "Unable to notify AppOpsService of removing user.", e);
+                Slog.w(LOG_TAG, "Unable to notify AppOpsService of removing user.", e);
             }
 
             // TODO(b/142482943): Send some sort of broadcast for profiles even if non-managed?
@@ -3616,7 +3615,7 @@
                             }
                         });
             } catch (RemoteException e) {
-                Log.w(LOG_TAG, "Failed to stop user during removal.", e);
+                Slog.w(LOG_TAG, "Failed to stop user during removal.", e);
                 return false;
             }
             return res == ActivityManager.USER_OP_SUCCESS;
@@ -3828,7 +3827,7 @@
                 readEntry(restrictions, values, parser);
             }
         } catch (IOException|XmlPullParserException e) {
-            Log.w(LOG_TAG, "Error parsing " + restrictionsFile.getBaseFile(), e);
+            Slog.w(LOG_TAG, "Error parsing " + restrictionsFile.getBaseFile(), e);
         } finally {
             IoUtils.closeQuietly(fis);
         }
@@ -4862,8 +4861,8 @@
     }
 
     private static void debug(String message) {
-        Log.d(LOG_TAG, message +
-                (DBG_WITH_STACKTRACE ? " called at\n" + Debug.getCallers(10, "  ") : ""));
+        Slog.d(LOG_TAG, message
+                + (DBG_WITH_STACKTRACE ? " called at\n" + Debug.getCallers(10, "  ") : ""));
     }
 
     /** @see #getMaxUsersOfTypePerParent(UserTypeDetails) */
diff --git a/services/core/java/com/android/server/utils/quota/Categorizer.java b/services/core/java/com/android/server/utils/quota/Categorizer.java
new file mode 100644
index 0000000..bf24991
--- /dev/null
+++ b/services/core/java/com/android/server/utils/quota/Categorizer.java
@@ -0,0 +1,35 @@
+/*
+ * 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.utils.quota;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * Identifies the {@link Category} that each UPTC belongs in.
+ *
+ * @see Uptc
+ */
+public interface Categorizer {
+    /**
+     * Return the {@link Category} that this UPTC belongs to.
+     *
+     * @see Uptc
+     */
+    @NonNull
+    Category getCategory(int userId, @NonNull String packageName, @Nullable String tag);
+}
diff --git a/services/core/java/com/android/server/utils/quota/Category.java b/services/core/java/com/android/server/utils/quota/Category.java
new file mode 100644
index 0000000..d8459d7
--- /dev/null
+++ b/services/core/java/com/android/server/utils/quota/Category.java
@@ -0,0 +1,71 @@
+/*
+ * 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.utils.quota;
+
+import android.annotation.NonNull;
+import android.util.proto.ProtoOutputStream;
+import android.util.quota.CategoryProto;
+
+/**
+ * A category as defined by the (system) client. Categories are used to put UPTCs in different
+ * groups. A sample group of Categories could be the various App Standby buckets or foreground vs
+ * background.
+ *
+ * @see Uptc
+ */
+public final class Category {
+    @NonNull
+    private final String mName;
+
+    private final int mHash;
+
+    /** Construct a new Category with the specified name. */
+    public Category(@NonNull String name) {
+        mName = name;
+        mHash = name.hashCode();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (other instanceof Category) {
+            return this.mName.equals(((Category) other).mName);
+        }
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return mHash;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return "Category{" + mName + "}";
+    }
+
+    void dumpDebug(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(CategoryProto.NAME, mName);
+        proto.end(token);
+    }
+}
diff --git a/services/core/java/com/android/server/utils/quota/QuotaChangeListener.java b/services/core/java/com/android/server/utils/quota/QuotaChangeListener.java
new file mode 100644
index 0000000..b673050
--- /dev/null
+++ b/services/core/java/com/android/server/utils/quota/QuotaChangeListener.java
@@ -0,0 +1,34 @@
+/*
+ * 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.utils.quota;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * Listener that is notified when a UPTC goes in and out of quota.
+ *
+ * @see Uptc
+ */
+public interface QuotaChangeListener {
+    /**
+     * Called when the UPTC reaches its quota or comes back into quota.
+     *
+     * @see Uptc
+     */
+    void onQuotaStateChanged(int userId, @NonNull String packageName, @Nullable String tag);
+}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index dee9e9f..4de36b8b 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -89,6 +89,14 @@
     @VisibleForTesting
     static final int SNAPSHOT_MODE_NONE = 2;
 
+    /**
+     * Constant for <code>scaleFactor</code> when calling {@link #snapshotTask} which is
+     * interpreted as using the most appropriate scale ratio for the system.
+     * This may yield a smaller ratio on low memory devices.
+     */
+    @VisibleForTesting
+    static final float SNAPSHOT_SCALE_AUTO = -1f;
+
     private final WindowManagerService mService;
 
     private final TaskSnapshotCache mCache;
@@ -260,6 +268,84 @@
         });
     }
 
+    /**
+     * Validates the state of the Task is appropriate to capture a snapshot, collects
+     * information from the task and populates the builder.
+     *
+     * @param task the task to capture
+     * @param scaleFraction the scale fraction between 0-1.0, or {@link #SNAPSHOT_SCALE_AUTO}
+     *                      to automatically select
+     * @param pixelFormat the desired pixel format, or {@link PixelFormat#UNKNOWN} to
+     *                    automatically select
+     * @param builder the snapshot builder to populate
+     *
+     * @return true if the state of the task is ok to proceed
+     */
+    private boolean prepareTaskSnapshot(Task task, float scaleFraction, int pixelFormat,
+            TaskSnapshot.Builder builder) {
+        if (!mService.mPolicy.isScreenOn()) {
+            if (DEBUG_SCREENSHOT) {
+                Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
+            }
+            return false;
+        }
+        final ActivityRecord activity = findAppTokenForSnapshot(task);
+        if (activity == null) {
+            if (DEBUG_SCREENSHOT) {
+                Slog.w(TAG_WM, "Failed to take screenshot. No visible windows for " + task);
+            }
+            return false;
+        }
+        if (activity.hasCommittedReparentToAnimationLeash()) {
+            if (DEBUG_SCREENSHOT) {
+                Slog.w(TAG_WM, "Failed to take screenshot. App is animating " + activity);
+            }
+            return false;
+        }
+
+        final WindowState mainWindow = activity.findMainWindow();
+        if (mainWindow == null) {
+            Slog.w(TAG_WM, "Failed to take screenshot. No main window for " + task);
+            return false;
+        }
+
+        builder.setIsRealSnapshot(true);
+        builder.setId(System.currentTimeMillis());
+        builder.setContentInsets(getInsets(mainWindow));
+
+        final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
+
+        if (scaleFraction == SNAPSHOT_SCALE_AUTO) {
+            builder.setScaleFraction(isLowRamDevice
+                    ? mPersister.getReducedScale()
+                    : mFullSnapshotScale);
+            builder.setReducedResolution(isLowRamDevice);
+        } else {
+            builder.setScaleFraction(scaleFraction);
+            builder.setReducedResolution(scaleFraction < 1.0f);
+        }
+
+        final boolean isWindowTranslucent = mainWindow.getAttrs().format != PixelFormat.OPAQUE;
+        final boolean isShowWallpaper = (mainWindow.getAttrs().flags & FLAG_SHOW_WALLPAPER) != 0;
+
+        if (pixelFormat == PixelFormat.UNKNOWN) {
+            pixelFormat = mPersister.use16BitFormat() && activity.fillsParent()
+                    && !(isWindowTranslucent && isShowWallpaper)
+                    ? PixelFormat.RGB_565
+                    : PixelFormat.RGBA_8888;
+        }
+
+        final boolean isTranslucent = PixelFormat.formatHasAlpha(pixelFormat)
+                && (!activity.fillsParent() || isWindowTranslucent);
+
+        builder.setTopActivityComponent(activity.mActivityComponent);
+        builder.setIsTranslucent(isTranslucent);
+        builder.setOrientation(activity.getTask().getConfiguration().orientation);
+        builder.setWindowingMode(task.getWindowingMode());
+        builder.setSystemUiVisibility(getSystemUiVisibility(task));
+        return true;
+    }
+
     @Nullable
     SurfaceControl.ScreenshotGraphicBuffer createTaskSnapshot(@NonNull Task task,
             float scaleFraction) {
@@ -288,63 +374,31 @@
         return screenshotBuffer;
     }
 
-    @Nullable private TaskSnapshot snapshotTask(Task task) {
-        if (!mService.mPolicy.isScreenOn()) {
-            if (DEBUG_SCREENSHOT) {
-                Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
-            }
+    @Nullable
+    TaskSnapshot snapshotTask(Task task) {
+        return snapshotTask(task, SNAPSHOT_SCALE_AUTO, PixelFormat.UNKNOWN);
+    }
+
+    @Nullable
+    TaskSnapshot snapshotTask(Task task, float scaleFraction, int pixelFormat) {
+        TaskSnapshot.Builder builder = new TaskSnapshot.Builder();
+
+        if (!prepareTaskSnapshot(task, scaleFraction, pixelFormat, builder)) {
+            // Failed some pre-req. Has been logged.
             return null;
         }
 
-        final ActivityRecord activity = findAppTokenForSnapshot(task);
-        if (activity == null) {
-            if (DEBUG_SCREENSHOT) {
-                Slog.w(TAG_WM, "Failed to take screenshot. No visible windows for " + task);
-            }
-            return null;
-        }
-        if (activity.hasCommittedReparentToAnimationLeash()) {
-            if (DEBUG_SCREENSHOT) {
-                Slog.w(TAG_WM, "Failed to take screenshot. App is animating " + activity);
-            }
-            return null;
-        }
-
-        final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
-        final float scaleFraction = isLowRamDevice
-                ? mPersister.getReducedScale()
-                : mFullSnapshotScale;
-
-        final WindowState mainWindow = activity.findMainWindow();
-        if (mainWindow == null) {
-            Slog.w(TAG_WM, "Failed to take screenshot. No main window for " + task);
-            return null;
-        }
-        final boolean isWindowTranslucent = mainWindow.getAttrs().format != PixelFormat.OPAQUE;
-        final boolean isShowWallpaper = (mainWindow.getAttrs().flags & FLAG_SHOW_WALLPAPER) != 0;
-        final int pixelFormat = mPersister.use16BitFormat() && activity.fillsParent()
-                && !(isWindowTranslucent && isShowWallpaper)
-                ? PixelFormat.RGB_565
-                : PixelFormat.RGBA_8888;
         final SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer =
-                createTaskSnapshot(task, scaleFraction, pixelFormat);
+                createTaskSnapshot(task, builder.getScaleFraction(),
+                builder.getPixelFormat());
 
         if (screenshotBuffer == null) {
-            if (DEBUG_SCREENSHOT) {
-                Slog.w(TAG_WM, "Failed to take screenshot for " + task);
-            }
+            // Failed to acquire image. Has been logged.
             return null;
         }
-        final boolean isTranslucent = PixelFormat.formatHasAlpha(pixelFormat)
-                && (!activity.fillsParent() || isWindowTranslucent);
-        return new TaskSnapshot(
-                System.currentTimeMillis() /* id */,
-                activity.mActivityComponent, screenshotBuffer.getGraphicBuffer(),
-                screenshotBuffer.getColorSpace(),
-                activity.getTask().getConfiguration().orientation,
-                getInsets(mainWindow), isLowRamDevice /* reduced */, scaleFraction /* scale */,
-                true /* isRealSnapshot */, task.getWindowingMode(), getSystemUiVisibility(task),
-                isTranslucent);
+        builder.setSnapshot(screenshotBuffer.getGraphicBuffer());
+        builder.setColorSpace(screenshotBuffer.getColorSpace());
+        return builder.build();
     }
 
     private boolean shouldDisableSnapshots() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
index 7174e5c..e17e0d8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
@@ -23,9 +23,20 @@
 import static com.android.server.wm.TaskSnapshotController.SNAPSHOT_MODE_REAL;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
 
+import android.app.ActivityManager;
+import android.app.WindowConfiguration;
+import android.content.ComponentName;
+import android.content.res.Configuration;
+import android.graphics.ColorSpace;
+import android.graphics.GraphicBuffer;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.util.ArraySet;
+import android.view.View;
 
 import androidx.test.filters.SmallTest;
 
@@ -33,6 +44,7 @@
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mockito;
 
 /**
  * Test class for {@link TaskSnapshotController}.
@@ -110,4 +122,57 @@
         assertEquals(SNAPSHOT_MODE_APP_THEME,
                 mWm.mTaskSnapshotController.getSnapshotMode(secureWindow.getTask()));
     }
+
+    @Test
+    public void testSnapshotBuilder() {
+        final GraphicBuffer buffer = Mockito.mock(GraphicBuffer.class);
+        final ColorSpace sRGB = ColorSpace.get(ColorSpace.Named.SRGB);
+        final long id = 1234L;
+        final ComponentName activityComponent = new ComponentName("package", ".Class");
+        final int windowingMode = WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+        final int systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN;
+        final int pixelFormat = PixelFormat.RGBA_8888;
+        final int orientation = Configuration.ORIENTATION_PORTRAIT;
+        final float scaleFraction = 0.25f;
+        final Rect contentInsets = new Rect(1, 2, 3, 4);
+
+        try {
+            ActivityManager.TaskSnapshot.Builder builder =
+                    new ActivityManager.TaskSnapshot.Builder();
+            builder.setId(id);
+            builder.setTopActivityComponent(activityComponent);
+            builder.setSystemUiVisibility(systemUiVisibility);
+            builder.setWindowingMode(windowingMode);
+            builder.setColorSpace(sRGB);
+            builder.setReducedResolution(true);
+            builder.setOrientation(orientation);
+            builder.setContentInsets(contentInsets);
+            builder.setIsTranslucent(true);
+            builder.setScaleFraction(0.25f);
+            builder.setSnapshot(buffer);
+            builder.setIsRealSnapshot(true);
+            builder.setPixelFormat(pixelFormat);
+
+            // Not part of TaskSnapshot itself, used in screenshot process
+            assertEquals(pixelFormat, builder.getPixelFormat());
+
+            ActivityManager.TaskSnapshot snapshot = builder.build();
+            assertEquals(id, snapshot.getId());
+            assertEquals(activityComponent, snapshot.getTopActivityComponent());
+            assertEquals(systemUiVisibility, snapshot.getSystemUiVisibility());
+            assertEquals(windowingMode, snapshot.getWindowingMode());
+            assertEquals(sRGB, snapshot.getColorSpace());
+            assertTrue(snapshot.isReducedResolution());
+            assertEquals(orientation, snapshot.getOrientation());
+            assertEquals(contentInsets, snapshot.getContentInsets());
+            assertTrue(snapshot.isTranslucent());
+            assertEquals(scaleFraction, builder.getScaleFraction(), 0.001f);
+            assertSame(buffer, snapshot.getSnapshot());
+            assertTrue(snapshot.isRealSnapshot());
+        } finally {
+            if (buffer != null) {
+                buffer.destroy();
+            }
+        }
+    }
 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 6132cc2..404346f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -1291,6 +1291,7 @@
                 pw.println("  mCurUser: " + mCurUser);
                 pw.println("  mCurUserUnlocked: " + mCurUserUnlocked);
                 pw.println("  mCurUserSupported: " + mCurUserSupported);
+                dumpSupportedUsers(pw, "  ");
                 if (mImpl == null) {
                     pw.println("  (No active implementation)");
                     return;
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
deleted file mode 100644
index c16eafb..0000000
--- a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
+++ /dev/null
@@ -1,521 +0,0 @@
-/*
- * Copyright (C) 2012 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.internal.telephony.gsm;
-
-import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE;
-import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI;
-import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_OTHER_EMERGENCY;
-import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE;
-import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.content.res.Resources;
-import android.telephony.CbGeoUtils;
-import android.telephony.CbGeoUtils.Circle;
-import android.telephony.CbGeoUtils.Geometry;
-import android.telephony.CbGeoUtils.LatLng;
-import android.telephony.CbGeoUtils.Polygon;
-import android.telephony.Rlog;
-import android.telephony.SmsCbLocation;
-import android.telephony.SmsCbMessage;
-import android.telephony.SubscriptionManager;
-import android.util.Pair;
-
-import com.android.internal.R;
-import com.android.internal.telephony.GsmAlphabet;
-import com.android.internal.telephony.SmsConstants;
-import com.android.internal.telephony.gsm.GsmSmsCbMessage.GeoFencingTriggerMessage.CellBroadcastIdentity;
-import com.android.internal.telephony.gsm.SmsCbHeader.DataCodingScheme;
-
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * Parses a GSM or UMTS format SMS-CB message into an {@link SmsCbMessage} object. The class is
- * public because {@link #createSmsCbMessage(SmsCbLocation, byte[][])} is used by some test cases.
- */
-public class GsmSmsCbMessage {
-    private static final String TAG = GsmSmsCbMessage.class.getSimpleName();
-
-    private static final char CARRIAGE_RETURN = 0x0d;
-
-    private static final int PDU_BODY_PAGE_LENGTH = 82;
-
-    /** Utility class with only static methods. */
-    private GsmSmsCbMessage() { }
-
-    /**
-     * Get built-in ETWS primary messages by category. ETWS primary message does not contain text,
-     * so we have to show the pre-built messages to the user.
-     *
-     * @param context Device context
-     * @param category ETWS message category defined in SmsCbConstants
-     * @return ETWS text message in string. Return an empty string if no match.
-     */
-    private static String getEtwsPrimaryMessage(Context context, int category) {
-        final Resources r = context.getResources();
-        switch (category) {
-            case ETWS_WARNING_TYPE_EARTHQUAKE:
-                return r.getString(R.string.etws_primary_default_message_earthquake);
-            case ETWS_WARNING_TYPE_TSUNAMI:
-                return r.getString(R.string.etws_primary_default_message_tsunami);
-            case ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI:
-                return r.getString(R.string.etws_primary_default_message_earthquake_and_tsunami);
-            case ETWS_WARNING_TYPE_TEST_MESSAGE:
-                return r.getString(R.string.etws_primary_default_message_test);
-            case ETWS_WARNING_TYPE_OTHER_EMERGENCY:
-                return r.getString(R.string.etws_primary_default_message_others);
-            default:
-                return "";
-        }
-    }
-
-    /**
-     * Create a new SmsCbMessage object from a header object plus one or more received PDUs.
-     *
-     * @param pdus PDU bytes
-     * @slotIndex slotIndex for which received sms cb message
-     */
-    public static SmsCbMessage createSmsCbMessage(Context context, SmsCbHeader header,
-            SmsCbLocation location, byte[][] pdus, int slotIndex)
-            throws IllegalArgumentException {
-        SubscriptionManager sm = (SubscriptionManager) context.getSystemService(
-                Context.TELEPHONY_SUBSCRIPTION_SERVICE);
-        int subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
-        int[] subIds = sm.getSubscriptionIds(slotIndex);
-        if (subIds != null && subIds.length > 0) {
-            subId = subIds[0];
-        }
-
-        long receivedTimeMillis = System.currentTimeMillis();
-        if (header.isEtwsPrimaryNotification()) {
-            // ETSI TS 23.041 ETWS Primary Notification message
-            // ETWS primary message only contains 4 fields including serial number,
-            // message identifier, warning type, and warning security information.
-            // There is no field for the content/text so we get the text from the resources.
-            return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, header.getGeographicalScope(),
-                    header.getSerialNumber(), location, header.getServiceCategory(), null,
-                    getEtwsPrimaryMessage(context, header.getEtwsInfo().getWarningType()),
-                    SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, header.getEtwsInfo(),
-                    header.getCmasInfo(), 0, null /* geometries */, receivedTimeMillis, slotIndex,
-                    subId);
-        } else if (header.isUmtsFormat()) {
-            // UMTS format has only 1 PDU
-            byte[] pdu = pdus[0];
-            Pair<String, String> cbData = parseUmtsBody(header, pdu);
-            String language = cbData.first;
-            String body = cbData.second;
-            int priority = header.isEmergencyMessage() ? SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY
-                    : SmsCbMessage.MESSAGE_PRIORITY_NORMAL;
-            int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH];
-            int wacDataOffset = SmsCbHeader.PDU_HEADER_LENGTH
-                    + 1 // number of pages
-                    + (PDU_BODY_PAGE_LENGTH + 1) * nrPages; // cb data
-
-            // Has Warning Area Coordinates information
-            List<Geometry> geometries = null;
-            int maximumWaitingTimeSec = 255;
-            if (pdu.length > wacDataOffset) {
-                try {
-                    Pair<Integer, List<Geometry>> wac = parseWarningAreaCoordinates(pdu,
-                            wacDataOffset);
-                    maximumWaitingTimeSec = wac.first;
-                    geometries = wac.second;
-                } catch (Exception ex) {
-                    // Catch the exception here, the message will be considered as having no WAC
-                    // information which means the message will be broadcasted directly.
-                    Rlog.e(TAG, "Can't parse warning area coordinates, ex = " + ex.toString());
-                }
-            }
-
-            return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
-                    header.getGeographicalScope(), header.getSerialNumber(), location,
-                    header.getServiceCategory(), language, body, priority,
-                    header.getEtwsInfo(), header.getCmasInfo(), maximumWaitingTimeSec, geometries,
-                    receivedTimeMillis, slotIndex, subId);
-        } else {
-            String language = null;
-            StringBuilder sb = new StringBuilder();
-            for (byte[] pdu : pdus) {
-                Pair<String, String> p = parseGsmBody(header, pdu);
-                language = p.first;
-                sb.append(p.second);
-            }
-            int priority = header.isEmergencyMessage() ? SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY
-                    : SmsCbMessage.MESSAGE_PRIORITY_NORMAL;
-
-            return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
-                    header.getGeographicalScope(), header.getSerialNumber(), location,
-                    header.getServiceCategory(), language, sb.toString(), priority,
-                    header.getEtwsInfo(), header.getCmasInfo(), 0, null /* geometries */,
-                    receivedTimeMillis, slotIndex, subId);
-        }
-    }
-
-    /**
-     * Parse WEA Handset Action Message(WHAM) a.k.a geo-fencing trigger message.
-     *
-     * WEA Handset Action Message(WHAM) is a cell broadcast service message broadcast by the network
-     * to direct devices to perform a geo-fencing check on selected alerts.
-     *
-     * WEA Handset Action Message(WHAM) requirements from ATIS-0700041 section 4
-     * 1. The Warning Message contents of a WHAM shall be in Cell Broadcast(CB) data format as
-     * defined in TS 23.041.
-     * 2. The Warning Message Contents of WHAM shall be limited to one CB page(max 20 referenced
-     * WEA messages).
-     * 3. The broadcast area for a WHAM shall be the union of the broadcast areas of the referenced
-     * WEA message.
-     * @param pdu cell broadcast pdu, including the header
-     * @return {@link GeoFencingTriggerMessage} instance
-     */
-    public static GeoFencingTriggerMessage createGeoFencingTriggerMessage(byte[] pdu) {
-        try {
-            // Header length + 1(number of page). ATIS-0700041 define the number of page of
-            // geo-fencing trigger message is 1.
-            int whamOffset = SmsCbHeader.PDU_HEADER_LENGTH + 1;
-
-            BitStreamReader bitReader = new BitStreamReader(pdu, whamOffset);
-            int type = bitReader.read(4);
-            int length = bitReader.read(7);
-            // Skip the remained 5 bits
-            bitReader.skip();
-
-            int messageIdentifierCount = (length - 2) * 8 / 32;
-            List<CellBroadcastIdentity> cbIdentifiers = new ArrayList<>();
-            for (int i = 0; i < messageIdentifierCount; i++) {
-                // Both messageIdentifier and serialNumber are 16 bits integers.
-                // ATIS-0700041 Section 5.1.6
-                int messageIdentifier = bitReader.read(16);
-                int serialNumber = bitReader.read(16);
-                cbIdentifiers.add(new CellBroadcastIdentity(messageIdentifier, serialNumber));
-            }
-            return new GeoFencingTriggerMessage(type, cbIdentifiers);
-        } catch (Exception ex) {
-            Rlog.e(TAG, "create geo-fencing trigger failed, ex = " + ex.toString());
-            return null;
-        }
-    }
-
-    /**
-     * Parse the broadcast area and maximum wait time from the Warning Area Coordinates TLV.
-     *
-     * @param pdu Warning Area Coordinates TLV.
-     * @param wacOffset the offset of Warning Area Coordinates TLV.
-     * @return a pair with the first element is maximum wait time and the second is the broadcast
-     * area. The default value of the maximum wait time is 255 which means use the device default
-     * value.
-     */
-    private static Pair<Integer, List<Geometry>> parseWarningAreaCoordinates(
-            byte[] pdu, int wacOffset) {
-        // little-endian
-        int wacDataLength = ((pdu[wacOffset + 1] & 0xff) << 8) | (pdu[wacOffset] & 0xff);
-        int offset = wacOffset + 2;
-
-        if (offset + wacDataLength > pdu.length) {
-            throw new IllegalArgumentException("Invalid wac data, expected the length of pdu at"
-                    + "least " + offset + wacDataLength + ", actual is " + pdu.length);
-        }
-
-        BitStreamReader bitReader = new BitStreamReader(pdu, offset);
-
-        int maximumWaitTimeSec = SmsCbMessage.MAXIMUM_WAIT_TIME_NOT_SET;
-
-        List<Geometry> geo = new ArrayList<>();
-        int remainedBytes = wacDataLength;
-        while (remainedBytes > 0) {
-            int type = bitReader.read(4);
-            int length = bitReader.read(10);
-            remainedBytes -= length;
-            // Skip the 2 remained bits
-            bitReader.skip();
-
-            switch (type) {
-                case CbGeoUtils.GEO_FENCING_MAXIMUM_WAIT_TIME:
-                    maximumWaitTimeSec = bitReader.read(8);
-                    break;
-                case CbGeoUtils.GEOMETRY_TYPE_POLYGON:
-                    List<LatLng> latLngs = new ArrayList<>();
-                    // Each coordinate is represented by 44 bits integer.
-                    // ATIS-0700041 5.2.4 Coordinate coding
-                    int n = (length - 2) * 8 / 44;
-                    for (int i = 0; i < n; i++) {
-                        latLngs.add(getLatLng(bitReader));
-                    }
-                    // Skip the padding bits
-                    bitReader.skip();
-                    geo.add(new Polygon(latLngs));
-                    break;
-                case CbGeoUtils.GEOMETRY_TYPE_CIRCLE:
-                    LatLng center = getLatLng(bitReader);
-                    // radius = (wacRadius / 2^6). The unit of wacRadius is km, we use meter as the
-                    // distance unit during geo-fencing.
-                    // ATIS-0700041 5.2.5 radius coding
-                    double radius = (bitReader.read(20) * 1.0 / (1 << 6)) * 1000.0;
-                    geo.add(new Circle(center, radius));
-                    break;
-                default:
-                    throw new IllegalArgumentException("Unsupported geoType = " + type);
-            }
-        }
-        return new Pair(maximumWaitTimeSec, geo);
-    }
-
-    /**
-     * The coordinate is (latitude, longitude), represented by a 44 bits integer.
-     * The coding is defined in ATIS-0700041 5.2.4
-     * @param bitReader
-     * @return coordinate (latitude, longitude)
-     */
-    private static LatLng getLatLng(BitStreamReader bitReader) {
-        // wacLatitude = floor(((latitude + 90) / 180) * 2^22)
-        // wacLongitude = floor(((longitude + 180) / 360) * 2^22)
-        int wacLat = bitReader.read(22);
-        int wacLng = bitReader.read(22);
-
-        // latitude = wacLatitude * 180 / 2^22 - 90
-        // longitude = wacLongitude * 360 / 2^22 -180
-        return new LatLng((wacLat * 180.0 / (1 << 22)) - 90, (wacLng * 360.0 / (1 << 22) - 180));
-    }
-
-    /**
-     * Parse and unpack the UMTS body text according to the encoding in the data coding scheme.
-     *
-     * @param header the message header to use
-     * @param pdu the PDU to decode
-     * @return a pair of string containing the language and body of the message in order
-     */
-    private static Pair<String, String> parseUmtsBody(SmsCbHeader header, byte[] pdu) {
-        // Payload may contain multiple pages
-        int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH];
-        String language = header.getDataCodingSchemeStructedData().language;
-
-        if (pdu.length < SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1)
-                * nrPages) {
-            throw new IllegalArgumentException("Pdu length " + pdu.length + " does not match "
-                    + nrPages + " pages");
-        }
-
-        StringBuilder sb = new StringBuilder();
-
-        for (int i = 0; i < nrPages; i++) {
-            // Each page is 82 bytes followed by a length octet indicating
-            // the number of useful octets within those 82
-            int offset = SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1) * i;
-            int length = pdu[offset + PDU_BODY_PAGE_LENGTH];
-
-            if (length > PDU_BODY_PAGE_LENGTH) {
-                throw new IllegalArgumentException("Page length " + length
-                        + " exceeds maximum value " + PDU_BODY_PAGE_LENGTH);
-            }
-
-            Pair<String, String> p = unpackBody(pdu, offset, length,
-                    header.getDataCodingSchemeStructedData());
-            language = p.first;
-            sb.append(p.second);
-        }
-        return new Pair(language, sb.toString());
-
-    }
-
-    /**
-     * Parse and unpack the GSM body text according to the encoding in the data coding scheme.
-     * @param header the message header to use
-     * @param pdu the PDU to decode
-     * @return a pair of string containing the language and body of the message in order
-     */
-    private static Pair<String, String> parseGsmBody(SmsCbHeader header, byte[] pdu) {
-        // Payload is one single page
-        int offset = SmsCbHeader.PDU_HEADER_LENGTH;
-        int length = pdu.length - offset;
-        return unpackBody(pdu, offset, length, header.getDataCodingSchemeStructedData());
-    }
-
-    /**
-     * Unpack body text from the pdu using the given encoding, position and length within the pdu.
-     *
-     * @param pdu The pdu
-     * @param offset Position of the first byte to unpack
-     * @param length Number of bytes to unpack
-     * @param dcs data coding scheme
-     * @return a Pair of Strings containing the language and body of the message
-     */
-    private static Pair<String, String> unpackBody(byte[] pdu, int offset, int length,
-            DataCodingScheme dcs) {
-        String body = null;
-
-        String language = dcs.language;
-        switch (dcs.encoding) {
-            case SmsConstants.ENCODING_7BIT:
-                body = GsmAlphabet.gsm7BitPackedToString(pdu, offset, length * 8 / 7);
-
-                if (dcs.hasLanguageIndicator && body != null && body.length() > 2) {
-                    // Language is two GSM characters followed by a CR.
-                    // The actual body text is offset by 3 characters.
-                    language = body.substring(0, 2);
-                    body = body.substring(3);
-                }
-                break;
-
-            case SmsConstants.ENCODING_16BIT:
-                if (dcs.hasLanguageIndicator && pdu.length >= offset + 2) {
-                    // Language is two GSM characters.
-                    // The actual body text is offset by 2 bytes.
-                    language = GsmAlphabet.gsm7BitPackedToString(pdu, offset, 2);
-                    offset += 2;
-                    length -= 2;
-                }
-
-                try {
-                    body = new String(pdu, offset, (length & 0xfffe), "utf-16");
-                } catch (UnsupportedEncodingException e) {
-                    // Apparently it wasn't valid UTF-16.
-                    throw new IllegalArgumentException("Error decoding UTF-16 message", e);
-                }
-                break;
-
-            default:
-                break;
-        }
-
-        if (body != null) {
-            // Remove trailing carriage return
-            for (int i = body.length() - 1; i >= 0; i--) {
-                if (body.charAt(i) != CARRIAGE_RETURN) {
-                    body = body.substring(0, i + 1);
-                    break;
-                }
-            }
-        } else {
-            body = "";
-        }
-
-        return new Pair<String, String>(language, body);
-    }
-
-    /** A class use to facilitate the processing of bits stream data. */
-    private static final class BitStreamReader {
-        /** The bits stream represent by a bytes array. */
-        private final byte[] mData;
-
-        /** The offset of the current byte. */
-        private int mCurrentOffset;
-
-        /**
-         * The remained bits of the current byte which have not been read. The most significant
-         * will be read first, so the remained bits are always the least significant bits.
-         */
-        private int mRemainedBit;
-
-        /**
-         * Constructor
-         * @param data bit stream data represent by byte array.
-         * @param offset the offset of the first byte.
-         */
-        BitStreamReader(byte[] data, int offset) {
-            mData = data;
-            mCurrentOffset = offset;
-            mRemainedBit = 8;
-        }
-
-        /**
-         * Read the first {@code count} bits.
-         * @param count the number of bits need to read
-         * @return {@code bits} represent by an 32-bits integer, therefore {@code count} must be no
-         * greater than 32.
-         */
-        public int read(int count) throws IndexOutOfBoundsException {
-            int val = 0;
-            while (count > 0) {
-                if (count >= mRemainedBit) {
-                    val <<= mRemainedBit;
-                    val |= mData[mCurrentOffset] & ((1 << mRemainedBit) - 1);
-                    count -= mRemainedBit;
-                    mRemainedBit = 8;
-                    ++mCurrentOffset;
-                } else {
-                    val <<= count;
-                    val |= (mData[mCurrentOffset] & ((1 << mRemainedBit) - 1))
-                            >> (mRemainedBit - count);
-                    mRemainedBit -= count;
-                    count = 0;
-                }
-            }
-            return val;
-        }
-
-        /**
-         * Skip the current bytes if the remained bits is less than 8. This is useful when
-         * processing the padding or reserved bits.
-         */
-        public void skip() {
-            if (mRemainedBit < 8) {
-                mRemainedBit = 8;
-                ++mCurrentOffset;
-            }
-        }
-    }
-
-    /**
-     * Part of a GSM SMS cell broadcast message which may trigger geo-fencing logic.
-     * @hide
-     */
-    public static final class GeoFencingTriggerMessage {
-        /**
-         * Indicate the list of active alerts share their warning area coordinates which means the
-         * broadcast area is the union of the broadcast areas of the active alerts in this list.
-         */
-        public static final int TYPE_ACTIVE_ALERT_SHARE_WAC = 2;
-
-        public final int type;
-        public final List<CellBroadcastIdentity> cbIdentifiers;
-
-        GeoFencingTriggerMessage(int type, @NonNull List<CellBroadcastIdentity> cbIdentifiers) {
-            this.type = type;
-            this.cbIdentifiers = cbIdentifiers;
-        }
-
-        /**
-         * Whether the trigger message indicates that the broadcast areas are shared between all
-         * active alerts.
-         * @return true if broadcast areas are to be shared
-         */
-        boolean shouldShareBroadcastArea() {
-            return type == TYPE_ACTIVE_ALERT_SHARE_WAC;
-        }
-
-        static final class CellBroadcastIdentity {
-            public final int messageIdentifier;
-            public final int serialNumber;
-            CellBroadcastIdentity(int messageIdentifier, int serialNumber) {
-                this.messageIdentifier = messageIdentifier;
-                this.serialNumber = serialNumber;
-            }
-        }
-
-        @Override
-        public String toString() {
-            String identifiers = cbIdentifiers.stream()
-                    .map(cbIdentifier ->String.format("(msgId = %d, serial = %d)",
-                            cbIdentifier.messageIdentifier, cbIdentifier.serialNumber))
-                    .collect(Collectors.joining(","));
-            return "triggerType=" + type + " identifiers=" + identifiers;
-        }
-    }
-}