Merge "Import translations. DO NOT MERGE ANYWHERE"
diff --git a/Android.bp b/Android.bp
index 4e7eba2..3d25bc1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -100,7 +100,7 @@
         ":android.hardware.gnss-V2-java-source",
         ":android.hardware.graphics.common-V3-java-source",
         ":android.hardware.keymaster-V4-java-source",
-        ":android.hardware.security.keymint-V2-java-source",
+        ":android.hardware.security.keymint-V3-java-source",
         ":android.hardware.security.secureclock-V1-java-source",
         ":android.hardware.tv.tuner-V1-java-source",
         ":android.security.apc-java-source",
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index e3bd5ac..dcc6aa6 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -29,6 +29,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.AlarmManager;
+import android.app.BroadcastOptions;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.IIntentReceiver;
@@ -314,7 +315,9 @@
     private Sensor mMotionSensor;
     private LocationRequest mLocationRequest;
     private Intent mIdleIntent;
+    private Bundle mIdleIntentOptions;
     private Intent mLightIdleIntent;
+    private Bundle mLightIdleIntentOptions;
     private AnyMotionDetector mAnyMotionDetector;
     private final AppStateTrackerImpl mAppStateTracker;
     @GuardedBy("this")
@@ -1798,10 +1801,12 @@
                     } catch (RemoteException e) {
                     }
                     if (deepChanged) {
-                        getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
+                        getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL,
+                                null /* receiverPermission */, mIdleIntentOptions);
                     }
                     if (lightChanged) {
-                        getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
+                        getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL,
+                                null /* receiverPermission */, mLightIdleIntentOptions);
                     }
                     EventLogTags.writeDeviceIdleOnComplete();
                     mGoingIdleWakeLock.release();
@@ -1821,13 +1826,13 @@
                         incActiveIdleOps();
                         mLocalActivityManager.broadcastIntentWithCallback(mIdleIntent,
                                 mIdleStartedDoneReceiver, null, UserHandle.USER_ALL,
-                                null, null, null);
+                                null, null, mIdleIntentOptions);
                     }
                     if (lightChanged) {
                         incActiveIdleOps();
                         mLocalActivityManager.broadcastIntentWithCallback(mLightIdleIntent,
                                 mIdleStartedDoneReceiver, null, UserHandle.USER_ALL,
-                                null, null, null);
+                                null, null, mLightIdleIntentOptions);
                     }
                     // Always start with one active op for the message being sent here.
                     // Now we are done!
@@ -1849,10 +1854,12 @@
                     } catch (RemoteException e) {
                     }
                     if (deepChanged) {
-                        getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
+                        getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL,
+                                null /* receiverPermission */, mIdleIntentOptions);
                     }
                     if (lightChanged) {
-                        getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
+                        getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL,
+                                null /* receiverPermission */, mLightIdleIntentOptions);
                     }
                     EventLogTags.writeDeviceIdleOffComplete();
                 } break;
@@ -2531,6 +2538,9 @@
                 mLightIdleIntent = new Intent(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
                 mLightIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                         | Intent.FLAG_RECEIVER_FOREGROUND);
+                final BroadcastOptions options = BroadcastOptions.makeBasic();
+                options.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+                mIdleIntentOptions = mLightIdleIntentOptions = options.toBundle();
 
                 IntentFilter filter = new IntentFilter();
                 filter.addAction(Intent.ACTION_BATTERY_CHANGED);
diff --git a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
index 5e189f2..7b38bd1 100644
--- a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
+++ b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
@@ -39,7 +39,7 @@
   void Write8(uint8_t value);
   void Write16(uint16_t value);
   void Write32(uint32_t value);
-  void WriteString(const StringPiece& value);
+  void WriteString(StringPiece value);
   std::ostream& stream_;
 };
 
diff --git a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
index 4b271a1..8976924 100644
--- a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
+++ b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
@@ -38,7 +38,7 @@
   stream_.write(reinterpret_cast<char*>(&x), sizeof(uint32_t));
 }
 
-void BinaryStreamVisitor::WriteString(const StringPiece& value) {
+void BinaryStreamVisitor::WriteString(StringPiece value) {
   // pad with null to nearest word boundary;
   size_t padding_size = CalculatePadding(value.size());
   Write32(value.size());
diff --git a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
index d517e29..dd5be21c 100644
--- a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
+++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
@@ -101,10 +101,10 @@
 }
 
 Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() {
-  using ConfigMap = std::map<std::string, TargetValue>;
-  using EntryMap = std::map<std::string, ConfigMap>;
-  using TypeMap = std::map<std::string, EntryMap>;
-  using PackageMap = std::map<std::string, TypeMap>;
+  using ConfigMap = std::map<std::string, TargetValue, std::less<>>;
+  using EntryMap = std::map<std::string, ConfigMap, std::less<>>;
+  using TypeMap = std::map<std::string, EntryMap, std::less<>>;
+  using PackageMap = std::map<std::string, TypeMap, std::less<>>;
   PackageMap package_map;
   android::StringPool string_pool;
   for (const auto& res_entry : entries_) {
@@ -116,8 +116,7 @@
       return Error("failed to parse resource name '%s'", res_entry.resource_name.c_str());
     }
 
-    std::string package_name =
-        package_substr.empty() ? target_package_name_ : package_substr.to_string();
+    std::string_view package_name = package_substr.empty() ? target_package_name_ : package_substr;
     if (type_name.empty()) {
       return Error("resource name '%s' missing type name", res_entry.resource_name.c_str());
     }
@@ -133,17 +132,14 @@
                     .first;
     }
 
-    auto type = package->second.find(type_name.to_string());
+    auto type = package->second.find(type_name);
     if (type == package->second.end()) {
-      type =
-          package->second
-              .insert(std::make_pair(type_name.to_string(), EntryMap()))
-              .first;
+      type = package->second.insert(std::make_pair(type_name, EntryMap())).first;
     }
 
-    auto entry = type->second.find(entry_name.to_string());
+    auto entry = type->second.find(entry_name);
     if (entry == type->second.end()) {
-      entry = type->second.insert(std::make_pair(entry_name.to_string(), ConfigMap())).first;
+      entry = type->second.insert(std::make_pair(entry_name, ConfigMap())).first;
     }
 
     auto value = entry->second.find(res_entry.configuration);
diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp
index 813dff1..7c0b937 100644
--- a/cmds/idmap2/libidmap2/Idmap.cpp
+++ b/cmds/idmap2/libidmap2/Idmap.cpp
@@ -317,7 +317,7 @@
   }
 
   std::unique_ptr<IdmapData> data(new IdmapData());
-  data->string_pool_data_ = resource_mapping.GetStringPoolData().to_string();
+  data->string_pool_data_ = std::string(resource_mapping.GetStringPoolData());
   uint32_t inline_value_count = 0;
   std::set<std::string> config_set;
   for (const auto& mapping : resource_mapping.GetTargetToOverlayMap()) {
diff --git a/cmds/idmap2/libidmap2/PolicyUtils.cpp b/cmds/idmap2/libidmap2/PolicyUtils.cpp
index 4e3f54d2..76c70ca 100644
--- a/cmds/idmap2/libidmap2/PolicyUtils.cpp
+++ b/cmds/idmap2/libidmap2/PolicyUtils.cpp
@@ -53,7 +53,7 @@
 
   for (const auto& policy : kPolicyStringToFlag) {
     if ((bitmask & policy.second) != 0) {
-      policies.emplace_back(policy.first.to_string());
+      policies.emplace_back(policy.first);
     }
   }
 
diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp
index bb31c11..b2300ce 100644
--- a/cmds/idmap2/libidmap2/ResourceMapping.cpp
+++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp
@@ -89,7 +89,7 @@
     // If the overlay supplies a target overlayable name, the resource must belong to the
     // overlayable defined with the specified name to be overlaid.
     return Error(R"(<overlay> android:targetName "%s" does not match overlayable name "%s")",
-                 overlay_info.target_name.c_str(), (*overlayable_info)->name.c_str());
+                 overlay_info.target_name.c_str(), (*overlayable_info)->name.data());
   }
 
   // Enforce policy restrictions if the resource is declared as overlayable.
diff --git a/cmds/svc/src/com/android/commands/svc/UsbCommand.java b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
index 7d80493..26e20f6 100644
--- a/cmds/svc/src/com/android/commands/svc/UsbCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
@@ -29,12 +29,18 @@
 import java.util.function.Consumer;
 import java.util.concurrent.Executor;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
 
 public class UsbCommand extends Svc.Command {
     public UsbCommand() {
         super("usb");
     }
 
+    /**
+     * Counter for tracking UsbOperation operations.
+     */
+    private static final AtomicInteger sUsbOperationCount = new AtomicInteger();
+
     @Override
     public String shortHelp() {
         return "Control Usb state";
@@ -92,8 +98,10 @@
 
             if ("setFunctions".equals(args[1])) {
                 try {
+                    int operationId = sUsbOperationCount.incrementAndGet();
+                    System.out.println("setCurrentFunctions opId:" + operationId);
                     usbMgr.setCurrentFunctions(UsbManager.usbFunctionsFromString(
-                            args.length >= 3 ? args[2] : ""));
+                            args.length >= 3 ? args[2] : ""), operationId);
                 } catch (RemoteException e) {
                     System.err.println("Error communicating with UsbManager: " + e);
                 }
diff --git a/core/api/current.txt b/core/api/current.txt
index aa97849..f550952 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1041,6 +1041,7 @@
     field public static final int max = 16843062; // 0x1010136
     field public static final int maxAspectRatio = 16844128; // 0x1010560
     field public static final int maxButtonHeight = 16844029; // 0x10104fd
+    field public static final int maxConcurrentSessionsCount;
     field public static final int maxDate = 16843584; // 0x1010340
     field public static final int maxEms = 16843095; // 0x1010157
     field public static final int maxHeight = 16843040; // 0x1010120
@@ -6968,6 +6969,7 @@
     method @Deprecated public void onStart(android.content.Intent, int);
     method public int onStartCommand(android.content.Intent, int, int);
     method public void onTaskRemoved(android.content.Intent);
+    method public void onTimeout(int);
     method public void onTrimMemory(int);
     method public boolean onUnbind(android.content.Intent);
     method public final void startForeground(int, android.app.Notification);
@@ -11663,6 +11665,7 @@
 
   public class PackageInstaller {
     method public void abandonSession(int);
+    method public void checkInstallConstraints(@NonNull java.util.List<java.lang.String>, @NonNull android.content.pm.PackageInstaller.InstallConstraints, @NonNull java.util.function.Consumer<android.content.pm.PackageInstaller.InstallConstraintsResult>);
     method public int createSession(@NonNull android.content.pm.PackageInstaller.SessionParams) throws java.io.IOException;
     method @Deprecated @Nullable public android.content.pm.PackageInstaller.SessionInfo getActiveStagedSession();
     method @NonNull public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getActiveStagedSessions();
@@ -11707,6 +11710,35 @@
     field public static final int STATUS_SUCCESS = 0; // 0x0
   }
 
+  public static final class PackageInstaller.InstallConstraints implements android.os.Parcelable {
+    method public int describeContents();
+    method public boolean isRequireAppNotForeground();
+    method public boolean isRequireAppNotInteracting();
+    method public boolean isRequireAppNotTopVisible();
+    method public boolean isRequireDeviceIdle();
+    method public boolean isRequireNotInCall();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.InstallConstraints> CREATOR;
+    field @NonNull public static final android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE;
+  }
+
+  public static final class PackageInstaller.InstallConstraints.Builder {
+    ctor public PackageInstaller.InstallConstraints.Builder();
+    method @NonNull public android.content.pm.PackageInstaller.InstallConstraints build();
+    method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotForeground();
+    method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotInteracting();
+    method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotTopVisible();
+    method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireDeviceIdle();
+    method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireNotInCall();
+  }
+
+  public static final class PackageInstaller.InstallConstraintsResult implements android.os.Parcelable {
+    method public int describeContents();
+    method public boolean isAllConstraintsSatisfied();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.InstallConstraintsResult> CREATOR;
+  }
+
   public static final class PackageInstaller.PreapprovalDetails implements android.os.Parcelable {
     method public int describeContents();
     method @Nullable public android.graphics.Bitmap getIcon();
@@ -12444,6 +12476,7 @@
     field @Deprecated public static final int FOREGROUND_SERVICE_TYPE_NONE = 0; // 0x0
     field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_PHONE_CALL}, anyOf={android.Manifest.permission.MANAGE_OWN_CALLS}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_PHONE_CALL = 4; // 0x4
     field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING = 512; // 0x200
+    field public static final int FOREGROUND_SERVICE_TYPE_SHORT_SERVICE = 2048; // 0x800
     field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_SPECIAL_USE = 1073741824; // 0x40000000
     field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED = 1024; // 0x400
     field public int flags;
@@ -12994,10 +13027,12 @@
   }
 
   public final class CreateCredentialRequest implements android.os.Parcelable {
-    ctor public CreateCredentialRequest(@NonNull String, @NonNull android.os.Bundle);
+    ctor public CreateCredentialRequest(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle, boolean);
     method public int describeContents();
-    method @NonNull public android.os.Bundle getData();
+    method @NonNull public android.os.Bundle getCandidateQueryData();
+    method @NonNull public android.os.Bundle getCredentialData();
     method @NonNull public String getType();
+    method public boolean requireSystemProvider();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.credentials.CreateCredentialRequest> CREATOR;
   }
@@ -13035,10 +13070,11 @@
   }
 
   public final class GetCredentialOption implements android.os.Parcelable {
-    ctor public GetCredentialOption(@NonNull String, @NonNull android.os.Bundle);
+    ctor public GetCredentialOption(@NonNull String, @NonNull android.os.Bundle, boolean);
     method public int describeContents();
     method @NonNull public android.os.Bundle getData();
     method @NonNull public String getType();
+    method public boolean requireSystemProvider();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.credentials.GetCredentialOption> CREATOR;
   }
@@ -15450,7 +15486,6 @@
   }
 
   public static class PathIterator.Segment {
-    ctor public PathIterator.Segment(@NonNull int, @NonNull float[], float);
     method public float getConicWeight();
     method @NonNull public float[] getPoints();
     method @NonNull public int getVerb();
@@ -32667,7 +32702,6 @@
     method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.QUERY_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.content.pm.UserProperties getUserProperties(@NonNull android.os.UserHandle);
     method public android.os.Bundle getUserRestrictions();
     method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.os.Bundle getUserRestrictions(android.os.UserHandle);
-    method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}) public java.util.Set<android.os.UserHandle> getVisibleUsers();
     method public boolean hasUserRestriction(String);
     method public boolean isDemoUser();
     method public static boolean isHeadlessSystemUserMode();
@@ -32681,7 +32715,6 @@
     method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public boolean isUserRunningOrStopping(android.os.UserHandle);
     method public boolean isUserUnlocked();
     method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public boolean isUserUnlocked(android.os.UserHandle);
-    method public boolean isUserVisible();
     method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.MODIFY_QUIET_MODE"}, conditional=true) public boolean requestQuietModeEnabled(boolean, @NonNull android.os.UserHandle);
     method public boolean requestQuietModeEnabled(boolean, @NonNull android.os.UserHandle, int);
     method @Deprecated public boolean setRestrictionsChallenge(String);
@@ -48662,6 +48695,7 @@
     method public float getRefreshRate();
     method public int getRotation();
     method @Nullable public android.view.RoundedCorner getRoundedCorner(int);
+    method @NonNull public android.view.DisplayShape getShape();
     method @Deprecated public void getSize(android.graphics.Point);
     method public int getState();
     method public android.view.Display.Mode[] getSupportedModes();
@@ -48742,6 +48776,13 @@
     method @NonNull public android.view.DisplayCutout.Builder setWaterfallInsets(@NonNull android.graphics.Insets);
   }
 
+  public final class DisplayShape implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.graphics.Path getPath();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.view.DisplayShape> CREATOR;
+  }
+
   public final class DragAndDropPermissions implements android.os.Parcelable {
     method public int describeContents();
     method public void release();
@@ -52123,6 +52164,7 @@
     method @Deprecated @NonNull public android.view.WindowInsets consumeStableInsets();
     method @Deprecated @NonNull public android.view.WindowInsets consumeSystemWindowInsets();
     method @Nullable public android.view.DisplayCutout getDisplayCutout();
+    method @Nullable public android.view.DisplayShape getDisplayShape();
     method @NonNull public android.graphics.Insets getInsets(int);
     method @NonNull public android.graphics.Insets getInsetsIgnoringVisibility(int);
     method @Deprecated @NonNull public android.graphics.Insets getMandatorySystemGestureInsets();
@@ -52158,6 +52200,7 @@
     ctor public WindowInsets.Builder(@NonNull android.view.WindowInsets);
     method @NonNull public android.view.WindowInsets build();
     method @NonNull public android.view.WindowInsets.Builder setDisplayCutout(@Nullable android.view.DisplayCutout);
+    method @NonNull public android.view.WindowInsets.Builder setDisplayShape(@NonNull android.view.DisplayShape);
     method @NonNull public android.view.WindowInsets.Builder setInsets(int, @NonNull android.graphics.Insets);
     method @NonNull public android.view.WindowInsets.Builder setInsetsIgnoringVisibility(int, @NonNull android.graphics.Insets) throws java.lang.IllegalArgumentException;
     method @Deprecated @NonNull public android.view.WindowInsets.Builder setMandatorySystemGestureInsets(@NonNull android.graphics.Insets);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index b6e2d2a..3228ce6 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -121,6 +121,7 @@
     field public static final int GADGET_HAL_V1_0 = 10; // 0xa
     field public static final int GADGET_HAL_V1_1 = 11; // 0xb
     field public static final int GADGET_HAL_V1_2 = 12; // 0xc
+    field public static final int GADGET_HAL_V2_0 = 20; // 0x14
     field public static final int USB_DATA_TRANSFER_RATE_10G = 10240; // 0x2800
     field public static final int USB_DATA_TRANSFER_RATE_20G = 20480; // 0x5000
     field public static final int USB_DATA_TRANSFER_RATE_40G = 40960; // 0xa000
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 057c1ada..13914c5 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2964,6 +2964,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
     method public int getDeviceId();
+    method @Nullable public android.companion.virtual.sensor.VirtualSensor getVirtualSensor(int, @NonNull String);
     method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
     method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean);
@@ -2981,6 +2982,7 @@
     method public int getLockState();
     method @Nullable public String getName();
     method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts();
+    method @NonNull public java.util.List<android.companion.virtual.sensor.VirtualSensorConfig> getVirtualSensorConfigs();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field public static final int ACTIVITY_POLICY_DEFAULT_ALLOWED = 0; // 0x0
     field public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1; // 0x1
@@ -2996,12 +2998,13 @@
 
   public static final class VirtualDeviceParams.Builder {
     ctor public VirtualDeviceParams.Builder();
-    method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addDevicePolicy(int, int);
+    method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addVirtualSensorConfig(@NonNull android.companion.virtual.sensor.VirtualSensorConfig);
     method @NonNull public android.companion.virtual.VirtualDeviceParams build();
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@NonNull java.util.Set<android.content.ComponentName>);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@NonNull java.util.Set<android.content.ComponentName>);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
+    method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDevicePolicy(int, int);
     method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setName(@NonNull String);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setUsersWithMatchingAccounts(@NonNull java.util.Set<android.os.UserHandle>);
@@ -3054,6 +3057,50 @@
 
 }
 
+package android.companion.virtual.sensor {
+
+  public class VirtualSensor {
+    method @NonNull public String getName();
+    method public int getType();
+    method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendSensorEvent(@NonNull android.companion.virtual.sensor.VirtualSensorEvent);
+  }
+
+  public static interface VirtualSensor.SensorStateChangeCallback {
+    method public void onStateChanged(boolean, @NonNull java.time.Duration, @NonNull java.time.Duration);
+  }
+
+  public final class VirtualSensorConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public String getName();
+    method public int getType();
+    method @Nullable public String getVendor();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensorConfig> CREATOR;
+  }
+
+  public static final class VirtualSensorConfig.Builder {
+    ctor public VirtualSensorConfig.Builder(int, @NonNull String);
+    method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig build();
+    method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setStateChangeCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.sensor.VirtualSensor.SensorStateChangeCallback);
+    method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setVendor(@Nullable String);
+  }
+
+  public final class VirtualSensorEvent implements android.os.Parcelable {
+    method public int describeContents();
+    method public long getTimestampNanos();
+    method @NonNull public float[] getValues();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensorEvent> CREATOR;
+  }
+
+  public static final class VirtualSensorEvent.Builder {
+    ctor public VirtualSensorEvent.Builder(@NonNull float[]);
+    method @NonNull public android.companion.virtual.sensor.VirtualSensorEvent build();
+    method @NonNull public android.companion.virtual.sensor.VirtualSensorEvent.Builder setTimestampNanos(long);
+  }
+
+}
+
 package android.content {
 
   public class ApexEnvironment {
@@ -10049,6 +10096,8 @@
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.NewUserResponse createUser(@NonNull android.os.NewUserRequest);
     method @NonNull public java.util.List<android.os.UserHandle> getAllProfiles();
     method @NonNull public java.util.List<android.os.UserHandle> getEnabledProfiles();
+    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public android.os.UserHandle getMainUser();
+    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public android.os.UserHandle getPreviousForegroundUser();
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public android.os.UserHandle getProfileParent(@NonNull android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public int getRemainingCreatableProfileCount(@NonNull String);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public int getRemainingCreatableUserCount(@NonNull String);
@@ -10062,6 +10111,7 @@
     method @Deprecated @android.os.UserManager.UserRestrictionSource @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public int getUserRestrictionSource(String, android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public java.util.List<android.os.UserManager.EnforcingUser> getUserRestrictionSources(String, android.os.UserHandle);
     method @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public int getUserSwitchability();
+    method @NonNull @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.MANAGE_USERS"}) public java.util.Set<android.os.UserHandle> getVisibleUsers();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean hasRestrictedProfiles();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean hasUserRestrictionForUser(@NonNull String, @NonNull android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isAdminUser();
@@ -10079,6 +10129,7 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public boolean isUserNameSet();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isUserOfType(@NonNull String);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isUserUnlockingOrUnlocked(@NonNull android.os.UserHandle);
+    method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.MANAGE_USERS"}) public boolean isUserVisible();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean removeUser(@NonNull android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public int removeUserWhenPossible(@NonNull android.os.UserHandle, boolean);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserIcon(@NonNull android.graphics.Bitmap) throws android.os.UserManager.UserOperationException;
@@ -12825,14 +12876,14 @@
     method @NonNull public android.telephony.BarringInfo createLocationInfoSanitizedCopy();
   }
 
-  public final class CallAttributes implements android.os.Parcelable {
-    ctor public CallAttributes(@NonNull android.telephony.PreciseCallState, int, @NonNull android.telephony.CallQuality);
-    method public int describeContents();
-    method @NonNull public android.telephony.CallQuality getCallQuality();
-    method public int getNetworkType();
-    method @NonNull public android.telephony.PreciseCallState getPreciseCallState();
-    method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallAttributes> CREATOR;
+  @Deprecated public final class CallAttributes implements android.os.Parcelable {
+    ctor @Deprecated public CallAttributes(@NonNull android.telephony.PreciseCallState, int, @NonNull android.telephony.CallQuality);
+    method @Deprecated public int describeContents();
+    method @Deprecated @NonNull public android.telephony.CallQuality getCallQuality();
+    method @Deprecated public int getNetworkType();
+    method @Deprecated @NonNull public android.telephony.PreciseCallState getPreciseCallState();
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
+    field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallAttributes> CREATOR;
   }
 
   public final class CallForwardingInfo implements android.os.Parcelable {
@@ -12912,6 +12963,28 @@
     method @NonNull public android.telephony.CallQuality.Builder setUplinkCallQualityLevel(int);
   }
 
+  public final class CallState implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.telephony.CallQuality getCallQuality();
+    method public int getCallState();
+    method public int getImsCallServiceType();
+    method @Nullable public String getImsCallSessionId();
+    method public int getImsCallType();
+    method public int getNetworkType();
+    method public void writeToParcel(@Nullable android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallState> CREATOR;
+  }
+
+  public static final class CallState.Builder {
+    ctor public CallState.Builder(int);
+    method @NonNull public android.telephony.CallState build();
+    method @NonNull public android.telephony.CallState.Builder setCallQuality(@Nullable android.telephony.CallQuality);
+    method @NonNull public android.telephony.CallState.Builder setImsCallServiceType(int);
+    method @NonNull public android.telephony.CallState.Builder setImsCallSessionId(@Nullable String);
+    method @NonNull public android.telephony.CallState.Builder setImsCallType(int);
+    method @NonNull public android.telephony.CallState.Builder setNetworkType(int);
+  }
+
   public class CarrierConfigManager {
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDefaultCarrierServicePackageName();
     method @NonNull public static android.os.PersistableBundle getDefaultConfig();
@@ -13662,7 +13735,8 @@
   }
 
   public static interface TelephonyCallback.CallAttributesListener {
-    method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onCallAttributesChanged(@NonNull android.telephony.CallAttributes);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public default void onCallAttributesChanged(@NonNull android.telephony.CallAttributes);
+    method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public default void onCallStatesChanged(@NonNull java.util.List<android.telephony.CallState>);
   }
 
   public static interface TelephonyCallback.DataEnabledListener {
@@ -14763,6 +14837,7 @@
     field public static final int CALL_RESTRICT_CAUSE_HD = 3; // 0x3
     field public static final int CALL_RESTRICT_CAUSE_NONE = 0; // 0x0
     field public static final int CALL_RESTRICT_CAUSE_RAT = 1; // 0x1
+    field public static final int CALL_TYPE_NONE = 0; // 0x0
     field public static final int CALL_TYPE_VIDEO_N_VOICE = 3; // 0x3
     field public static final int CALL_TYPE_VOICE = 2; // 0x2
     field public static final int CALL_TYPE_VOICE_N_VIDEO = 1; // 0x1
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 35e01f1..984e822 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1039,6 +1039,7 @@
     method @NonNull public static android.util.Pair<java.util.List<android.graphics.Typeface>,java.util.List<android.graphics.Typeface>> changeDefaultFontForTest(@NonNull java.util.List<android.graphics.Typeface>, @NonNull java.util.List<android.graphics.Typeface>);
     method @NonNull public static long[] deserializeFontMap(@NonNull java.nio.ByteBuffer, @NonNull java.util.Map<java.lang.String,android.graphics.Typeface>) throws java.io.IOException;
     method @Nullable public static android.os.SharedMemory getSystemFontMapSharedMemory();
+    method public void releaseNativeObjectForTest();
     method @NonNull public static android.os.SharedMemory serializeFontMap(@NonNull java.util.Map<java.lang.String,android.graphics.Typeface>) throws android.system.ErrnoException, java.io.IOException;
   }
 
@@ -1251,6 +1252,7 @@
     field public static final int SWITCHING_TYPE_NONE = 0; // 0x0
     field public static final int SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY = 3; // 0x3
     field public static final int SWITCHING_TYPE_WITHIN_GROUPS = 1; // 0x1
+    field public static final int VIRTUAL_DISPLAY_FLAG_OWN_FOCUS = 16384; // 0x4000
     field public static final int VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 512; // 0x200
   }
 
@@ -1907,6 +1909,7 @@
   }
 
   public abstract class VibrationEffect implements android.os.Parcelable {
+    method @Nullable public abstract long[] computeCreateWaveformOffOnTimingsOrNull();
     method public static android.os.VibrationEffect get(int);
     method public static android.os.VibrationEffect get(int, boolean);
     method @Nullable public static android.os.VibrationEffect get(android.net.Uri, android.content.Context);
@@ -1921,6 +1924,7 @@
   }
 
   public static final class VibrationEffect.Composed extends android.os.VibrationEffect {
+    method @Nullable public long[] computeCreateWaveformOffOnTimingsOrNull();
     method public long getDuration();
     method public int getRepeatIndex();
     method @NonNull public java.util.List<android.os.vibrator.VibrationEffectSegment> getSegments();
@@ -2884,6 +2888,10 @@
     method @NonNull public android.view.Display.Mode.Builder setResolution(int, int);
   }
 
+  public final class DisplayShape implements android.os.Parcelable {
+    method @NonNull public static android.view.DisplayShape fromSpecString(@NonNull String, float, int, int);
+  }
+
   public class FocusFinder {
     method public static void sort(android.view.View[], int, int, android.view.ViewGroup, boolean);
   }
diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
index 4d4a4d7..e447d86 100644
--- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java
+++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
@@ -402,7 +402,7 @@
                 mExistingAccounts = AccountManager.get(this).getAccountsForPackage(mCallingPackage,
                         mCallingUid);
                 intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
-                startActivityForResult(intent, REQUEST_ADD_ACCOUNT);
+                startActivityForResult(new Intent(intent), REQUEST_ADD_ACCOUNT);
                 return;
             }
         } catch (OperationCanceledException e) {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 884870b..96ced41 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -502,6 +502,7 @@
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     static volatile Handler sMainThreadHandler;  // set once in main()
+    private long mStartSeq; // Only accesssed from the main thread
 
     Bundle mCoreSettings = null;
 
@@ -6809,6 +6810,14 @@
         Application app;
         final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
         final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();
+
+        final IActivityManager mgr = ActivityManager.getService();
+        try {
+            mgr.finishAttachApplication(mStartSeq);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+
         try {
             // If the app is being launched for full backup or restore, bring it up in
             // a restricted environment with the base application class.
@@ -7649,6 +7658,8 @@
         sCurrentActivityThread = this;
         mConfigurationController = new ConfigurationController(this);
         mSystemThread = system;
+        mStartSeq = startSeq;
+
         if (!system) {
             android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
                                                     UserHandle.myUserId());
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index f25e639..9d5c01a 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -767,11 +767,11 @@
      */
     @SystemApi
     public void setDeliveryGroupMatchingKey(@NonNull String namespace, @NonNull String key) {
-        Preconditions.checkArgument(!namespace.contains("/"),
-                "namespace should not contain '/'");
-        Preconditions.checkArgument(!key.contains("/"),
-                "key should not contain '/'");
-        mDeliveryGroupMatchingKey = namespace + "/" + key;
+        Preconditions.checkArgument(!namespace.contains(":"),
+                "namespace should not contain ':'");
+        Preconditions.checkArgument(!key.contains(":"),
+                "key should not contain ':'");
+        mDeliveryGroupMatchingKey = namespace + ":" + key;
     }
 
     /**
@@ -779,7 +779,7 @@
      * broadcast belongs to.
      *
      * @return the delivery group namespace and key that was previously set using
-     *         {@link #setDeliveryGroupMatchingKey(String, String)}, concatenated with a {@code /}.
+     *         {@link #setDeliveryGroupMatchingKey(String, String)}, concatenated with a {@code :}.
      * @hide
      */
     @SystemApi
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
index 9bf8550..63fdc2e 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -48,6 +48,7 @@
 import android.compat.annotation.EnabledAfter;
 import android.compat.annotation.Overridable;
 import android.content.Context;
+import android.content.PermissionChecker;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
 import android.content.pm.ServiceInfo.ForegroundServiceType;
@@ -879,7 +880,8 @@
         int checkPermission(@NonNull Context context, @NonNull String name, int callerUid,
                 int callerPid, String packageName, boolean allowWhileInUse) {
             // Simple case, check if it's already granted.
-            if (context.checkPermission(name, callerPid, callerUid) == PERMISSION_GRANTED) {
+            if (PermissionChecker.checkPermissionForPreflight(context, name,
+                    callerPid, callerUid, packageName) == PERMISSION_GRANTED) {
                 return PERMISSION_GRANTED;
             }
             if (allowWhileInUse) {
diff --git a/core/java/android/app/GameManager.java b/core/java/android/app/GameManager.java
index f92194d..2f51b17 100644
--- a/core/java/android/app/GameManager.java
+++ b/core/java/android/app/GameManager.java
@@ -213,7 +213,7 @@
     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
     public @GameMode int[] getAvailableGameModes(@NonNull String packageName) {
         try {
-            return mService.getAvailableGameModes(packageName);
+            return mService.getAvailableGameModes(packageName, mContext.getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 902f172..3edaabd 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -147,6 +147,7 @@
     oneway void finishReceiver(in IBinder who, int resultCode, in String resultData, in Bundle map,
             boolean abortBroadcast, int flags);
     void attachApplication(in IApplicationThread app, long startSeq);
+    void finishAttachApplication(long startSeq);
     List<ActivityManager.RunningTaskInfo> getTasks(int maxNum);
     @UnsupportedAppUsage
     void moveTaskToFront(in IApplicationThread caller, in String callingPackage, int task,
@@ -718,8 +719,8 @@
 
     /**
      * Control the app freezer state. Returns true in case of success, false if the operation
-     * didn't succeed (for example, when the app freezer isn't supported). 
-     * Handling the freezer state via this method is reentrant, that is it can be 
+     * didn't succeed (for example, when the app freezer isn't supported).
+     * Handling the freezer state via this method is reentrant, that is it can be
      * disabled and re-enabled multiple times in parallel. As long as there's a 1:1 disable to
      * enable match, the freezer is re-enabled at last enable only.
      * @param enable set it to true to enable the app freezer, false to disable it.
diff --git a/core/java/android/app/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl
index aea097d..3d6ab6f 100644
--- a/core/java/android/app/IGameManagerService.aidl
+++ b/core/java/android/app/IGameManagerService.aidl
@@ -29,7 +29,7 @@
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
     void setGameMode(String packageName, int gameMode, int userId);
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
-    int[] getAvailableGameModes(String packageName);
+    int[] getAvailableGameModes(String packageName, int userId);
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
     boolean isAngleEnabled(String packageName, int userId);
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 6d7a161..3a7d483 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -1128,12 +1128,10 @@
 
     /**
      * Callback called on timeout for {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}.
+     * See {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE} for more details.
      *
-     * TODO Implement it
-     * TODO Javadoc
-     *
-     * @param startId
-     * @hide
+     * @param startId the startId passed to {@link #onStartCommand(Intent, int, int)} when
+     * the service started.
      */
     public void onTimeout(int startId) {
     }
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 295d69d..0837d85 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -19,6 +19,9 @@
 import android.app.PendingIntent;
 import android.companion.virtual.audio.IAudioConfigChangedCallback;
 import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.companion.virtual.sensor.IVirtualSensorStateChangeCallback;
+import android.companion.virtual.sensor.VirtualSensorConfig;
+import android.companion.virtual.sensor.VirtualSensorEvent;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.hardware.input.VirtualKeyEvent;
@@ -97,6 +100,24 @@
     boolean sendTouchEvent(IBinder token, in VirtualTouchEvent event);
 
     /**
+     * Creates a virtual sensor, capable of injecting sensor events into the system.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+    void createVirtualSensor(IBinder tokenm, in VirtualSensorConfig config);
+
+    /**
+     * Removes the sensor corresponding to the given token from the system.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+    void unregisterSensor(IBinder token);
+
+    /**
+     * Sends an event to the virtual sensor corresponding to the given token.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+    boolean sendSensorEvent(IBinder token, in VirtualSensorEvent event);
+
+    /**
      * Launches a pending intent on the given display that is owned by this virtual device.
      */
     void launchPendingIntent(
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 9154701..01b42bf 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -22,12 +22,15 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.app.PendingIntent;
 import android.companion.AssociationInfo;
 import android.companion.virtual.audio.VirtualAudioDevice;
 import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback;
+import android.companion.virtual.sensor.VirtualSensor;
+import android.companion.virtual.sensor.VirtualSensorConfig;
 import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.Point;
@@ -58,6 +61,7 @@
 import java.lang.annotation.Target;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.function.IntConsumer;
 
@@ -76,7 +80,8 @@
                     | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
                     | DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL
                     | DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH
-                    | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
+                    | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP
+                    | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
 
     /**
      * The default device ID, which is the ID of the primary (non-virtual) device.
@@ -88,6 +93,26 @@
      */
     public static final int INVALID_DEVICE_ID = -1;
 
+    /**
+     * Broadcast Action: A Virtual Device was removed.
+     *
+     * <p class="note">This is a protected intent that can only be sent by the system.</p>
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_VIRTUAL_DEVICE_REMOVED =
+            "android.companion.virtual.action.VIRTUAL_DEVICE_REMOVED";
+
+    /**
+     * Int intent extra to be used with {@link #ACTION_VIRTUAL_DEVICE_REMOVED}.
+     * Contains the identifier of the virtual device, which was removed.
+     *
+     * @hide
+     */
+    public static final String EXTRA_VIRTUAL_DEVICE_ID =
+            "android.companion.virtual.extra.VIRTUAL_DEVICE_ID";
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(
@@ -250,7 +275,10 @@
                 };
         @Nullable
         private VirtualAudioDevice mVirtualAudioDevice;
+        @NonNull
+        private List<VirtualSensor> mVirtualSensors = new ArrayList<>();
 
+        @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
         private VirtualDevice(
                 IVirtualDeviceManager service,
                 Context context,
@@ -264,6 +292,10 @@
                     associationId,
                     params,
                     mActivityListenerBinder);
+            final List<VirtualSensorConfig> virtualSensorConfigs = params.getVirtualSensorConfigs();
+            for (int i = 0; i < virtualSensorConfigs.size(); ++i) {
+                mVirtualSensors.add(createVirtualSensor(virtualSensorConfigs.get(i)));
+            }
         }
 
         /**
@@ -278,6 +310,23 @@
         }
 
         /**
+         * Returns this device's sensor with the given type and name, if any.
+         *
+         * @see VirtualDeviceParams.Builder#addVirtualSensorConfig
+         *
+         * @param type The type of the sensor.
+         * @param name The name of the sensor.
+         * @return The matching sensor if found, {@code null} otherwise.
+         */
+        @Nullable
+        public VirtualSensor getVirtualSensor(int type, @NonNull String name) {
+            return mVirtualSensors.stream()
+                    .filter(sensor -> sensor.getType() == type && sensor.getName().equals(name))
+                    .findAny()
+                    .orElse(null);
+        }
+
+        /**
          * Launches a given pending intent on the give display ID.
          *
          * @param displayId The display to launch the pending intent on. This display must be
@@ -437,6 +486,7 @@
         @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
         public void close() {
             try {
+                // This also takes care of unregistering all virtual sensors.
                 mVirtualDevice.close();
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
@@ -622,6 +672,28 @@
         }
 
         /**
+         * Creates a virtual sensor, capable of injecting sensor events into the system. Only for
+         * internal use, since device sensors must remain valid for the entire lifetime of the
+         * device.
+         *
+         * @param config The configuration of the sensor.
+         * @hide
+         */
+        @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+        @NonNull
+        public VirtualSensor createVirtualSensor(@NonNull VirtualSensorConfig config) {
+            Objects.requireNonNull(config);
+            try {
+                final IBinder token = new Binder(
+                        "android.hardware.sensor.VirtualSensor:" + config.getName());
+                mVirtualDevice.createVirtualSensor(token, config);
+                return new VirtualSensor(config.getType(), config.getName(), mVirtualDevice, token);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
          * Adds an activity listener to listen for events such as top activity change or virtual
          * display task stack became empty.
          *
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index f8c2e34a..e2b5c5d 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -23,20 +23,22 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.companion.virtual.sensor.VirtualSensorConfig;
 import android.content.ComponentName;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
 import android.util.ArraySet;
+import android.util.SparseArray;
 import android.util.SparseIntArray;
 
-import com.android.internal.util.Preconditions;
-
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 
@@ -158,6 +160,7 @@
     @Nullable private final String mName;
     // Mapping of @PolicyType to @DevicePolicy
     @NonNull private final SparseIntArray mDevicePolicies;
+    @NonNull private final List<VirtualSensorConfig> mVirtualSensorConfigs;
 
     private VirtualDeviceParams(
             @LockState int lockState,
@@ -169,24 +172,22 @@
             @NonNull Set<ComponentName> blockedActivities,
             @ActivityPolicy int defaultActivityPolicy,
             @Nullable String name,
-            @NonNull SparseIntArray devicePolicies) {
-        Preconditions.checkNotNull(usersWithMatchingAccounts);
-        Preconditions.checkNotNull(allowedCrossTaskNavigations);
-        Preconditions.checkNotNull(blockedCrossTaskNavigations);
-        Preconditions.checkNotNull(allowedActivities);
-        Preconditions.checkNotNull(blockedActivities);
-        Preconditions.checkNotNull(devicePolicies);
-
+            @NonNull SparseIntArray devicePolicies,
+            @NonNull List<VirtualSensorConfig> virtualSensorConfigs) {
         mLockState = lockState;
-        mUsersWithMatchingAccounts = new ArraySet<>(usersWithMatchingAccounts);
-        mAllowedCrossTaskNavigations = new ArraySet<>(allowedCrossTaskNavigations);
-        mBlockedCrossTaskNavigations = new ArraySet<>(blockedCrossTaskNavigations);
+        mUsersWithMatchingAccounts =
+                new ArraySet<>(Objects.requireNonNull(usersWithMatchingAccounts));
+        mAllowedCrossTaskNavigations =
+                new ArraySet<>(Objects.requireNonNull(allowedCrossTaskNavigations));
+        mBlockedCrossTaskNavigations =
+                new ArraySet<>(Objects.requireNonNull(blockedCrossTaskNavigations));
         mDefaultNavigationPolicy = defaultNavigationPolicy;
-        mAllowedActivities = new ArraySet<>(allowedActivities);
-        mBlockedActivities = new ArraySet<>(blockedActivities);
+        mAllowedActivities = new ArraySet<>(Objects.requireNonNull(allowedActivities));
+        mBlockedActivities = new ArraySet<>(Objects.requireNonNull(blockedActivities));
         mDefaultActivityPolicy = defaultActivityPolicy;
         mName = name;
-        mDevicePolicies = devicePolicies;
+        mDevicePolicies = Objects.requireNonNull(devicePolicies);
+        mVirtualSensorConfigs = Objects.requireNonNull(virtualSensorConfigs);
     }
 
     @SuppressWarnings("unchecked")
@@ -201,6 +202,8 @@
         mDefaultActivityPolicy = parcel.readInt();
         mName = parcel.readString8();
         mDevicePolicies = parcel.readSparseIntArray();
+        mVirtualSensorConfigs = new ArrayList<>();
+        parcel.readTypedList(mVirtualSensorConfigs, VirtualSensorConfig.CREATOR);
     }
 
     /**
@@ -310,12 +313,21 @@
      * Returns the policy specified for this policy type, or {@link #DEVICE_POLICY_DEFAULT} if no
      * policy for this type has been explicitly specified.
      *
-     * @see Builder#addDevicePolicy
+     * @see Builder#setDevicePolicy
      */
     public @DevicePolicy int getDevicePolicy(@PolicyType int policyType) {
         return mDevicePolicies.get(policyType, DEVICE_POLICY_DEFAULT);
     }
 
+    /**
+     * Returns the configurations for all sensors that should be created for this device.
+     *
+     * @see Builder#addVirtualSensorConfig
+     */
+    public @NonNull List<VirtualSensorConfig> getVirtualSensorConfigs() {
+        return mVirtualSensorConfigs;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -333,6 +345,7 @@
         dest.writeInt(mDefaultActivityPolicy);
         dest.writeString8(mName);
         dest.writeSparseIntArray(mDevicePolicies);
+        dest.writeTypedList(mVirtualSensorConfigs);
     }
 
     @Override
@@ -428,6 +441,7 @@
         private boolean mDefaultActivityPolicyConfigured = false;
         @Nullable private String mName;
         @NonNull private SparseIntArray mDevicePolicies = new SparseIntArray();
+        @NonNull private List<VirtualSensorConfig> mVirtualSensorConfigs = new ArrayList<>();
 
         /**
          * Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY}
@@ -467,8 +481,7 @@
         @NonNull
         public Builder setUsersWithMatchingAccounts(
                 @NonNull Set<UserHandle> usersWithMatchingAccounts) {
-            Preconditions.checkNotNull(usersWithMatchingAccounts);
-            mUsersWithMatchingAccounts = usersWithMatchingAccounts;
+            mUsersWithMatchingAccounts = Objects.requireNonNull(usersWithMatchingAccounts);
             return this;
         }
 
@@ -491,7 +504,6 @@
         @NonNull
         public Builder setAllowedCrossTaskNavigations(
                 @NonNull Set<ComponentName> allowedCrossTaskNavigations) {
-            Preconditions.checkNotNull(allowedCrossTaskNavigations);
             if (mDefaultNavigationPolicyConfigured
                     && mDefaultNavigationPolicy != NAVIGATION_POLICY_DEFAULT_BLOCKED) {
                 throw new IllegalArgumentException(
@@ -500,7 +512,7 @@
             }
             mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_BLOCKED;
             mDefaultNavigationPolicyConfigured = true;
-            mAllowedCrossTaskNavigations = allowedCrossTaskNavigations;
+            mAllowedCrossTaskNavigations = Objects.requireNonNull(allowedCrossTaskNavigations);
             return this;
         }
 
@@ -523,7 +535,6 @@
         @NonNull
         public Builder setBlockedCrossTaskNavigations(
                 @NonNull Set<ComponentName> blockedCrossTaskNavigations) {
-            Preconditions.checkNotNull(blockedCrossTaskNavigations);
             if (mDefaultNavigationPolicyConfigured
                      && mDefaultNavigationPolicy != NAVIGATION_POLICY_DEFAULT_ALLOWED) {
                 throw new IllegalArgumentException(
@@ -532,7 +543,7 @@
             }
             mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_ALLOWED;
             mDefaultNavigationPolicyConfigured = true;
-            mBlockedCrossTaskNavigations = blockedCrossTaskNavigations;
+            mBlockedCrossTaskNavigations = Objects.requireNonNull(blockedCrossTaskNavigations);
             return this;
         }
 
@@ -551,7 +562,6 @@
          */
         @NonNull
         public Builder setAllowedActivities(@NonNull Set<ComponentName> allowedActivities) {
-            Preconditions.checkNotNull(allowedActivities);
             if (mDefaultActivityPolicyConfigured
                     && mDefaultActivityPolicy != ACTIVITY_POLICY_DEFAULT_BLOCKED) {
                 throw new IllegalArgumentException(
@@ -559,7 +569,7 @@
             }
             mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_BLOCKED;
             mDefaultActivityPolicyConfigured = true;
-            mAllowedActivities = allowedActivities;
+            mAllowedActivities = Objects.requireNonNull(allowedActivities);
             return this;
         }
 
@@ -578,7 +588,6 @@
          */
         @NonNull
         public Builder setBlockedActivities(@NonNull Set<ComponentName> blockedActivities) {
-            Preconditions.checkNotNull(blockedActivities);
             if (mDefaultActivityPolicyConfigured
                     && mDefaultActivityPolicy != ACTIVITY_POLICY_DEFAULT_ALLOWED) {
                 throw new IllegalArgumentException(
@@ -586,7 +595,7 @@
             }
             mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_ALLOWED;
             mDefaultActivityPolicyConfigured = true;
-            mBlockedActivities = blockedActivities;
+            mBlockedActivities = Objects.requireNonNull(blockedActivities);
             return this;
         }
 
@@ -606,7 +615,7 @@
         }
 
         /**
-         * Add a policy for this virtual device.
+         * Specifies a policy for this virtual device.
          *
          * Policies define the system behavior that may be specific for this virtual device. A
          * policy can be defined for each {@code PolicyType}, but they are all optional.
@@ -615,16 +624,55 @@
          * @param devicePolicy the value of the policy, i.e. how to interpret the device behavior.
          */
         @NonNull
-        public Builder addDevicePolicy(@PolicyType int policyType, @DevicePolicy int devicePolicy) {
+        public Builder setDevicePolicy(@PolicyType int policyType, @DevicePolicy int devicePolicy) {
             mDevicePolicies.put(policyType, devicePolicy);
             return this;
         }
 
         /**
+         * Adds a configuration for a sensor that should be created for this virtual device.
+         *
+         * <p>Device sensors must remain valid for the entire lifetime of the device, hence they are
+         * created together with the device itself, and removed when the device is removed.
+         *
+         * <p>Requires {@link #DEVICE_POLICY_CUSTOM} to be set for {@link #POLICY_TYPE_SENSORS}.
+         *
+         * @see android.companion.virtual.sensor.VirtualSensor
+         * @see #setDevicePolicy
+         */
+        @NonNull
+        public Builder addVirtualSensorConfig(@NonNull VirtualSensorConfig virtualSensorConfig) {
+            mVirtualSensorConfigs.add(Objects.requireNonNull(virtualSensorConfig));
+            return this;
+        }
+
+        /**
          * Builds the {@link VirtualDeviceParams} instance.
+         *
+         * @throws IllegalArgumentException if there's mismatch between policy definition and
+         * the passed parameters or if there are sensor configs with the same type and name.
+         *
          */
         @NonNull
         public VirtualDeviceParams build() {
+            if (!mVirtualSensorConfigs.isEmpty()
+                    && (mDevicePolicies.get(POLICY_TYPE_SENSORS, DEVICE_POLICY_DEFAULT)
+                            != DEVICE_POLICY_CUSTOM)) {
+                throw new IllegalArgumentException(
+                        "DEVICE_POLICY_CUSTOM for POLICY_TYPE_SENSORS is required for creating "
+                                + "virtual sensors.");
+            }
+            SparseArray<Set<String>> sensorNameByType = new SparseArray();
+            for (int i = 0; i < mVirtualSensorConfigs.size(); ++i) {
+                VirtualSensorConfig config = mVirtualSensorConfigs.get(i);
+                Set<String> sensorNames = sensorNameByType.get(config.getType(), new ArraySet<>());
+                if (!sensorNames.add(config.getName())) {
+                    throw new IllegalArgumentException(
+                            "Sensor names must be unique for a particular sensor type.");
+                }
+                sensorNameByType.put(config.getType(), sensorNames);
+            }
+
             return new VirtualDeviceParams(
                     mLockState,
                     mUsersWithMatchingAccounts,
@@ -635,7 +683,8 @@
                     mBlockedActivities,
                     mDefaultActivityPolicy,
                     mName,
-                    mDevicePolicies);
+                    mDevicePolicies,
+                    mVirtualSensorConfigs);
         }
     }
 }
diff --git a/core/java/android/companion/virtual/sensor/IVirtualSensorStateChangeCallback.aidl b/core/java/android/companion/virtual/sensor/IVirtualSensorStateChangeCallback.aidl
new file mode 100644
index 0000000..b99cc7e
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/IVirtualSensorStateChangeCallback.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 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.companion.virtual.sensor;
+
+/**
+ * Interface for notification of listener registration changes for a virtual sensor.
+ *
+ * @hide
+ */
+oneway interface IVirtualSensorStateChangeCallback {
+
+    /**
+     * Called when the registered listeners to a virtual sensor have changed.
+     *
+     * @param enabled Whether the sensor is enabled.
+     * @param samplingPeriodMicros The requested sensor's sampling period in microseconds.
+     * @param batchReportingLatencyMicros The requested maximum time interval in microseconds
+     * between the delivery of two batches of sensor events.
+     */
+    void onStateChanged(boolean enabled, int samplingPeriodMicros, int batchReportLatencyMicros);
+}
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensor.java b/core/java/android/companion/virtual/sensor/VirtualSensor.java
new file mode 100644
index 0000000..a184481
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/VirtualSensor.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2022 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.companion.virtual.sensor;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.companion.virtual.IVirtualDevice;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import java.time.Duration;
+
+/**
+ * Representation of a sensor on a remote device, capable of sending events, such as an
+ * accelerometer or a gyroscope.
+ *
+ * This registers the sensor device with the sensor framework as a runtime sensor.
+ *
+ * @hide
+ */
+@SystemApi
+public class VirtualSensor {
+
+    /**
+     * Interface for notification of listener registration changes for a virtual sensor.
+     */
+    public interface SensorStateChangeCallback {
+        /**
+         * Called when the registered listeners to a virtual sensor have changed.
+         *
+         * @param enabled Whether the sensor is enabled.
+         * @param samplingPeriod The requested sampling period of the sensor.
+         * @param batchReportLatency The requested maximum time interval between the delivery of two
+         * batches of sensor events.
+         */
+        void onStateChanged(boolean enabled, @NonNull Duration samplingPeriod,
+                @NonNull Duration batchReportLatency);
+    }
+
+    private final int mType;
+    private final String mName;
+    private final IVirtualDevice mVirtualDevice;
+    private final IBinder mToken;
+
+    /**
+     * @hide
+     */
+    public VirtualSensor(int type, String name, IVirtualDevice virtualDevice, IBinder token) {
+        mType = type;
+        mName = name;
+        mVirtualDevice = virtualDevice;
+        mToken = token;
+    }
+
+    /**
+     * Returns the
+     * <a href="https://source.android.com/devices/sensors/sensor-types">type</a> of the sensor.
+     */
+    public int getType() {
+        return mType;
+    }
+
+    /**
+     * Returns the name of the sensor.
+     */
+    @NonNull
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * Send a sensor event to the system.
+     */
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public void sendSensorEvent(@NonNull VirtualSensorEvent event) {
+        try {
+            mVirtualDevice.sendSensorEvent(mToken, event);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.aidl b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.aidl
new file mode 100644
index 0000000..48b463a
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 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.companion.virtual.sensor;
+
+parcelable VirtualSensorConfig;
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
new file mode 100644
index 0000000..7982fa5
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2022 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.companion.virtual.sensor;
+
+import static java.util.concurrent.TimeUnit.MICROSECONDS;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.time.Duration;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Configuration for creation of a virtual sensor.
+ * @see VirtualSensor
+ * @hide
+ */
+@SystemApi
+public final class VirtualSensorConfig implements Parcelable {
+
+    private final int mType;
+    @NonNull
+    private final String mName;
+    @Nullable
+    private final String mVendor;
+    @Nullable
+    private final IVirtualSensorStateChangeCallback mStateChangeCallback;
+
+    private VirtualSensorConfig(int type, @NonNull String name, @Nullable String vendor,
+            @Nullable IVirtualSensorStateChangeCallback stateChangeCallback) {
+        mType = type;
+        mName = name;
+        mVendor = vendor;
+        mStateChangeCallback = stateChangeCallback;
+    }
+
+    private VirtualSensorConfig(@NonNull Parcel parcel) {
+        mType = parcel.readInt();
+        mName = parcel.readString8();
+        mVendor = parcel.readString8();
+        mStateChangeCallback =
+                IVirtualSensorStateChangeCallback.Stub.asInterface(parcel.readStrongBinder());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel parcel, int flags) {
+        parcel.writeInt(mType);
+        parcel.writeString8(mName);
+        parcel.writeString8(mVendor);
+        parcel.writeStrongBinder(
+                mStateChangeCallback != null ? mStateChangeCallback.asBinder() : null);
+    }
+
+    /**
+     * Returns the
+     * <a href="https://source.android.com/devices/sensors/sensor-types">type</a> of the sensor.
+     */
+    public int getType() {
+        return mType;
+    }
+
+    /**
+     * Returns the name of the sensor, which must be unique per sensor type for each virtual device.
+     */
+    @NonNull
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * Returns the vendor string of the sensor.
+     * @see Builder#setVendor
+     */
+    @Nullable
+    public String getVendor() {
+        return mVendor;
+    }
+
+    /**
+     * Returns the callback to get notified about changes in the sensor listeners.
+     * @hide
+     */
+    @Nullable
+    public IVirtualSensorStateChangeCallback getStateChangeCallback() {
+        return mStateChangeCallback;
+    }
+
+    /**
+     * Builder for {@link VirtualSensorConfig}.
+     */
+    public static final class Builder {
+
+        private final int mType;
+        @NonNull
+        private final String mName;
+        @Nullable
+        private String mVendor;
+        @Nullable
+        private IVirtualSensorStateChangeCallback mStateChangeCallback;
+
+        private static class SensorStateChangeCallbackDelegate
+                extends IVirtualSensorStateChangeCallback.Stub {
+            @NonNull
+            private final Executor mExecutor;
+            @NonNull
+            private final VirtualSensor.SensorStateChangeCallback mCallback;
+
+            SensorStateChangeCallbackDelegate(@NonNull @CallbackExecutor Executor executor,
+                    @NonNull VirtualSensor.SensorStateChangeCallback callback) {
+                mCallback = callback;
+                mExecutor = executor;
+            }
+            @Override
+            public void onStateChanged(boolean enabled, int samplingPeriodMicros,
+                    int batchReportLatencyMicros) {
+                final Duration samplingPeriod =
+                        Duration.ofNanos(MICROSECONDS.toNanos(samplingPeriodMicros));
+                final Duration batchReportingLatency =
+                        Duration.ofNanos(MICROSECONDS.toNanos(batchReportLatencyMicros));
+                mExecutor.execute(() -> mCallback.onStateChanged(
+                        enabled, samplingPeriod, batchReportingLatency));
+            }
+        }
+
+        /**
+         * Creates a new builder.
+         *
+         * @param type The
+         * <a href="https://source.android.com/devices/sensors/sensor-types">type</a> of the sensor.
+         * @param name The name of the sensor. Must be unique among all sensors with the same type
+         * that belong to the same virtual device.
+         */
+        public Builder(int type, @NonNull String name) {
+            mType = type;
+            mName = Objects.requireNonNull(name);
+        }
+
+        /**
+         * Creates a new {@link VirtualSensorConfig}.
+         */
+        @NonNull
+        public VirtualSensorConfig build() {
+            return new VirtualSensorConfig(mType, mName, mVendor, mStateChangeCallback);
+        }
+
+        /**
+         * Sets the vendor string of the sensor.
+         */
+        @NonNull
+        public VirtualSensorConfig.Builder setVendor(@Nullable String vendor) {
+            mVendor = vendor;
+            return this;
+        }
+
+        /**
+         * Sets the callback to get notified about changes in the sensor listeners.
+         *
+         * @param executor The executor where the callback is executed on.
+         * @param callback The callback to get notified when the state of the sensor
+         * listeners has changed, see {@link VirtualSensor.SensorStateChangeCallback}
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public VirtualSensorConfig.Builder setStateChangeCallback(
+                @NonNull @CallbackExecutor Executor executor,
+                @NonNull VirtualSensor.SensorStateChangeCallback callback) {
+            mStateChangeCallback = new SensorStateChangeCallbackDelegate(
+                    Objects.requireNonNull(executor),
+                    Objects.requireNonNull(callback));
+            return this;
+        }
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<VirtualSensorConfig> CREATOR =
+            new Parcelable.Creator<>() {
+                public VirtualSensorConfig createFromParcel(Parcel source) {
+                    return new VirtualSensorConfig(source);
+                }
+
+                public VirtualSensorConfig[] newArray(int size) {
+                    return new VirtualSensorConfig[size];
+                }
+            };
+}
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorEvent.aidl b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.aidl
new file mode 100644
index 0000000..9943946
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 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.companion.virtual.sensor;
+
+parcelable VirtualSensorEvent;
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java
new file mode 100644
index 0000000..8f8860e
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2022 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.companion.virtual.sensor;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+
+
+/**
+ * A sensor event that originated from a virtual device's sensor.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualSensorEvent implements Parcelable {
+
+    @NonNull
+    private float[] mValues;
+    private long mTimestampNanos;
+
+    private VirtualSensorEvent(@NonNull float[] values, long timestampNanos) {
+        mValues = values;
+        mTimestampNanos = timestampNanos;
+    }
+
+    private VirtualSensorEvent(@NonNull Parcel parcel) {
+        final int valuesLength = parcel.readInt();
+        mValues = new float[valuesLength];
+        parcel.readFloatArray(mValues);
+        mTimestampNanos = parcel.readLong();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel parcel, int parcelableFlags) {
+        parcel.writeInt(mValues.length);
+        parcel.writeFloatArray(mValues);
+        parcel.writeLong(mTimestampNanos);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Returns the values of this sensor event. The length and contents depend on the
+     * <a href="https://source.android.com/devices/sensors/sensor-types">sensor type</a>.
+     * @see android.hardware.SensorEvent#values
+     */
+    @NonNull
+    public float[] getValues() {
+        return mValues;
+    }
+
+    /**
+     * The time in nanoseconds at which the event happened. For a given sensor, each new sensor
+     * event should be monotonically increasing.
+     *
+     * @see Builder#setTimestampNanos(long)
+     */
+    public long getTimestampNanos() {
+        return mTimestampNanos;
+    }
+
+    /**
+     * Builder for {@link VirtualSensorEvent}.
+     */
+    public static final class Builder {
+
+        @NonNull
+        private float[] mValues;
+        private long mTimestampNanos = 0;
+
+        /**
+         * Creates a new builder.
+         * @param values the values of the sensor event. @see android.hardware.SensorEvent#values
+         */
+        public Builder(@NonNull float[] values) {
+            mValues = values;
+        }
+
+        /**
+         * Creates a new {@link VirtualSensorEvent}.
+         */
+        @NonNull
+        public VirtualSensorEvent build() {
+            if (mValues == null || mValues.length == 0) {
+                throw new IllegalArgumentException(
+                        "Cannot build virtual sensor event with no values.");
+            }
+            if (mTimestampNanos <= 0) {
+                mTimestampNanos = SystemClock.elapsedRealtimeNanos();
+            }
+            return new VirtualSensorEvent(mValues, mTimestampNanos);
+        }
+
+        /**
+         * Sets the timestamp of this event. For a given sensor, each new sensor event should be
+         * monotonically increasing using the same time base as
+         * {@link android.os.SystemClock#elapsedRealtimeNanos()}.
+         *
+         * If not explicitly set, the current timestamp is used for the sensor event.
+         *
+         * @see android.hardware.SensorEvent#timestamp
+         */
+        @NonNull
+        public Builder setTimestampNanos(long timestampNanos) {
+            mTimestampNanos = timestampNanos;
+            return this;
+        }
+    }
+
+    public static final @NonNull Parcelable.Creator<VirtualSensorEvent> CREATOR =
+            new Parcelable.Creator<>() {
+                public VirtualSensorEvent createFromParcel(Parcel source) {
+                    return new VirtualSensorEvent(source);
+                }
+
+                public VirtualSensorEvent[] newArray(int size) {
+                    return new VirtualSensorEvent[size];
+                }
+            };
+}
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index cc7977a..99fc5a3 100644
--- a/core/java/android/content/om/FabricatedOverlay.java
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -16,15 +16,21 @@
 
 package android.content.om;
 
+import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.FabricatedOverlayInternal;
 import android.os.FabricatedOverlayInternalEntry;
 import android.os.ParcelFileDescriptor;
 import android.text.TextUtils;
+import android.util.TypedValue;
 
+import com.android.internal.content.om.OverlayManagerImpl;
 import com.android.internal.util.Preconditions;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Objects;
 
@@ -82,8 +88,24 @@
         }
 
         /**
+         * Constructs a builder for building a fabricated overlay.
+         *
+         * @param name a name used to uniquely identify the fabricated overlay owned by the caller
+         *             itself.
+         * @param targetPackage the name of the package to overlay
+         */
+        public Builder(@NonNull String name, @NonNull String targetPackage) {
+            mName = OverlayManagerImpl.checkOverlayNameValid(name);
+            mTargetPackage =
+                    Preconditions.checkStringNotEmpty(
+                            targetPackage, "'targetPackage' must not be empty nor null");
+            mOwningPackage = ""; // The package name is filled in OverlayManager.commit
+        }
+
+        /**
          * Sets the name of the overlayable resources to overlay (can be null).
          */
+        @NonNull
         public Builder setTargetOverlayable(@Nullable String targetOverlayable) {
             mTargetOverlayable = TextUtils.emptyIfNull(targetOverlayable);
             return this;
@@ -111,45 +133,110 @@
         }
 
         /**
-         * Sets the value of the fabricated overlay
+         * Sets the value of the fabricated overlay for the integer-like types.
          *
          * @param resourceName name of the target resource to overlay (in the form
-         *                     [package]:type/entry)
+         *     [package]:type/entry)
          * @param dataType the data type of the new value
          * @param value the unsigned 32 bit integer representing the new value
-         *
+         * @return the builder itself
+         * @see #setResourceValue(String, int, int, String)
          * @see android.util.TypedValue#type
          */
-        public Builder setResourceValue(@NonNull String resourceName, int dataType, int value) {
+        @NonNull
+        public Builder setResourceValue(
+                @NonNull String resourceName,
+                @IntRange(from = TypedValue.TYPE_FIRST_INT, to = TypedValue.TYPE_LAST_INT)
+                        int dataType,
+                int value) {
+            return setResourceValue(resourceName, dataType, value, null /* configuration */);
+        }
+
+        /**
+         * Sets the value of the fabricated overlay for the integer-like types with the
+         * configuration.
+         *
+         * @param resourceName name of the target resource to overlay (in the form
+         *     [package]:type/entry)
+         * @param dataType the data type of the new value
+         * @param value the unsigned 32 bit integer representing the new value
+         * @param configuration The string representation of the config this overlay is enabled for
+         * @see android.util.TypedValue#type
+         */
+        @NonNull
+        public Builder setResourceValue(
+                @NonNull String resourceName,
+                @IntRange(from = TypedValue.TYPE_FIRST_INT, to = TypedValue.TYPE_LAST_INT)
+                        int dataType,
+                int value,
+                @Nullable String configuration) {
             ensureValidResourceName(resourceName);
 
             final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
             entry.resourceName = resourceName;
-            entry.dataType = dataType;
+            entry.dataType =
+                    Preconditions.checkArgumentInRange(
+                            dataType,
+                            TypedValue.TYPE_FIRST_INT,
+                            TypedValue.TYPE_LAST_INT,
+                            "dataType");
             entry.data = value;
+            entry.configuration = configuration;
             mEntries.add(entry);
             return this;
         }
 
+        /** @hide */
+        @IntDef(
+                prefix = {"OVERLAY_TYPE"},
+                value = {
+                    TypedValue.TYPE_STRING,
+                })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface StringTypeOverlayResource {}
+
         /**
-         * Sets the value of the fabricated overlay
+         * Sets the value of the fabricated overlay for the string-like type.
          *
          * @param resourceName name of the target resource to overlay (in the form
-         *                     [package]:type/entry)
+         *     [package]:type/entry)
          * @param dataType the data type of the new value
-         * @param value the unsigned 32 bit integer representing the new value
-         * @param configuration The string representation of the config this overlay is enabled for
-         *
+         * @param value the string representing the new value
+         * @return the builder itself
          * @see android.util.TypedValue#type
          */
-        public Builder setResourceValue(@NonNull String resourceName, int dataType, int value,
-                String configuration) {
+        @NonNull
+        public Builder setResourceValue(
+                @NonNull String resourceName,
+                @StringTypeOverlayResource int dataType,
+                @NonNull String value) {
+            return setResourceValue(resourceName, dataType, value, null /* configuration */);
+        }
+
+        /**
+         * Sets the value of the fabricated overlay for the string-like type with the configuration.
+         *
+         * @param resourceName name of the target resource to overlay (in the form
+         *     [package]:type/entry)
+         * @param dataType the data type of the new value
+         * @param value the string representing the new value
+         * @param configuration The string representation of the config this overlay is enabled for
+         * @see android.util.TypedValue#type
+         */
+        @NonNull
+        public Builder setResourceValue(
+                @NonNull String resourceName,
+                @StringTypeOverlayResource int dataType,
+                @NonNull String value,
+                @Nullable String configuration) {
             ensureValidResourceName(resourceName);
 
             final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
             entry.resourceName = resourceName;
-            entry.dataType = dataType;
-            entry.data = value;
+            entry.dataType =
+                    Preconditions.checkArgumentInRange(
+                            dataType, TypedValue.TYPE_STRING, TypedValue.TYPE_FRACTION, "dataType");
+            entry.stringData = Objects.requireNonNull(value);
             entry.configuration = configuration;
             mEntries.add(entry);
             return this;
@@ -159,68 +246,32 @@
          * Sets the value of the fabricated overlay
          *
          * @param resourceName name of the target resource to overlay (in the form
-         *                     [package]:type/entry)
-         * @param dataType the data type of the new value
-         * @param value the string representing the new value
-         *
-         * @see android.util.TypedValue#type
-         */
-        public Builder setResourceValue(@NonNull String resourceName, int dataType, String value) {
-            ensureValidResourceName(resourceName);
-
-            final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
-            entry.resourceName = resourceName;
-            entry.dataType = dataType;
-            entry.stringData = value;
-            mEntries.add(entry);
-            return this;
-        }
-
-        /**
-         * Sets the value of the fabricated overlay
-         *
-         * @param resourceName name of the target resource to overlay (in the form
-         *                     [package]:type/entry)
-         * @param dataType the data type of the new value
-         * @param value the string representing the new value
-         * @param configuration The string representation of the config this overlay is enabled for
-         *
-         * @see android.util.TypedValue#type
-         */
-        public Builder setResourceValue(@NonNull String resourceName, int dataType, String value,
-                String configuration) {
-            ensureValidResourceName(resourceName);
-
-            final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
-            entry.resourceName = resourceName;
-            entry.dataType = dataType;
-            entry.stringData = value;
-            entry.configuration = configuration;
-            mEntries.add(entry);
-            return this;
-        }
-
-        /**
-         * Sets the value of the fabricated overlay
-         *
-         * @param resourceName name of the target resource to overlay (in the form
-         *                     [package]:type/entry)
+         *     [package]:type/entry)
          * @param value the file descriptor whose contents are the value of the frro
          * @param configuration The string representation of the config this overlay is enabled for
+         * @return the builder itself
          */
-        public Builder setResourceValue(@NonNull String resourceName, ParcelFileDescriptor value,
-                String configuration) {
+        @NonNull
+        public Builder setResourceValue(
+                @NonNull String resourceName,
+                @NonNull ParcelFileDescriptor value,
+                @Nullable String configuration) {
             ensureValidResourceName(resourceName);
 
             final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
             entry.resourceName = resourceName;
-            entry.binaryData = value;
+            entry.binaryData = Objects.requireNonNull(value);
             entry.configuration = configuration;
             mEntries.add(entry);
             return this;
         }
 
-        /** Builds an immutable fabricated overlay. */
+        /**
+         * Builds an immutable fabricated overlay.
+         *
+         * @return the fabricated overlay
+         */
+        @NonNull
         public FabricatedOverlay build() {
             final FabricatedOverlayInternal overlay = new FabricatedOverlayInternal();
             overlay.packageName = mOwningPackage;
diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java
index 812f6b0..ed1f6a2 100644
--- a/core/java/android/content/om/OverlayManager.java
+++ b/core/java/android/content/om/OverlayManager.java
@@ -17,6 +17,7 @@
 package android.content.om;
 
 import android.annotation.NonNull;
+import android.annotation.NonUiContext;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
@@ -25,12 +26,16 @@
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Build;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 
+import com.android.internal.content.om.OverlayManagerImpl;
+
+import java.io.IOException;
 import java.util.List;
 
 /**
@@ -76,6 +81,7 @@
 
     private final IOverlayManager mService;
     private final Context mContext;
+    private final OverlayManagerImpl mOverlayManagerImpl;
 
     /**
      * Pre R a {@link java.lang.SecurityException} would only be thrown by setEnabled APIs (e
@@ -117,6 +123,7 @@
     public OverlayManager(Context context, IOverlayManager service) {
         mContext = context;
         mService = service;
+        mOverlayManagerImpl = new OverlayManagerImpl(context);
     }
 
     /** @hide */
@@ -301,6 +308,17 @@
      * @hide
      */
     public void commit(@NonNull final OverlayManagerTransaction transaction) {
+        if (transaction.isSelfTargetingTransaction()
+                || mService == null
+                || mService.asBinder() == null) {
+            try {
+                commitSelfTarget(transaction);
+            } catch (PackageManager.NameNotFoundException | IOException e) {
+                throw new RuntimeException(e);
+            }
+            return;
+        }
+
         try {
             mService.commit(transaction);
         } catch (RemoteException e) {
@@ -332,4 +350,48 @@
             throw e;
         }
     }
+
+    /**
+     * Get a OverlayManagerTransaction.Builder to build out a overlay manager transaction.
+     *
+     * @return a builder of the overlay manager transaction.
+     * @hide
+     */
+    @NonNull
+    public OverlayManagerTransaction.Builder beginTransaction() {
+        return new OverlayManagerTransaction.Builder(this);
+    }
+
+    /**
+     * Commit the self-targeting transaction to register or unregister overlays.
+     *
+     * <p>Applications can request OverlayManager to register overlays and unregister the registered
+     * overlays via {@link OverlayManagerTransaction}.
+     *
+     * @throws IOException if there is a file operation error.
+     * @throws PackageManager.NameNotFoundException if the package name is not found.
+     * @hide
+     */
+    @NonUiContext
+    void commitSelfTarget(@NonNull final OverlayManagerTransaction transaction)
+            throws PackageManager.NameNotFoundException, IOException {
+        synchronized (mOverlayManagerImpl) {
+            mOverlayManagerImpl.commit(transaction);
+        }
+    }
+
+    /**
+     * Get the related information of overlays for {@code targetPackageName}.
+     *
+     * @param targetPackageName the target package name
+     * @return a list of overlay information
+     * @hide
+     */
+    @NonNull
+    @NonUiContext
+    public List<OverlayInfo> getOverlayInfosForTarget(@NonNull final String targetPackageName) {
+        synchronized (mOverlayManagerImpl) {
+            return mOverlayManagerImpl.getOverlayInfosForTarget(targetPackageName);
+        }
+    }
 }
diff --git a/core/java/android/content/om/OverlayManagerTransaction.java b/core/java/android/content/om/OverlayManagerTransaction.java
index 868dab2..42b3ef3 100644
--- a/core/java/android/content/om/OverlayManagerTransaction.java
+++ b/core/java/android/content/om/OverlayManagerTransaction.java
@@ -20,19 +20,22 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.NonUiContext;
 import android.annotation.Nullable;
-import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
 
+import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
+import java.util.Objects;
 
 /**
  * Container for a batch of requests to the OverlayManagerService.
@@ -53,13 +56,16 @@
     // TODO: remove @hide from this class when OverlayManager is added to the
     // SDK, but keep OverlayManagerTransaction.Request @hidden
     private final List<Request> mRequests;
+    private final OverlayManager mOverlayManager;
 
-    OverlayManagerTransaction(@NonNull final List<Request> requests) {
+    OverlayManagerTransaction(
+            @NonNull final List<Request> requests, @Nullable OverlayManager overlayManager) {
         checkNotNull(requests);
         if (requests.contains(null)) {
             throw new IllegalArgumentException("null request");
         }
         mRequests = requests;
+        mOverlayManager = overlayManager;
     }
 
     private OverlayManagerTransaction(@NonNull final Parcel source) {
@@ -72,6 +78,7 @@
             final Bundle extras = source.readBundle(null);
             mRequests.add(new Request(request, overlay, userId, extras));
         }
+        mOverlayManager = null;
     }
 
     @Override
@@ -156,6 +163,20 @@
      */
     public static class Builder {
         private final List<Request> mRequests = new ArrayList<>();
+        @Nullable private final OverlayManager mOverlayManager;
+
+        public Builder() {
+            mOverlayManager = null;
+        }
+
+        /**
+         * The transaction builder for self-targeting.
+         *
+         * @param overlayManager is not null if the transaction is for self-targeting.
+         */
+        Builder(@NonNull OverlayManager overlayManager) {
+            mOverlayManager = Objects.requireNonNull(overlayManager);
+        }
 
         /**
          * Request that an overlay package be enabled and change its loading
@@ -205,7 +226,10 @@
          *
          * @hide
          */
+        @NonNull
         public Builder registerFabricatedOverlay(@NonNull FabricatedOverlay overlay) {
+            Objects.requireNonNull(overlay);
+
             final Bundle extras = new Bundle();
             extras.putParcelable(Request.BUNDLE_FABRICATED_OVERLAY, overlay.mOverlay);
             mRequests.add(new Request(Request.TYPE_REGISTER_FABRICATED, overlay.getIdentifier(),
@@ -220,7 +244,10 @@
          *
          * @hide
          */
+        @NonNull
         public Builder unregisterFabricatedOverlay(@NonNull OverlayIdentifier overlay) {
+            Objects.requireNonNull(overlay);
+
             mRequests.add(new Request(Request.TYPE_UNREGISTER_FABRICATED, overlay,
                     UserHandle.USER_ALL));
             return this;
@@ -233,8 +260,9 @@
          * @see OverlayManager#commit
          * @return a new transaction
          */
+        @NonNull
         public OverlayManagerTransaction build() {
-            return new OverlayManagerTransaction(mRequests);
+            return new OverlayManagerTransaction(mRequests, mOverlayManager);
         }
     }
 
@@ -269,4 +297,23 @@
             return new OverlayManagerTransaction[size];
         }
     };
+
+    /**
+     * Commit the overlay manager transaction to register or unregister overlays for self-targeting.
+     *
+     * <p>Applications can register overlays and unregister the registered overlays via {@link
+     * OverlayManagerTransaction}.
+     *
+     * @throws IOException if there is a file operation error.
+     * @throws PackageManager.NameNotFoundException if the package name is not found.
+     * @hide
+     */
+    @NonUiContext
+    public void commit() throws PackageManager.NameNotFoundException, IOException {
+        mOverlayManager.commitSelfTarget(this);
+    }
+
+    boolean isSelfTargetingTransaction() {
+        return mOverlayManager != null;
+    }
 }
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 12911d6..1e928bd 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -23,6 +23,7 @@
 import android.content.pm.ParceledListSlice;
 import android.content.pm.VersionedPackage;
 import android.content.IntentSender;
+import android.os.RemoteCallback;
 
 import android.graphics.Bitmap;
 
@@ -66,4 +67,6 @@
 
     void setAllowUnlimitedSilentUpdates(String installerPackageName);
     void setSilentUpdatesThrottleTime(long throttleTimeInSeconds);
+    void checkInstallConstraints(String installerPackageName, in List<String> packageNames,
+            in PackageInstaller.InstallConstraints constraints, in RemoteCallback callback);
 }
diff --git a/core/java/android/content/pm/PackageInstaller.aidl b/core/java/android/content/pm/PackageInstaller.aidl
index 833919e..ab9d4f3 100644
--- a/core/java/android/content/pm/PackageInstaller.aidl
+++ b/core/java/android/content/pm/PackageInstaller.aidl
@@ -16,6 +16,7 @@
 
 package android.content.pm;
 
+parcelable PackageInstaller.InstallConstraints;
 parcelable PackageInstaller.SessionParams;
 parcelable PackageInstaller.SessionInfo;
 parcelable PackageInstaller.PreapprovalDetails;
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 3551827..c79f99d 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -36,6 +36,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.app.ActivityManager;
@@ -57,6 +58,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
 import android.os.ParcelableException;
+import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -89,6 +91,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 /**
  * Offers the ability to install, upgrade, and remove applications on the
@@ -854,6 +857,29 @@
     }
 
     /**
+     * Check if install constraints are satisfied for the given packages.
+     *
+     * Note this query result is just a hint and subject to race because system states could
+     * change anytime in-between this query and committing the session.
+     *
+     * The result is returned by a callback because some constraints might take a long time
+     * to evaluate.
+     */
+    public void checkInstallConstraints(@NonNull List<String> packageNames,
+            @NonNull InstallConstraints constraints,
+            @NonNull Consumer<InstallConstraintsResult> callback) {
+        try {
+            var remoteCallback = new RemoteCallback(b -> {
+                callback.accept(b.getParcelable("result", InstallConstraintsResult.class));
+            });
+            mInstaller.checkInstallConstraints(
+                    mInstallerPackageName, packageNames, constraints, remoteCallback);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Events for observing session lifecycle.
      * <p>
      * A typical session lifecycle looks like this:
@@ -3647,4 +3673,362 @@
         // End of generated code
 
     }
+
+    /**
+     * The callback result of {@link #checkInstallConstraints(List, InstallConstraints, Consumer)}.
+     */
+    @DataClass(genParcelable = true, genHiddenConstructor = true)
+    public static final class InstallConstraintsResult implements Parcelable {
+        /**
+         * True if all constraints are satisfied.
+         */
+        private boolean mAllConstraintsSatisfied;
+
+
+
+        // Code below generated by codegen v1.0.23.
+        //
+        // DO NOT MODIFY!
+        // CHECKSTYLE:OFF Generated code
+        //
+        // To regenerate run:
+        // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/PackageInstaller.java
+        //
+        // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+        //   Settings > Editor > Code Style > Formatter Control
+        //@formatter:off
+
+
+        /**
+         * Creates a new InstallConstraintsResult.
+         *
+         * @param allConstraintsSatisfied
+         *   True if all constraints are satisfied.
+         * @hide
+         */
+        @DataClass.Generated.Member
+        public InstallConstraintsResult(
+                boolean allConstraintsSatisfied) {
+            this.mAllConstraintsSatisfied = allConstraintsSatisfied;
+
+            // onConstructed(); // You can define this method to get a callback
+        }
+
+        /**
+         * True if all constraints are satisfied.
+         */
+        @DataClass.Generated.Member
+        public boolean isAllConstraintsSatisfied() {
+            return mAllConstraintsSatisfied;
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            // You can override field parcelling by defining methods like:
+            // void parcelFieldName(Parcel dest, int flags) { ... }
+
+            byte flg = 0;
+            if (mAllConstraintsSatisfied) flg |= 0x1;
+            dest.writeByte(flg);
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public int describeContents() { return 0; }
+
+        /** @hide */
+        @SuppressWarnings({"unchecked", "RedundantCast"})
+        @DataClass.Generated.Member
+        /* package-private */ InstallConstraintsResult(@NonNull Parcel in) {
+            // You can override field unparcelling by defining methods like:
+            // static FieldType unparcelFieldName(Parcel in) { ... }
+
+            byte flg = in.readByte();
+            boolean allConstraintsSatisfied = (flg & 0x1) != 0;
+
+            this.mAllConstraintsSatisfied = allConstraintsSatisfied;
+
+            // onConstructed(); // You can define this method to get a callback
+        }
+
+        @DataClass.Generated.Member
+        public static final @NonNull Parcelable.Creator<InstallConstraintsResult> CREATOR
+                = new Parcelable.Creator<InstallConstraintsResult>() {
+            @Override
+            public InstallConstraintsResult[] newArray(int size) {
+                return new InstallConstraintsResult[size];
+            }
+
+            @Override
+            public InstallConstraintsResult createFromParcel(@NonNull Parcel in) {
+                return new InstallConstraintsResult(in);
+            }
+        };
+
+        @DataClass.Generated(
+                time = 1668650523745L,
+                codegenVersion = "1.0.23",
+                sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java",
+                inputSignatures = "private  boolean mAllConstraintsSatisfied\nclass InstallConstraintsResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true)")
+        @Deprecated
+        private void __metadata() {}
+
+
+        //@formatter:on
+        // End of generated code
+
+    }
+
+    /**
+     * A class to encapsulate constraints for installation.
+     *
+     * When used with {@link #checkInstallConstraints(List, InstallConstraints, Consumer)}, it
+     * specifies the conditions to check against for the packages in question. This can be used
+     * by app stores to deliver auto updates without disrupting the user experience (referred as
+     * gentle update) - for example, an app store might hold off updates when it find out the
+     * app to update is interacting with the user.
+     *
+     * Use {@link Builder} to create a new instance and call mutator methods to add constraints.
+     * If no mutators were called, default constraints will be generated which implies no
+     * constraints. It is recommended to use preset constraints which are useful in most
+     * cases.
+     *
+     * For the purpose of gentle update, it is recommended to always use {@link #GENTLE_UPDATE}
+     * for the system knows best how to do it. It will also benefits the installer as the
+     * platform evolves and add more constraints to improve the accuracy and efficiency of
+     * gentle update.
+     *
+     * Note the constraints are applied transitively. If app Foo is used by app Bar (via shared
+     * library or bounded service), the constraints will also be applied to Bar.
+     */
+    @DataClass(genParcelable = true, genHiddenConstructor = true)
+    public static final class InstallConstraints implements Parcelable {
+        /**
+         * Preset constraints suitable for gentle update.
+         */
+        @NonNull
+        public static final InstallConstraints GENTLE_UPDATE =
+                new Builder().requireAppNotInteracting().build();
+
+        private final boolean mRequireDeviceIdle;
+        private final boolean mRequireAppNotForeground;
+        private final boolean mRequireAppNotInteracting;
+        private final boolean mRequireAppNotTopVisible;
+        private final boolean mRequireNotInCall;
+
+        /**
+         * Builder class for constructing {@link InstallConstraints}.
+         */
+        public static final class Builder {
+            private boolean mRequireDeviceIdle;
+            private boolean mRequireAppNotForeground;
+            private boolean mRequireAppNotInteracting;
+            private boolean mRequireAppNotTopVisible;
+            private boolean mRequireNotInCall;
+
+            /**
+             * This constraint requires the device is idle.
+             */
+            @SuppressLint("BuilderSetStyle")
+            @NonNull
+            public Builder requireDeviceIdle() {
+                mRequireDeviceIdle = true;
+                return this;
+            }
+
+            /**
+             * This constraint requires the app in question is not in the foreground.
+             */
+            @SuppressLint("BuilderSetStyle")
+            @NonNull
+            public Builder requireAppNotForeground() {
+                mRequireAppNotForeground = true;
+                return this;
+            }
+
+            /**
+             * This constraint requires the app in question is not interacting with the user.
+             * User interaction includes:
+             * <ul>
+             *     <li>playing or recording audio/video</li>
+             *     <li>sending or receiving network data</li>
+             *     <li>being visible to the user</li>
+             * </ul>
+             */
+            @SuppressLint("BuilderSetStyle")
+            @NonNull
+            public Builder requireAppNotInteracting() {
+                mRequireAppNotInteracting = true;
+                return this;
+            }
+
+            /**
+             * This constraint requires the app in question is not top-visible to the user.
+             * A top-visible app is showing UI at the top of the screen that the user is
+             * interacting with.
+             *
+             * Note this constraint is a subset of {@link #requireAppNotForeground()}
+             * because a top-visible app is also a foreground app. This is also a subset
+             * of {@link #requireAppNotInteracting()} because a top-visible app is interacting
+             * with the user.
+             */
+            @SuppressLint("BuilderSetStyle")
+            @NonNull
+            public Builder requireAppNotTopVisible() {
+                mRequireAppNotTopVisible = true;
+                return this;
+            }
+
+            /**
+             * This constraint requires there is no ongoing call in the device.
+             */
+            @SuppressLint("BuilderSetStyle")
+            @NonNull
+            public Builder requireNotInCall() {
+                mRequireNotInCall = true;
+                return this;
+            }
+
+            /**
+             * Builds a new {@link InstallConstraints} instance.
+             */
+            @NonNull
+            public InstallConstraints build() {
+                return new InstallConstraints(mRequireDeviceIdle, mRequireAppNotForeground,
+                        mRequireAppNotInteracting, mRequireAppNotTopVisible, mRequireNotInCall);
+            }
+        }
+
+
+
+        // Code below generated by codegen v1.0.23.
+        //
+        // DO NOT MODIFY!
+        // CHECKSTYLE:OFF Generated code
+        //
+        // To regenerate run:
+        // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/PackageInstaller.java
+        //
+        // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+        //   Settings > Editor > Code Style > Formatter Control
+        //@formatter:off
+
+
+        /**
+         * Creates a new InstallConstraints.
+         *
+         * @hide
+         */
+        @DataClass.Generated.Member
+        public InstallConstraints(
+                boolean requireDeviceIdle,
+                boolean requireAppNotForeground,
+                boolean requireAppNotInteracting,
+                boolean requireAppNotTopVisible,
+                boolean requireNotInCall) {
+            this.mRequireDeviceIdle = requireDeviceIdle;
+            this.mRequireAppNotForeground = requireAppNotForeground;
+            this.mRequireAppNotInteracting = requireAppNotInteracting;
+            this.mRequireAppNotTopVisible = requireAppNotTopVisible;
+            this.mRequireNotInCall = requireNotInCall;
+
+            // onConstructed(); // You can define this method to get a callback
+        }
+
+        @DataClass.Generated.Member
+        public boolean isRequireDeviceIdle() {
+            return mRequireDeviceIdle;
+        }
+
+        @DataClass.Generated.Member
+        public boolean isRequireAppNotForeground() {
+            return mRequireAppNotForeground;
+        }
+
+        @DataClass.Generated.Member
+        public boolean isRequireAppNotInteracting() {
+            return mRequireAppNotInteracting;
+        }
+
+        @DataClass.Generated.Member
+        public boolean isRequireAppNotTopVisible() {
+            return mRequireAppNotTopVisible;
+        }
+
+        @DataClass.Generated.Member
+        public boolean isRequireNotInCall() {
+            return mRequireNotInCall;
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            // You can override field parcelling by defining methods like:
+            // void parcelFieldName(Parcel dest, int flags) { ... }
+
+            byte flg = 0;
+            if (mRequireDeviceIdle) flg |= 0x1;
+            if (mRequireAppNotForeground) flg |= 0x2;
+            if (mRequireAppNotInteracting) flg |= 0x4;
+            if (mRequireAppNotTopVisible) flg |= 0x8;
+            if (mRequireNotInCall) flg |= 0x10;
+            dest.writeByte(flg);
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public int describeContents() { return 0; }
+
+        /** @hide */
+        @SuppressWarnings({"unchecked", "RedundantCast"})
+        @DataClass.Generated.Member
+        /* package-private */ InstallConstraints(@NonNull Parcel in) {
+            // You can override field unparcelling by defining methods like:
+            // static FieldType unparcelFieldName(Parcel in) { ... }
+
+            byte flg = in.readByte();
+            boolean requireDeviceIdle = (flg & 0x1) != 0;
+            boolean requireAppNotForeground = (flg & 0x2) != 0;
+            boolean requireAppNotInteracting = (flg & 0x4) != 0;
+            boolean requireAppNotTopVisible = (flg & 0x8) != 0;
+            boolean requireNotInCall = (flg & 0x10) != 0;
+
+            this.mRequireDeviceIdle = requireDeviceIdle;
+            this.mRequireAppNotForeground = requireAppNotForeground;
+            this.mRequireAppNotInteracting = requireAppNotInteracting;
+            this.mRequireAppNotTopVisible = requireAppNotTopVisible;
+            this.mRequireNotInCall = requireNotInCall;
+
+            // onConstructed(); // You can define this method to get a callback
+        }
+
+        @DataClass.Generated.Member
+        public static final @NonNull Parcelable.Creator<InstallConstraints> CREATOR
+                = new Parcelable.Creator<InstallConstraints>() {
+            @Override
+            public InstallConstraints[] newArray(int size) {
+                return new InstallConstraints[size];
+            }
+
+            @Override
+            public InstallConstraints createFromParcel(@NonNull Parcel in) {
+                return new InstallConstraints(in);
+            }
+        };
+
+        @DataClass.Generated(
+                time = 1668650523752L,
+                codegenVersion = "1.0.23",
+                sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java",
+                inputSignatures = "public static final @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE\nprivate final  boolean mRequireDeviceIdle\nprivate final  boolean mRequireAppNotForeground\nprivate final  boolean mRequireAppNotInteracting\nprivate final  boolean mRequireAppNotTopVisible\nprivate final  boolean mRequireNotInCall\nclass InstallConstraints extends java.lang.Object implements [android.os.Parcelable]\nprivate  boolean mRequireDeviceIdle\nprivate  boolean mRequireAppNotForeground\nprivate  boolean mRequireAppNotInteracting\nprivate  boolean mRequireAppNotTopVisible\nprivate  boolean mRequireNotInCall\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireDeviceIdle()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotForeground()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotInteracting()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotTopVisible()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireNotInCall()\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints build()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true)")
+        @Deprecated
+        private void __metadata() {}
+
+
+        //@formatter:on
+        // End of generated code
+
+    }
+
 }
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 9e6cf62..7ea6733 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -366,24 +366,48 @@
     public static final int FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED = 1 << 10;
 
     /**
-     * Foreground service type corresponding to {@code shortService} in
-     * the {@link android.R.attr#foregroundServiceType} attribute.
+     * A foreground service type for "short-lived" services, which corresponds to
+     * {@code shortService} in the {@link android.R.attr#foregroundServiceType} attribute in the
+     * manifest.
      *
-     * TODO Implement it
+     * <p>Unlike other foreground service types, this type is not associated with a specific use
+     * case, and it will not require any special permissions
+     * (besides {@link Manifest.permission#FOREGROUND_SERVICE}).
      *
-     * TODO Expand the javadoc
+     * However, this type has the following restrictions.
      *
-     * This type is not associated with specific use cases unlike other types, but this has
-     * unique restrictions.
      * <ul>
-     *     <li>Has a timeout
-     *     <li>Cannot start other foreground services from this
      *     <li>
+     *         The type has a 1 minute timeout.
+     *         A foreground service of this type must be stopped within the timeout by
+     *         {@link android.app.Service#stopSelf),
+     *         or {@link android.content.Context#stopService).
+     *         {@link android.app.Service#stopForeground) will also work, which will demote the
+     *         service to a "background" service, which will soon be stopped by the system.
+     *
+     *         <p>The system will <em>not</em> automatically stop it.
+     *
+     *         <p>If the service isn't stopped within the timeout,
+     *         {@link android.app.Service#onTimeout(int)} will be called.
+     *         If the service is still not stopped after the callback,
+     *         the app will be declared an ANR.
+     *
+     *     <li>
+     *         A foreground service of this type cannot be made "sticky"
+     *         (see {@link android.app.Service#START_STICKY}). That is, if an app is killed
+     *         due to a crash or out-of memory while it's running a short foregorund-service,
+     *         the system will not restart the service.
+     *     <li>
+     *         Other foreground services cannot be started from short foreground services.
+     *         Unlike other foreground service types, when an app is running in the background
+     *         while only having a "short" foreground service, it's not allowed to start
+     *         other foreground services, due to the restriction describe here:
+     *         <a href="/guide/components/foreground-services#background-start-restrictions>
+     *             Restrictions on background starts
+     *         </a>
      * </ul>
      *
-     * @see Service#onTimeout
-     *
-     * @hide
+     * @see android.app.Service#onTimeout(int)
      */
     public static final int FOREGROUND_SERVICE_TYPE_SHORT_SERVICE = 1 << 11;
 
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index 7a5ac8e..143c00d 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -26,6 +26,8 @@
 
 import com.android.internal.annotations.GuardedBy;
 
+import dalvik.annotation.optimization.CriticalNative;
+
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -459,7 +461,7 @@
     private static native @NonNull String nativeGetAssetPath(long ptr);
     private static native @NonNull String nativeGetDebugName(long ptr);
     private static native long nativeGetStringBlock(long ptr);
-    private static native boolean nativeIsUpToDate(long ptr);
+    @CriticalNative private static native boolean nativeIsUpToDate(long ptr);
     private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException;
     private static native @Nullable OverlayableInfo nativeGetOverlayableInfo(long ptr,
             String overlayableName) throws IOException;
diff --git a/core/java/android/content/res/loader/ResourcesProvider.java b/core/java/android/content/res/loader/ResourcesProvider.java
index 463dcac..a5a1fa689 100644
--- a/core/java/android/content/res/loader/ResourcesProvider.java
+++ b/core/java/android/content/res/loader/ResourcesProvider.java
@@ -18,7 +18,10 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.content.Context;
+import android.content.om.OverlayInfo;
+import android.content.om.OverlayManager;
 import android.content.pm.ApplicationInfo;
 import android.content.res.ApkAssets;
 import android.content.res.AssetFileDescriptor;
@@ -27,11 +30,17 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.content.om.OverlayManagerImpl;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
 
 import java.io.Closeable;
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Objects;
 
 /**
  * Provides methods to load resources data from APKs ({@code .apk}) and resources tables
@@ -63,6 +72,48 @@
     }
 
     /**
+     * Creates a ResourcesProvider instance from the specified overlay information.
+     *
+     * <p>In order to enable the registered overlays, an application can create a {@link
+     * ResourcesProvider} instance according to the specified {@link OverlayInfo} instance and put
+     * them into a {@link ResourcesLoader} instance. The application calls {@link
+     * android.content.res.Resources#addLoaders(ResourcesLoader...)} to load the overlays.
+     *
+     * @param overlayInfo is the information about the specified overlay
+     * @return the resources provider instance for the {@code overlayInfo}
+     * @throws IOException when the files can't be loaded.
+     * @see OverlayManager#getOverlayInfosForTarget(String) to get the list of overlay info.
+     * @hide
+     */
+    @SuppressLint("WrongConstant") // TODO(b/238713267): ApkAssets blocks PROPERTY_LOADER
+    @NonNull
+    public static ResourcesProvider loadOverlay(@NonNull OverlayInfo overlayInfo)
+            throws IOException {
+        Objects.requireNonNull(overlayInfo);
+        Preconditions.checkArgument(overlayInfo.isFabricated(), "Not accepted overlay");
+        Preconditions.checkStringNotEmpty(
+                overlayInfo.getTargetOverlayableName(), "Without overlayable name");
+        final String overlayName =
+                OverlayManagerImpl.checkOverlayNameValid(overlayInfo.getOverlayName());
+        final String path =
+                Preconditions.checkStringNotEmpty(
+                        overlayInfo.getBaseCodePath(), "Invalid base path");
+
+        final Path frroPath = Path.of(path);
+        if (!Files.isRegularFile(frroPath)) {
+            throw new FileNotFoundException("The frro file not found");
+        }
+        final Path idmapPath = frroPath.getParent().resolve(overlayName + ".idmap");
+        if (!Files.isRegularFile(idmapPath)) {
+            throw new FileNotFoundException("The idmap file not found");
+        }
+
+        return new ResourcesProvider(
+                ApkAssets.loadOverlayFromPath(
+                        idmapPath.toString(), 0 /* flags: self targeting overlay */));
+    }
+
+    /**
      * Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor.
      *
      * <p>The file descriptor is duplicated and the original may be closed by the application at any
diff --git a/core/java/android/credentials/CreateCredentialRequest.java b/core/java/android/credentials/CreateCredentialRequest.java
index 22ef230..4589039 100644
--- a/core/java/android/credentials/CreateCredentialRequest.java
+++ b/core/java/android/credentials/CreateCredentialRequest.java
@@ -39,10 +39,17 @@
     private final String mType;
 
     /**
-     * The request data.
+     * The full credential creation request data.
      */
     @NonNull
-    private final Bundle mData;
+    private final Bundle mCredentialData;
+
+    /**
+     * The partial request data that will be sent to the provider during the initial creation
+     * candidate query stage.
+     */
+    @NonNull
+    private final Bundle mCandidateQueryData;
 
     /**
      * Determines whether or not the request must only be fulfilled by a system provider.
@@ -58,18 +65,39 @@
     }
 
     /**
-     * Returns the request data.
+     * Returns the full credential creation request data.
+     *
+     * For security reason, a provider will receive the request data in two stages. First it gets
+     * a partial request, {@link #getCandidateQueryData()} that do not contain sensitive user
+     * information; it uses this information to provide credential creation candidates that the
+     * [@code CredentialManager] will show to the user. Next, this full request data will be sent to
+     * a provider only if the user further grants the consent by choosing a candidate from the
+     * provider.
      */
     @NonNull
-    public Bundle getData() {
-        return mData;
+    public Bundle getCredentialData() {
+        return mCredentialData;
+    }
+
+    /**
+     * Returns the partial request data that will be sent to the provider during the initial
+     * creation candidate query stage.
+     *
+     * For security reason, a provider will receive the request data in two stages. First it gets
+     * this partial request that do not contain sensitive user information; it uses this information
+     * to provide credential creation candidates that the [@code CredentialManager] will show to
+     * the user. Next, the full request data, {@link #getCredentialData()}, will be sent to a
+     * provider only if the user further grants the consent by choosing a candidate from the
+     * provider.
+     */
+    @NonNull
+    public Bundle getCandidateQueryData() {
+        return mCandidateQueryData;
     }
 
     /**
      * Returns true if the request must only be fulfilled by a system provider, and false
      * otherwise.
-     *
-     * @hide
      */
     public boolean requireSystemProvider() {
         return mRequireSystemProvider;
@@ -78,7 +106,8 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString8(mType);
-        dest.writeBundle(mData);
+        dest.writeBundle(mCredentialData);
+        dest.writeBundle(mCandidateQueryData);
         dest.writeBoolean(mRequireSystemProvider);
     }
 
@@ -91,7 +120,8 @@
     public String toString() {
         return "CreateCredentialRequest {"
                 + "type=" + mType
-                + ", data=" + mData
+                + ", credentialData=" + mCredentialData
+                + ", candidateQueryData=" + mCandidateQueryData
                 + ", requireSystemProvider=" + mRequireSystemProvider
                 + "}";
     }
@@ -100,44 +130,37 @@
      * Constructs a {@link CreateCredentialRequest}.
      *
      * @param type the requested credential type
-     * @param data the request data
-     *
-     * @throws IllegalArgumentException If type is empty
-     */
-    public CreateCredentialRequest(@NonNull String type, @NonNull Bundle data) {
-        this(type, data, /*requireSystemProvider=*/ false);
-    }
-
-    /**
-     * Constructs a {@link CreateCredentialRequest}.
-     *
-     * @param type the requested credential type
-     * @param data the request data
-     * @param requireSystemProvider whether or not the request must only be fulfilled by a system
-     *                              provider
+     * @param credentialData the full credential creation request data
+     * @param candidateQueryData the partial request data that will be sent to the provider
+     *                           during the initial creation candidate query stage
+     * @param requireSystemProvider whether the request must only be fulfilled by a system provider
      *
      * @throws IllegalArgumentException If type is empty.
-     *
-     * @hide
      */
     public CreateCredentialRequest(
             @NonNull String type,
-            @NonNull Bundle data,
+            @NonNull Bundle credentialData,
+            @NonNull Bundle candidateQueryData,
             boolean requireSystemProvider) {
         mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
-        mData = requireNonNull(data, "data must not be null");
+        mCredentialData = requireNonNull(credentialData, "credentialData must not be null");
+        mCandidateQueryData = requireNonNull(candidateQueryData,
+                "candidateQueryData must not be null");
         mRequireSystemProvider = requireSystemProvider;
     }
 
     private CreateCredentialRequest(@NonNull Parcel in) {
         String type = in.readString8();
-        Bundle data = in.readBundle();
+        Bundle credentialData = in.readBundle();
+        Bundle candidateQueryData = in.readBundle();
         boolean requireSystemProvider = in.readBoolean();
 
         mType = type;
         AnnotationValidations.validate(NonNull.class, null, mType);
-        mData = data;
-        AnnotationValidations.validate(NonNull.class, null, mData);
+        mCredentialData = credentialData;
+        AnnotationValidations.validate(NonNull.class, null, mCredentialData);
+        mCandidateQueryData = candidateQueryData;
+        AnnotationValidations.validate(NonNull.class, null, mCandidateQueryData);
         mRequireSystemProvider = requireSystemProvider;
     }
 
diff --git a/core/java/android/credentials/GetCredentialOption.java b/core/java/android/credentials/GetCredentialOption.java
index a0d3c0b..ed93dae 100644
--- a/core/java/android/credentials/GetCredentialOption.java
+++ b/core/java/android/credentials/GetCredentialOption.java
@@ -67,8 +67,6 @@
     /**
      * Returns true if the request must only be fulfilled by a system provider, and false
      * otherwise.
-     *
-     * @hide
      */
     public boolean requireSystemProvider() {
         return mRequireSystemProvider;
@@ -100,24 +98,10 @@
      *
      * @param type the requested credential type
      * @param data the request data
-     *
-     * @throws IllegalArgumentException If type is empty
-     */
-    public GetCredentialOption(@NonNull String type, @NonNull Bundle data) {
-        this(type, data, /*requireSystemProvider=*/ false);
-    }
-
-    /**
-     * Constructs a {@link GetCredentialOption}.
-     *
-     * @param type the requested credential type
-     * @param data the request data
      * @param requireSystemProvider whether or not the request must only be fulfilled by a system
      *                              provider
      *
      * @throws IllegalArgumentException If type is empty.
-     *
-     * @hide
      */
     public GetCredentialOption(
             @NonNull String type,
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 1c4898a..18118f5 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -16,8 +16,14 @@
 
 package android.hardware;
 
+import static android.companion.virtual.VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED;
+import static android.companion.virtual.VirtualDeviceManager.DEFAULT_DEVICE_ID;
+import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
+import android.companion.virtual.VirtualDeviceManager;
 import android.compat.Compatibility;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
@@ -45,6 +51,7 @@
 import java.io.UncheckedIOException;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -80,6 +87,8 @@
     private static native boolean nativeGetSensorAtIndex(long nativeInstance,
             Sensor sensor, int index);
     private static native void nativeGetDynamicSensors(long nativeInstance, List<Sensor> list);
+    private static native void nativeGetRuntimeSensors(
+            long nativeInstance, int deviceId, List<Sensor> list);
     private static native boolean nativeIsDataInjectionEnabled(long nativeInstance);
 
     private static native int nativeCreateDirectChannel(
@@ -100,6 +109,10 @@
 
     private final ArrayList<Sensor> mFullSensorsList = new ArrayList<>();
     private List<Sensor> mFullDynamicSensorsList = new ArrayList<>();
+    private final SparseArray<List<Sensor>> mFullRuntimeSensorListByDevice = new SparseArray<>();
+    private final SparseArray<SparseArray<List<Sensor>>> mRuntimeSensorListByDeviceByType =
+            new SparseArray<>();
+
     private boolean mDynamicSensorListDirty = true;
 
     private final HashMap<Integer, Sensor> mHandleToSensor = new HashMap<>();
@@ -114,6 +127,7 @@
     private HashMap<DynamicSensorCallback, Handler>
             mDynamicSensorCallbacks = new HashMap<>();
     private BroadcastReceiver mDynamicSensorBroadcastReceiver;
+    private BroadcastReceiver mRuntimeSensorBroadcastReceiver;
 
     // Looper associated with the context in which this instance was created.
     private final Looper mMainLooper;
@@ -121,6 +135,7 @@
     private final boolean mIsPackageDebuggable;
     private final Context mContext;
     private final long mNativeInstance;
+    private final VirtualDeviceManager mVdm;
 
     private Optional<Boolean> mHasHighSamplingRateSensorsPermission = Optional.empty();
 
@@ -139,6 +154,7 @@
         mContext = context;
         mNativeInstance = nativeCreate(context.getOpPackageName());
         mIsPackageDebuggable = (0 != (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE));
+        mVdm = mContext.getSystemService(VirtualDeviceManager.class);
 
         // initialize the sensor list
         for (int index = 0;; ++index) {
@@ -147,12 +163,63 @@
             mFullSensorsList.add(sensor);
             mHandleToSensor.put(sensor.getHandle(), sensor);
         }
+
+    }
+
+    /** @hide */
+    @Override
+    public List<Sensor> getSensorList(int type) {
+        final int deviceId = mContext.getDeviceId();
+        if (deviceId == DEFAULT_DEVICE_ID || mVdm == null
+                || mVdm.getDevicePolicy(deviceId, POLICY_TYPE_SENSORS) == DEVICE_POLICY_DEFAULT) {
+            return super.getSensorList(type);
+        }
+
+        // Cache the per-device lists on demand.
+        List<Sensor> list;
+        synchronized (mFullRuntimeSensorListByDevice) {
+            List<Sensor> fullList = mFullRuntimeSensorListByDevice.get(deviceId);
+            if (fullList == null) {
+                fullList = createRuntimeSensorListLocked(deviceId);
+            }
+            SparseArray<List<Sensor>> deviceSensorListByType =
+                    mRuntimeSensorListByDeviceByType.get(deviceId);
+            list = deviceSensorListByType.get(type);
+            if (list == null) {
+                if (type == Sensor.TYPE_ALL) {
+                    list = fullList;
+                } else {
+                    list = new ArrayList<>();
+                    for (Sensor i : fullList) {
+                        if (i.getType() == type) {
+                            list.add(i);
+                        }
+                    }
+                }
+                list = Collections.unmodifiableList(list);
+                deviceSensorListByType.append(type, list);
+            }
+        }
+        return list;
     }
 
     /** @hide */
     @Override
     protected List<Sensor> getFullSensorList() {
-        return mFullSensorsList;
+        final int deviceId = mContext.getDeviceId();
+        if (deviceId == DEFAULT_DEVICE_ID || mVdm == null
+                || mVdm.getDevicePolicy(deviceId, POLICY_TYPE_SENSORS) == DEVICE_POLICY_DEFAULT) {
+            return mFullSensorsList;
+        }
+
+        List<Sensor> fullList;
+        synchronized (mFullRuntimeSensorListByDevice) {
+            fullList = mFullRuntimeSensorListByDevice.get(deviceId);
+            if (fullList == null) {
+                fullList = createRuntimeSensorListLocked(deviceId);
+            }
+        }
+        return fullList;
     }
 
     /** @hide */
@@ -446,12 +513,53 @@
         }
     }
 
+    private List<Sensor> createRuntimeSensorListLocked(int deviceId) {
+        setupRuntimeSensorBroadcastReceiver();
+        List<Sensor> list = new ArrayList<>();
+        nativeGetRuntimeSensors(mNativeInstance, deviceId, list);
+        mFullRuntimeSensorListByDevice.put(deviceId, list);
+        mRuntimeSensorListByDeviceByType.put(deviceId, new SparseArray<>());
+        for (Sensor s : list) {
+            mHandleToSensor.put(s.getHandle(), s);
+        }
+        return list;
+    }
+
+    private void setupRuntimeSensorBroadcastReceiver() {
+        if (mRuntimeSensorBroadcastReceiver == null) {
+            mRuntimeSensorBroadcastReceiver = new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    if (intent.getAction().equals(ACTION_VIRTUAL_DEVICE_REMOVED)) {
+                        synchronized (mFullRuntimeSensorListByDevice) {
+                            final int deviceId = intent.getIntExtra(
+                                    EXTRA_VIRTUAL_DEVICE_ID, DEFAULT_DEVICE_ID);
+                            List<Sensor> removedSensors =
+                                    mFullRuntimeSensorListByDevice.removeReturnOld(deviceId);
+                            if (removedSensors != null) {
+                                for (Sensor s : removedSensors) {
+                                    cleanupSensorConnection(s);
+                                }
+                            }
+                            mRuntimeSensorListByDeviceByType.remove(deviceId);
+                        }
+                    }
+                }
+            };
+
+            IntentFilter filter = new IntentFilter("virtual_device_removed");
+            filter.addAction(ACTION_VIRTUAL_DEVICE_REMOVED);
+            mContext.registerReceiver(mRuntimeSensorBroadcastReceiver, filter,
+                    Context.RECEIVER_NOT_EXPORTED);
+        }
+    }
+
     private void setupDynamicSensorBroadcastReceiver() {
         if (mDynamicSensorBroadcastReceiver == null) {
             mDynamicSensorBroadcastReceiver = new BroadcastReceiver() {
                 @Override
                 public void onReceive(Context context, Intent intent) {
-                    if (intent.getAction() == Intent.ACTION_DYNAMIC_SENSOR_CHANGED) {
+                    if (intent.getAction().equals(Intent.ACTION_DYNAMIC_SENSOR_CHANGED)) {
                         if (DEBUG_DYNAMIC_SENSOR) {
                             Log.i(TAG, "DYNS received DYNAMIC_SENSOR_CHANED broadcast");
                         }
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 9b07d3a..441fd88 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -137,7 +137,8 @@
             VIRTUAL_DISPLAY_FLAG_TRUSTED,
             VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP,
             VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED,
-            VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED
+            VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED,
+            VIRTUAL_DISPLAY_FLAG_OWN_FOCUS,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface VirtualDisplayFlag {}
@@ -403,6 +404,22 @@
      */
     public static final int VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED = 1 << 13;
 
+    /**
+     * Virtual display flags: Indicates that the display maintains its own focus and touch mode.
+     *
+     * This flag is similar to {@link com.android.internal.R.bool.config_perDisplayFocusEnabled} in
+     * behavior, but only applies to the specific display instead of system-wide to all displays.
+     *
+     * Note: The display must be trusted in order to have its own focus.
+     *
+     * @see #createVirtualDisplay
+     * @see #VIRTUAL_DISPLAY_FLAG_TRUSTED
+     * @hide
+     */
+    @TestApi
+    public static final int VIRTUAL_DISPLAY_FLAG_OWN_FOCUS = 1 << 14;
+
+
     /** @hide */
     @IntDef(prefix = {"MATCH_CONTENT_FRAMERATE_"}, value = {
             MATCH_CONTENT_FRAMERATE_UNKNOWN,
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
index ade9fd6..b2dfd85 100644
--- a/core/java/android/hardware/radio/ProgramList.java
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -24,6 +24,8 @@
 import android.os.Parcelable;
 import android.util.ArrayMap;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
@@ -41,14 +43,25 @@
 public final class ProgramList implements AutoCloseable {
 
     private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
     private final Map<ProgramSelector.Identifier, RadioManager.ProgramInfo> mPrograms =
             new ArrayMap<>();
 
+    @GuardedBy("mLock")
     private final List<ListCallback> mListCallbacks = new ArrayList<>();
+
+    @GuardedBy("mLock")
     private final List<OnCompleteListener> mOnCompleteListeners = new ArrayList<>();
+
+    @GuardedBy("mLock")
     private OnCloseListener mOnCloseListener;
-    private boolean mIsClosed = false;
-    private boolean mIsComplete = false;
+
+    @GuardedBy("mLock")
+    private boolean mIsClosed;
+
+    @GuardedBy("mLock")
+    private boolean mIsComplete;
 
     ProgramList() {}
 
@@ -227,6 +240,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private void putLocked(RadioManager.ProgramInfo value,
             List<ProgramSelector.Identifier> changedIdentifierList) {
         ProgramSelector.Identifier key = value.getSelector().getPrimaryId();
@@ -235,6 +249,7 @@
         changedIdentifierList.add(sel);
     }
 
+    @GuardedBy("mLock")
     private void removeLocked(ProgramSelector.Identifier key,
             List<ProgramSelector.Identifier> removedIdentifierList) {
         RadioManager.ProgramInfo removed = mPrograms.remove(Objects.requireNonNull(key));
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 51236fe3..248b5d0 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -122,10 +122,10 @@
     boolean isFunctionEnabled(String function);
 
     /* Sets the current USB function. */
-    void setCurrentFunctions(long functions);
+    void setCurrentFunctions(long functions, int operationId);
 
     /* Compatibility version of setCurrentFunctions(long). */
-    void setCurrentFunction(String function, boolean usbDataUnlocked);
+    void setCurrentFunction(String function, boolean usbDataUnlocked, int operationId);
 
     /* Gets the current USB functions. */
     long getCurrentFunctions();
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 342c336..7a8117c 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -38,6 +38,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.hardware.usb.gadget.V1_0.GadgetFunction;
 import android.hardware.usb.gadget.V1_2.UsbSpeed;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
@@ -52,6 +53,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.StringJoiner;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * This class allows you to access the state of USB and communicate with USB devices.
@@ -95,7 +97,7 @@
      * If the sticky intent has not been found, that indicates USB is disconnected,
      * USB is not configued, MTP function is enabled, and all the other functions are disabled.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final String ACTION_USB_STATE =
@@ -185,7 +187,7 @@
      * <p>For more information about communicating with USB accessory handshake, refer to
      * <a href="https://source.android.com/devices/accessories/aoa">AOA</a> developer guide.</p>
      *
-     * {@hide}
+     * @hide
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     @SystemApi
@@ -197,7 +199,7 @@
      * Boolean extra indicating whether USB is connected or disconnected.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final String USB_CONNECTED = "connected";
@@ -206,7 +208,7 @@
      * Boolean extra indicating whether USB is connected or disconnected as host.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast.
      *
-     * {@hide}
+     * @hide
      */
     public static final String USB_HOST_CONNECTED = "host_connected";
 
@@ -214,7 +216,7 @@
      * Boolean extra indicating whether USB is configured.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final String USB_CONFIGURED = "configured";
@@ -225,7 +227,7 @@
      * has explicitly asked for this data to be unlocked.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast.
      *
-     * {@hide}
+     * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public static final String USB_DATA_UNLOCKED = "unlocked";
@@ -234,7 +236,7 @@
      * A placeholder indicating that no USB function is being specified.
      * Used for compatibility with old init scripts to indicate no functions vs. charging function.
      *
-     * {@hide}
+     * @hide
      */
     @UnsupportedAppUsage
     public static final String USB_FUNCTION_NONE = "none";
@@ -243,7 +245,7 @@
      * Name of the adb USB function.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
      *
-     * {@hide}
+     * @hide
      */
     public static final String USB_FUNCTION_ADB = "adb";
 
@@ -251,7 +253,7 @@
      * Name of the RNDIS ethernet USB function.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final String USB_FUNCTION_RNDIS = "rndis";
@@ -260,7 +262,7 @@
      * Name of the MTP USB function.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
      *
-     * {@hide}
+     * @hide
      */
     public static final String USB_FUNCTION_MTP = "mtp";
 
@@ -268,7 +270,7 @@
      * Name of the PTP USB function.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
      *
-     * {@hide}
+     * @hide
      */
     public static final String USB_FUNCTION_PTP = "ptp";
 
@@ -276,7 +278,7 @@
      * Name of the audio source USB function.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
      *
-     * {@hide}
+     * @hide
      */
     public static final String USB_FUNCTION_AUDIO_SOURCE = "audio_source";
 
@@ -284,7 +286,7 @@
      * Name of the MIDI USB function.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
      *
-     * {@hide}
+     * @hide
      */
     public static final String USB_FUNCTION_MIDI = "midi";
 
@@ -292,7 +294,7 @@
      * Name of the Accessory USB function.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
      *
-     * {@hide}
+     * @hide
      */
     public static final String USB_FUNCTION_ACCESSORY = "accessory";
 
@@ -300,7 +302,7 @@
      * Name of the NCM USB function.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final String USB_FUNCTION_NCM = "ncm";
@@ -308,32 +310,39 @@
     /**
      * Name of Gadget Hal Not Present;
      *
-     * {@hide}
+     * @hide
      */
     public static final String GADGET_HAL_UNKNOWN = "unknown";
 
     /**
      * Name of the USB Gadget Hal Version v1.0;
      *
-     * {@hide}
+     * @hide
      */
     public static final String GADGET_HAL_VERSION_1_0 = "V1_0";
 
     /**
      * Name of the USB Gadget Hal Version v1.1;
      *
-     * {@hide}
+     * @hide
      */
     public static final String GADGET_HAL_VERSION_1_1 = "V1_1";
 
     /**
      * Name of the USB Gadget Hal Version v1.2;
      *
-     * {@hide}
+     * @hide
      */
     public static final String GADGET_HAL_VERSION_1_2 = "V1_2";
 
     /**
+     * Name of the USB Gadget Hal Version v2.0;
+     *
+     * @hide
+     */
+    public static final String GADGET_HAL_VERSION_2_0 = "V2_0";
+
+    /**
      * Name of extra for {@link #ACTION_USB_PORT_CHANGED}
      * containing the {@link UsbPort} object for the port.
      *
@@ -369,7 +378,7 @@
      * This is obtained with SystemClock.elapsedRealtime()
      * Used in extras for {@link #ACTION_USB_ACCESSORY_HANDSHAKE} broadcasts.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final String EXTRA_ACCESSORY_UEVENT_TIME =
@@ -383,7 +392,7 @@
      * between communicating with USB accessory handshake, refer to
      * <a href="https://source.android.com/devices/accessories/aoa">AOA</a> developer guide.</p>
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final String EXTRA_ACCESSORY_STRING_COUNT =
@@ -393,7 +402,7 @@
      * Boolean extra indicating whether got start accessory or not
      * Used in extras for {@link #ACTION_USB_ACCESSORY_HANDSHAKE} broadcasts.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final String EXTRA_ACCESSORY_START =
@@ -405,7 +414,7 @@
      * sending {@link #ACTION_USB_ACCESSORY_HANDSHAKE}.
      * Used in extras for {@link #ACTION_USB_ACCESSORY_HANDSHAKE} broadcasts.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final String EXTRA_ACCESSORY_HANDSHAKE_END =
@@ -439,7 +448,7 @@
     /**
      * The Value for USB gadget hal is not presented.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int GADGET_HAL_NOT_SUPPORTED = -1;
@@ -447,7 +456,7 @@
     /**
      * Value for Gadget Hal Version v1.0.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int GADGET_HAL_V1_0 = 10;
@@ -455,7 +464,7 @@
     /**
      * Value for Gadget Hal Version v1.1.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int GADGET_HAL_V1_1 = 11;
@@ -463,15 +472,23 @@
     /**
      * Value for Gadget Hal Version v1.2.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int GADGET_HAL_V1_2 = 12;
 
     /**
+     * Value for Gadget Hal Version v2.0.
+     *
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final int GADGET_HAL_V2_0 = 20;
+
+    /**
      * Value for USB_STATE is not configured.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_DATA_TRANSFER_RATE_UNKNOWN = -1;
@@ -479,7 +496,7 @@
     /**
      * Value for USB Transfer Rate of Low Speed in Mbps (real value is 1.5Mbps).
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_DATA_TRANSFER_RATE_LOW_SPEED = 2;
@@ -487,7 +504,7 @@
     /**
      * Value for USB Transfer Rate of Full Speed in Mbps.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_DATA_TRANSFER_RATE_FULL_SPEED = 12;
@@ -495,7 +512,7 @@
     /**
      * Value for USB Transfer Rate of High Speed in Mbps.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_DATA_TRANSFER_RATE_HIGH_SPEED = 480;
@@ -503,7 +520,7 @@
     /**
      * Value for USB Transfer Rate of Super Speed in Mbps.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_DATA_TRANSFER_RATE_5G = 5 * 1024;
@@ -511,7 +528,7 @@
     /**
      * Value for USB Transfer Rate of 10G.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_DATA_TRANSFER_RATE_10G = 10 * 1024;
@@ -519,7 +536,7 @@
     /**
      * Value for USB Transfer Rate of 20G.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_DATA_TRANSFER_RATE_20G = 20 * 1024;
@@ -527,7 +544,7 @@
     /**
      * Value for USB Transfer Rate of 40G.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_DATA_TRANSFER_RATE_40G = 40 * 1024;
@@ -543,7 +560,7 @@
     /**
      * The Value for USB hal is not presented.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_HAL_NOT_SUPPORTED = -1;
@@ -551,7 +568,7 @@
     /**
      * Value for USB Hal Version v1.0.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_HAL_V1_0 = 10;
@@ -559,7 +576,7 @@
     /**
      * Value for USB Hal Version v1.1.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_HAL_V1_1 = 11;
@@ -567,7 +584,7 @@
     /**
      * Value for USB Hal Version v1.2.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_HAL_V1_2 = 12;
@@ -575,7 +592,7 @@
     /**
      * Value for USB Hal Version v1.3.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_HAL_V1_3 = 13;
@@ -590,63 +607,63 @@
 
     /**
      * Code for the charging usb function. Passed into {@link #setCurrentFunctions(long)}
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final long FUNCTION_NONE = 0;
 
     /**
      * Code for the mtp usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final long FUNCTION_MTP = GadgetFunction.MTP;
 
     /**
      * Code for the ptp usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final long FUNCTION_PTP = GadgetFunction.PTP;
 
     /**
      * Code for the rndis usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final long FUNCTION_RNDIS = GadgetFunction.RNDIS;
 
     /**
      * Code for the midi usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final long FUNCTION_MIDI = GadgetFunction.MIDI;
 
     /**
      * Code for the accessory usb function.
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final long FUNCTION_ACCESSORY = GadgetFunction.ACCESSORY;
 
     /**
      * Code for the audio source usb function.
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final long FUNCTION_AUDIO_SOURCE = GadgetFunction.AUDIO_SOURCE;
 
     /**
      * Code for the adb usb function.
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final long FUNCTION_ADB = GadgetFunction.ADB;
 
     /**
      * Code for the ncm source usb function.
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final long FUNCTION_NCM = 1 << 10;
@@ -656,6 +673,11 @@
 
     private static final Map<String, Long> FUNCTION_NAME_TO_CODE = new HashMap<>();
 
+    /**
+     * Counter for tracking UsbOperation operations.
+     */
+    private static final AtomicInteger sUsbOperationCount = new AtomicInteger();
+
     static {
         FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_MTP, FUNCTION_MTP);
         FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_PTP, FUNCTION_PTP);
@@ -687,6 +709,7 @@
             GADGET_HAL_V1_0,
             GADGET_HAL_V1_1,
             GADGET_HAL_V1_2,
+            GADGET_HAL_V2_0,
     })
     public @interface UsbGadgetHalVersion {}
 
@@ -705,7 +728,7 @@
     private final IUsbManager mService;
 
     /**
-     * {@hide}
+     * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public UsbManager(Context context, IUsbManager service) {
@@ -816,7 +839,7 @@
      * {@link #FUNCTION_PTP} are supported.
      * @return A ParcelFileDescriptor holding the valid fd, or null if the fd was not found.
      *
-     * {@hide}
+     * @hide
      */
     public ParcelFileDescriptor getControlFd(long function) {
         try {
@@ -977,7 +1000,7 @@
      * Only system components can call this function.
      * @param device to request permissions for
      *
-     * {@hide}
+     * @hide
      */
     public void grantPermission(UsbDevice device) {
         grantPermission(device, Process.myUid());
@@ -989,7 +1012,7 @@
      * @param device to request permissions for
      * @uid uid to give permission
      *
-     * {@hide}
+     * @hide
      */
     public void grantPermission(UsbDevice device, int uid) {
         try {
@@ -1005,7 +1028,7 @@
      * @param device to request permissions for
      * @param packageName of package to grant permissions
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.MANAGE_USB)
@@ -1030,7 +1053,7 @@
      * @param function name of the USB function
      * @return true if the USB function is enabled
      *
-     * {@hide}
+     * @hide
      */
     @Deprecated
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -1062,14 +1085,17 @@
      * @param functions the USB function(s) to set, as a bitwise mask.
      *                  Must satisfy {@link UsbManager#areSettableFunctions}
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.MANAGE_USB)
     public void setCurrentFunctions(@UsbFunctionMode long functions) {
+        int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
         try {
-            mService.setCurrentFunctions(functions);
+            mService.setCurrentFunctions(functions, operationId);
         } catch (RemoteException e) {
+            Log.e(TAG, "setCurrentFunctions: failed to call setCurrentFunctions. functions:"
+                        + functions + ", opId:" + operationId, e);
             throw e.rethrowFromSystemServer();
         }
     }
@@ -1081,14 +1107,17 @@
      * @param functions the USB function(s) to set.
      * @param usbDataUnlocked unused
 
-     * {@hide}
+     * @hide
      */
     @Deprecated
     @UnsupportedAppUsage
     public void setCurrentFunction(String functions, boolean usbDataUnlocked) {
+        int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
         try {
-            mService.setCurrentFunction(functions, usbDataUnlocked);
+            mService.setCurrentFunction(functions, usbDataUnlocked, operationId);
         } catch (RemoteException e) {
+            Log.e(TAG, "setCurrentFunction: failed to call setCurrentFunction. functions:"
+                        + functions + ", opId:" + operationId, e);
             throw e.rethrowFromSystemServer();
         }
     }
@@ -1103,7 +1132,7 @@
      * @return The currently enabled functions, in a bitwise mask.
      * A zero mask indicates that the current function is the charging function.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.MANAGE_USB)
@@ -1129,7 +1158,7 @@
      * @param functions functions to set, in a bitwise mask.
      *                  Must satisfy {@link UsbManager#areSettableFunctions}
      *
-     * {@hide}
+     * @hide
      */
     public void setScreenUnlockedFunctions(long functions) {
         try {
@@ -1145,7 +1174,7 @@
      * @return The currently set screen enabled functions.
      * A zero mask indicates that the screen unlocked functions feature is not enabled.
      *
-     * {@hide}
+     * @hide
      */
     public long getScreenUnlockedFunctions() {
         try {
@@ -1167,19 +1196,17 @@
      *
      * @return The value of currently USB Bandwidth.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     @RequiresPermission(Manifest.permission.MANAGE_USB)
     public int getUsbBandwidthMbps() {
         int usbSpeed;
-
         try {
             usbSpeed = mService.getCurrentUsbSpeed();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-
         return usbSpeedToBandwidth(usbSpeed);
     }
 
@@ -1191,7 +1218,7 @@
      *
      * @return a integer {@code GADGET_HAL_*} represent hal version.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     @RequiresPermission(Manifest.permission.MANAGE_USB)
@@ -1211,7 +1238,7 @@
      *
      * @return a integer {@code USB_HAL_*} represent hal version.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     @RequiresPermission(Manifest.permission.MANAGE_USB)
@@ -1507,7 +1534,7 @@
      * @param usbDeviceConnectionHandler The component to handle usb connections,
      * {@code null} to unset.
      *
-     * {@hide}
+     * @hide
      */
     public void setUsbDeviceConnectionHandler(@Nullable ComponentName usbDeviceConnectionHandler) {
         try {
@@ -1526,7 +1553,7 @@
      *
      * @return Whether the mask is settable.
      *
-     * {@hide}
+     * @hide
      */
     public static boolean areSettableFunctions(long functions) {
         return functions == FUNCTION_NONE
@@ -1540,7 +1567,7 @@
      *
      * @return String representation of given mask
      *
-     * {@hide}
+     * @hide
      */
     public static String usbFunctionsToString(long functions) {
         StringJoiner joiner = new StringJoiner(",");
@@ -1576,7 +1603,7 @@
      *
      * @return A mask of all valid functions in the string
      *
-     * {@hide}
+     * @hide
      */
     public static long usbFunctionsFromString(String functions) {
         if (functions == null || functions.equals(USB_FUNCTION_NONE)) {
@@ -1598,7 +1625,7 @@
      *
      * @return a value of USB bandwidth
      *
-     * {@hide}
+     * @hide
      */
     public static int usbSpeedToBandwidth(int speed) {
         switch (speed) {
@@ -1632,12 +1659,14 @@
      *
      * @return String representation of Usb Gadget Hal Version
      *
-     * {@hide}
+     * @hide
      */
     public static @NonNull String usbGadgetHalVersionToString(int version) {
         String halVersion;
 
-        if (version == GADGET_HAL_V1_2) {
+        if (version == GADGET_HAL_V2_0) {
+            halVersion = GADGET_HAL_VERSION_2_0;
+        } else if (version == GADGET_HAL_V1_2) {
             halVersion = GADGET_HAL_VERSION_1_2;
         } else if (version == GADGET_HAL_V1_1) {
             halVersion = GADGET_HAL_VERSION_1_1;
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index fb66cb9..872414a 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -70,11 +70,14 @@
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
 import android.database.ContentObserver;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -98,6 +101,7 @@
 import android.util.Log;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
+import android.util.Xml;
 import android.util.proto.ProtoOutputStream;
 import android.view.BatchedInputEventReceiver.SimpleBatchedInputEventReceiver;
 import android.view.Choreographer;
@@ -158,6 +162,8 @@
 import com.android.internal.inputmethod.SoftInputShowHideReason;
 import com.android.internal.util.RingBuffer;
 
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -730,7 +736,6 @@
         @Override
         public final void initializeInternal(@NonNull IInputMethod.InitParams params) {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initializeInternal");
-            mConfigTracker.onInitialize(params.configChanges);
             mPrivOps.set(params.privilegedOperations);
             InputMethodPrivilegedOperationsRegistry.put(params.token, mPrivOps);
             mNavigationBarController.onNavButtonFlagsChanged(params.navigationBarFlags);
@@ -1601,6 +1606,8 @@
         mHideNavBarForKeyboard = getApplicationContext().getResources().getBoolean(
                 com.android.internal.R.bool.config_hideNavBarForKeyboard);
 
+        initConfigurationTracker();
+
         // TODO(b/111364446) Need to address context lifecycle issue if need to re-create
         // for update resources & configuration correctly when show soft input
         // in non-default display.
@@ -1656,6 +1663,36 @@
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
     }
 
+    private void initConfigurationTracker() {
+        final int flags = PackageManager.GET_META_DATA
+                | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+        final ComponentName imeComponent = new ComponentName(
+                getPackageName(), getClass().getName());
+        final String imeId = imeComponent.flattenToShortString();
+        final ServiceInfo si;
+        try {
+            si = getPackageManager().getServiceInfo(imeComponent,
+                    PackageManager.ComponentInfoFlags.of(flags));
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.wtf(TAG, "Unable to find input method " + imeId, e);
+            return;
+        }
+        try (XmlResourceParser parser = si.loadXmlMetaData(getPackageManager(),
+                InputMethod.SERVICE_META_DATA);
+             TypedArray sa = getResources().obtainAttributes(Xml.asAttributeSet(parser),
+                     com.android.internal.R.styleable.InputMethod)) {
+            if (parser == null) {
+                throw new XmlPullParserException(
+                        "No " + InputMethod.SERVICE_META_DATA + " meta-data");
+            }
+            final int handledConfigChanges = sa.getInt(
+                    com.android.internal.R.styleable.InputMethod_configChanges, 0);
+            mConfigTracker.onInitialize(handledConfigChanges);
+        } catch (Exception e) {
+            Log.wtf(TAG, "Unable to load input method " + imeId, e);
+        }
+    }
+
     /**
      * This is a hook that subclasses can use to perform initialization of
      * their interface.  It is called for you prior to any of your UI objects
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index a887f2a..d31540a 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -58,6 +58,8 @@
     void setUserIcon(int userId, in Bitmap icon);
     ParcelFileDescriptor getUserIcon(int userId);
     UserInfo getPrimaryUser();
+    int getMainUserId();
+    int getPreviousFullUserToEnterForeground();
     List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying, boolean excludePreCreated);
     List<UserInfo> getProfiles(int userId, boolean enabledOnly);
     int[] getProfileIds(int userId, boolean enabledOnly);
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index 9ea4278..394927e 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -252,10 +252,12 @@
     }
 
     /**
-     * Returns the list of declared instances for an interface.
+     * Returns an array of all declared instances for a particular interface.
      *
-     * @return true if the service is declared somewhere (eg. VINTF manifest) and
-     * waitForService should always be able to return the service.
+     * For instance, if 'android.foo.IFoo/foo' is declared (e.g. in VINTF
+     * manifest), and 'android.foo.IFoo' is passed here, then ["foo"] would be
+     * returned.
+     *
      * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index fb197f5..b919393 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -241,9 +241,16 @@
     /**
      * Writes a trace message to indicate that a given section of code has
      * begun. Must be followed by a call to {@link #asyncTraceEnd} using the same
-     * tag. Unlike {@link #traceBegin(long, String)} and {@link #traceEnd(long)},
-     * asynchronous events do not need to be nested. The name and cookie used to
-     * begin an event must be used to end it.
+     * tag, name and cookie.
+     *
+     * If two events with the same methodName overlap in time then they *must* have
+     * different cookie values. If they do not, the trace can become corrupted
+     * in unpredictable ways.
+     *
+     * Unlike {@link #traceBegin(long, String)} and {@link #traceEnd(long)},
+     * asynchronous events cannot be not nested. Consider using
+     * {@link #asyncTraceForTrackBegin(long, String, String, int)}
+     * if nested asynchronous events are needed.
      *
      * @param traceTag The trace tag.
      * @param methodName The method name to appear in the trace.
@@ -264,6 +271,9 @@
      * Must be called exactly once for each call to {@link #asyncTraceBegin(long, String, int)}
      * using the same tag, name and cookie.
      *
+     * See the documentation for {@link #asyncTraceBegin(long, String, int)}.
+     * for inteded usage of this method.
+     *
      * @param traceTag The trace tag.
      * @param methodName The method name to appear in the trace.
      * @param cookie Unique identifier for distinguishing simultaneous events
@@ -283,14 +293,73 @@
      * Writes a trace message to indicate that a given section of code has
      * begun. Must be followed by a call to {@link #asyncTraceForTrackEnd} using the same
      * track name and cookie.
-     * This function operates exactly like {@link #asyncTraceBegin(long, String, int)},
-     * except with the inclusion of a track name argument for where this method should appear.
-     * The cookie must be unique on the trackName level, not the methodName level
+     *
+     * Events with the same trackName and cookie nest inside each other in the
+     * same way as calls to {@link #traceBegin(long, String)} and
+     * {@link #traceEnd(long)}.
+     *
+     * If two events with the same trackName overlap in time but do not nest
+     * correctly, then they *must* have different cookie values. If they do not,
+     * the trace can become corrupted in unpredictable ways.
+     *
+     * Good Example:
+     *
+     * public void parent() {
+     *   asyncTraceForTrackBegin(TRACE_TAG_ALWAYS, "Track", "parent", mId);
+     *   child()
+     *   asyncTraceForTrackEnd(TRACE_TAG_ALWAYS, "Track", mId);
+     * }
+     *
+     * public void child() {
+     *   asyncTraceForTrackBegin(TRACE_TAG_ALWAYS, "Track", "child", mId);
+     *   // Some code here.
+     *   asyncTraceForTrackEnd(TRACE_TAG_ALWAYS, "Track", mId);
+     * }
+     *
+     * This would be visualized as so:
+     *   [   Parent   ]
+     *     [ Child ]
+     *
+     * Bad Example:
+     *
+     * public static void processData(String dataToProcess) {
+     *   asyncTraceForTrackBegin(TRACE_TAG_ALWAYS, "processDataInParallel", "processData", 0);
+     *   // Some code here.
+     *   asyncTraceForTrackEnd(TRACE_TAG_ALWAYS, "processDataInParallel", 0);
+     * }
+     *
+     * public static void processDataInParallel({@code List<String>} data) {
+     *   ExecutorService executor = Executors.newCachedThreadPool();
+     *   for (String s : data) {
+     *     pool.execute(() -> processData(s));
+     *   }
+     * }
+     *
+     * This is invalid because it's possible for processData to be run many times
+     * in parallel (i.e. processData events overlap) but the same cookie is
+     * provided each time.
+     *
+     * To fix this, specify a different id in each invocation of processData:
+     *
+     * public static void processData(String dataToProcess, int id) {
+     *   asyncTraceForTrackBegin(TRACE_TAG_ALWAYS, "processDataInParallel", "processData", id);
+     *   // Some code here.
+     *   asyncTraceForTrackEnd(TRACE_TAG_ALWAYS, "processDataInParallel", id);
+     * }
+     *
+     * public static void processDataInParallel({@code List<String>} data) {
+     *   ExecutorService executor = Executors.newCachedThreadPool();
+     *   for (int i = 0; i < data.size(); ++i) {
+     *     pool.execute(() -> processData(data.get(i), i));
+     *   }
+     * }
      *
      * @param traceTag The trace tag.
      * @param trackName The track where the event should appear in the trace.
      * @param methodName The method name to appear in the trace.
-     * @param cookie Unique identifier for distinguishing simultaneous events
+     * @param cookie Unique identifier used for nesting events on a single
+     *               track. Events which overlap without nesting on the same
+     *               track must have different values for cookie.
      *
      * @hide
      */
@@ -307,9 +376,14 @@
      * {@link #asyncTraceForTrackBegin(long, String, String, int)}
      * using the same tag, track name, and cookie.
      *
+     * See the documentation for {@link #asyncTraceForTrackBegin(long, String, String, int)}.
+     * for inteded usage of this method.
+     *
      * @param traceTag The trace tag.
      * @param trackName The track where the event should appear in the trace.
-     * @param cookie Unique identifier for distinguishing simultaneous events
+     * @param cookie Unique identifier used for nesting events on a single
+     *               track. Events which overlap without nesting on the same
+     *               track must have different values for cookie.
      *
      * @hide
      */
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 1f21bfe..dd02e02 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2377,14 +2377,16 @@
     }
 
     /**
-     * Returns true if the context user is the designated "main user" of the device. This user may
-     * have access to certain features which are limited to at most one user.
+     * Returns {@code true} if the context user is the designated "main user" of the device. This
+     * user may have access to certain features which are limited to at most one user. There will
+     * never be more than one main user on a device.
      *
-     * <p>Currently, the first human user on the device will be the main user; in the future, the
-     * concept may be transferable, so a different user (or even no user at all) may be designated
-     * the main user instead.
+     * <p>Currently, on most form factors the first human user on the device will be the main user;
+     * in the future, the concept may be transferable, so a different user (or even no user at all)
+     * may be designated the main user instead. On other form factors there might not be a main
+     * user.
      *
-     * <p>Note that this will be the not be the system user on devices for which
+     * <p>Note that this will not be the system user on devices for which
      * {@link #isHeadlessSystemUserMode()} returns true.
      * @hide
      */
@@ -2400,6 +2402,29 @@
     }
 
     /**
+     * Returns the designated "main user" of the device, or {@code null} if there is no main user.
+     *
+     * @see #isMainUser()
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS,
+            Manifest.permission.QUERY_USERS})
+    public @Nullable UserHandle getMainUser() {
+        try {
+            final int mainUserId = mService.getMainUserId();
+            if (mainUserId == UserHandle.USER_NULL) {
+                return null;
+            }
+            return UserHandle.of(mainUserId);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Used to check if the context user is an admin user. An admin user is allowed to
      * modify or configure certain settings that aren't available to non-admin users,
      * create and delete additional users, etc. There can be more than one admin users.
@@ -2951,8 +2976,15 @@
      * </ol>
      *
      * @return whether the user is visible at the moment, as defined above.
+     *
+     * @hide
      */
+    @SystemApi
     @UserHandleAware
+    @RequiresPermission(anyOf = {
+            "android.permission.INTERACT_ACROSS_USERS",
+            "android.permission.MANAGE_USERS"
+    })
     public boolean isUserVisible() {
         try {
             return mService.isUserVisible(mUserId);
@@ -2965,9 +2997,14 @@
      * Gets the visible users (as defined by {@link #isUserVisible()}.
      *
      * @return visible users at the moment.
+     *
+     * @hide
      */
-    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
-            Manifest.permission.INTERACT_ACROSS_USERS})
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            "android.permission.INTERACT_ACROSS_USERS",
+            "android.permission.MANAGE_USERS"
+    })
     public @NonNull Set<UserHandle> getVisibleUsers() {
         ArraySet<UserHandle> result = new ArraySet<>();
         try {
@@ -4275,6 +4312,43 @@
     }
 
     /**
+     * Returns the user who was last in the foreground, not including the current user and not
+     * including profiles.
+     *
+     * <p>Returns {@code null} if there is no previous user, for example if there
+     * is only one full user (i.e. only one user which is not a profile) on the device.
+     *
+     * <p>This method may be used for example to find the user to switch back to if the
+     * current user is removed, or if creating a new user is aborted.
+     *
+     * <p>Note that reboots do not interrupt this calculation; the previous user need not have
+     * used the device since it rebooted.
+     *
+     * <p>Note also that on devices that support multiple users on multiple displays, it is possible
+     * that the returned user will be visible on a secondary display, as the foreground user is the
+     * one associated with the main display.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.CREATE_USERS,
+            android.Manifest.permission.QUERY_USERS
+    })
+    public @Nullable UserHandle getPreviousForegroundUser() {
+        try {
+            final int previousUser = mService.getPreviousFullUserToEnterForeground();
+            if (previousUser == UserHandle.USER_NULL) {
+                return null;
+            }
+            return UserHandle.of(previousUser);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Checks whether it's possible to add more users.
      *
      * @return true if more users can be added, false if limit has been reached.
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 71bc4b3..3448a9e 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -227,6 +227,31 @@
     }
 
     /**
+     * Computes a legacy vibration pattern (i.e. a pattern with duration values for "off/on"
+     * vibration components) that is equivalent to this VibrationEffect.
+     *
+     * <p>All non-repeating effects created with {@link #createWaveform(int[], int)} are convertible
+     * into an equivalent vibration pattern with this method. It is not guaranteed that an effect
+     * created with other means becomes converted into an equivalent legacy vibration pattern, even
+     * if it has an equivalent vibration pattern. If this method is unable to create an equivalent
+     * vibration pattern for such effects, it will return {@code null}.
+     *
+     * <p>Note that a valid equivalent long[] pattern cannot be created for an effect that has any
+     * form of repeating behavior, regardless of how the effect was created. For repeating effects,
+     * the method will always return {@code null}.
+     *
+     * @return a long array representing a vibration pattern equivalent to the VibrationEffect, if
+     *               the method successfully derived a vibration pattern equivalent to the effect
+     *               (this will always be the case if the effect was created via
+     *               {@link #createWaveform(int[], int)} and is non-repeating). Otherwise, returns
+     *               {@code null}.
+     * @hide
+     */
+    @TestApi
+    @Nullable
+    public abstract long[] computeCreateWaveformOffOnTimingsOrNull();
+
+    /**
      * Create a waveform vibration.
      *
      * <p>Waveform vibrations are a potentially repeating series of timing and amplitude pairs,
@@ -641,6 +666,51 @@
             return mRepeatIndex;
         }
 
+         /** @hide */
+        @Override
+        @Nullable
+        public long[] computeCreateWaveformOffOnTimingsOrNull() {
+            if (getRepeatIndex() >= 0) {
+                // Repeating effects cannot be fully represented as a long[] legacy pattern.
+                return null;
+            }
+
+            List<VibrationEffectSegment> segments = getSegments();
+
+            // The maximum possible size of the final pattern is 1 plus the number of segments in
+            // the original effect. This is because we will add an empty "off" segment at the
+            // start of the pattern if the first segment of the original effect is an "on" segment.
+            // (because the legacy patterns start with an "off" pattern). Other than this one case,
+            // we will add the durations of back-to-back segments of similar amplitudes (amplitudes
+            // that are all "on" or "off") and create a pattern entry for the total duration, which
+            // will not take more number pattern entries than the number of segments processed.
+            long[] patternBuffer = new long[segments.size() + 1];
+            int patternIndex = 0;
+
+            for (int i = 0; i < segments.size(); i++) {
+                StepSegment stepSegment =
+                        castToValidStepSegmentForOffOnTimingsOrNull(segments.get(i));
+                if (stepSegment == null) {
+                    // This means that there is 1 or more segments of this effect that is/are not a
+                    // possible component of a legacy vibration pattern. Thus, the VibrationEffect
+                    // does not have any equivalent legacy vibration pattern.
+                    return null;
+                }
+
+                boolean isSegmentOff = stepSegment.getAmplitude() == 0;
+                // Even pattern indices are "off", and odd pattern indices are "on"
+                boolean isCurrentPatternIndexOff = (patternIndex % 2) == 0;
+                if (isSegmentOff != isCurrentPatternIndexOff) {
+                    // Move the pattern index one step ahead, so that the current segment's
+                    // "off"/"on" property matches that of the index's
+                    ++patternIndex;
+                }
+                patternBuffer[patternIndex] += stepSegment.getDuration();
+            }
+
+            return Arrays.copyOf(patternBuffer, patternIndex + 1);
+        }
+
         /** @hide */
         @Override
         public void validate() {
@@ -806,6 +876,31 @@
                         return new Composed[size];
                     }
                 };
+
+        /**
+         * Casts a provided {@link VibrationEffectSegment} to a {@link StepSegment} and returns it,
+         * only if it can possibly be a segment for an effect created via
+         * {@link #createWaveform(int[], int)}. Otherwise, returns {@code null}.
+         */
+        @Nullable
+        private static StepSegment castToValidStepSegmentForOffOnTimingsOrNull(
+                VibrationEffectSegment segment) {
+            if (!(segment instanceof StepSegment)) {
+                return null;
+            }
+
+            StepSegment stepSegment = (StepSegment) segment;
+            if (stepSegment.getFrequencyHz() != 0) {
+                return null;
+            }
+
+            float amplitude = stepSegment.getAmplitude();
+            if (amplitude != 0 && amplitude != DEFAULT_AMPLITUDE) {
+                return null;
+            }
+
+            return stepSegment;
+        }
     }
 
     /**
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index aebd91a..84a233f 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -2183,6 +2183,17 @@
         }
     }
 
+    /**
+     * Returns a Looper which messages such as {@link WallpaperService#DO_ATTACH},
+     * {@link WallpaperService#DO_DETACH} etc. are sent to.
+     * By default, returns the process's main looper.
+     * @hide
+     */
+    @NonNull
+    public Looper onProvideEngineLooper() {
+        return super.getMainLooper();
+    }
+
     private boolean isValid(RectF area) {
         if (area == null) return false;
         boolean valid = area.bottom > area.top && area.left < area.right
@@ -2215,12 +2226,12 @@
         Engine mEngine;
         @SetWallpaperFlags int mWhich;
 
-        IWallpaperEngineWrapper(WallpaperService context,
+        IWallpaperEngineWrapper(WallpaperService service,
                 IWallpaperConnection conn, IBinder windowToken,
                 int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,
                 int displayId, @SetWallpaperFlags int which) {
             mWallpaperManager = getSystemService(WallpaperManager.class);
-            mCaller = new HandlerCaller(context, context.getMainLooper(), this, true);
+            mCaller = new HandlerCaller(service, service.onProvideEngineLooper(), this, true);
             mConnection = conn;
             mWindowToken = windowToken;
             mWindowType = windowType;
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index e5c9adb..dded76c 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -26,7 +26,6 @@
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.Looper;
-import android.telephony.Annotation.CallState;
 import android.telephony.Annotation.DisconnectCauses;
 import android.telephony.Annotation.PreciseDisconnectCauses;
 import android.telephony.Annotation.RadioPowerState;
@@ -726,7 +725,7 @@
      */
     @Deprecated
     @RequiresPermission(value = android.Manifest.permission.READ_PHONE_STATE, conditional = true)
-    public void onCallStateChanged(@CallState int state, String phoneNumber) {
+    public void onCallStateChanged(@Annotation.CallState int state, String phoneNumber) {
         // default implementation empty
     }
 
@@ -1569,12 +1568,48 @@
                     () -> mExecutor.execute(() -> psl.onRadioPowerStateChanged(state)));
         }
 
-        public void onCallAttributesChanged(CallAttributes callAttributes) {
+        public void onCallStatesChanged(List<CallState> callStateList) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
 
+            if (callStateList == null) return;
+            CallAttributes ca;
+            if (callStateList.isEmpty()) {
+                ca = new CallAttributes(
+                        new PreciseCallState(PreciseCallState.PRECISE_CALL_STATE_IDLE,
+                                PreciseCallState.PRECISE_CALL_STATE_IDLE,
+                                PreciseCallState.PRECISE_CALL_STATE_IDLE,
+                                DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID),
+                        TelephonyManager.NETWORK_TYPE_UNKNOWN, new CallQuality());
+            } else {
+                int foregroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+                int backgroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+                int ringingCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+                for (CallState cs : callStateList) {
+                    switch (cs.getCallClassification()) {
+                        case CallState.CALL_CLASSIFICATION_FOREGROUND:
+                            foregroundCallState = cs.getCallState();
+                            break;
+                        case CallState.CALL_CLASSIFICATION_BACKGROUND:
+                            backgroundCallState = cs.getCallState();
+                            break;
+                        case CallState.CALL_CLASSIFICATION_RINGING:
+                            ringingCallState = cs.getCallState();
+                            break;
+                        default:
+                            break;
+                    }
+                }
+                ca = new CallAttributes(
+                        new PreciseCallState(
+                                ringingCallState, foregroundCallState, backgroundCallState,
+                                DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID),
+                        callStateList.get(0).getNetworkType(),
+                        callStateList.get(0).getCallQuality());
+            }
             Binder.withCleanCallingIdentity(
-                    () -> mExecutor.execute(() -> psl.onCallAttributesChanged(callAttributes)));
+                    () -> mExecutor.execute(
+                            () -> psl.onCallAttributesChanged(ca)));
         }
 
         public void onActiveDataSubIdChanged(int subId) {
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index e8960b8..257f3b7 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -27,6 +27,7 @@
 import android.os.Build;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsReasonInfo;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.IPhoneStateListener;
@@ -62,7 +63,7 @@
  * appropriate sub-interfaces.
  */
 public class TelephonyCallback {
-
+    private static final String LOG_TAG = "TelephonyCallback";
     /**
      * Experiment flag to set the per-pid registration limit for TelephonyCallback
      *
@@ -1332,7 +1333,9 @@
     @SystemApi
     public interface CallAttributesListener {
         /**
-         * Callback invoked when the call attributes changes on the registered subscription.
+         * Callback invoked when the call attributes changes on the active call on the registered
+         * subscription. If the user swaps between a foreground and background call the call
+         * attributes will be reported for the active call only.
          * Note, the registration subscription ID comes from {@link TelephonyManager} object
          * which registers TelephonyCallback by
          * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}.
@@ -1346,9 +1349,77 @@
          * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}.
          *
          * @param callAttributes the call attributes
+         * @deprecated Use onCallStatesChanged({@link List<CallState>}) to get each of call
+         *          state for all ongoing calls on the subscription.
          */
         @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
-        void onCallAttributesChanged(@NonNull CallAttributes callAttributes);
+        @Deprecated
+        default void onCallAttributesChanged(@NonNull CallAttributes callAttributes) {
+            Log.w(LOG_TAG, "onCallAttributesChanged(List<CallState>) should be "
+                    + "overridden.");
+        }
+
+        /**
+         * Callback invoked when the call attributes changes on the ongoing calls on the registered
+         * subscription. If there are 1 foreground and 1 background call, Two {@link CallState}
+         * will be passed.
+         * Note, the registration subscription ID comes from {@link TelephonyManager} object
+         * which registers TelephonyCallback by
+         * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}.
+         * If this TelephonyManager object was created with
+         * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
+         * subscription ID. Otherwise, this callback applies to
+         * {@link SubscriptionManager#getDefaultSubscriptionId()}.
+         * In the event that there are no active(state is not
+         * {@link PreciseCallState#PRECISE_CALL_STATE_IDLE}) calls, this API will report empty list.
+         *
+         * The calling app should have carrier privileges
+         * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
+         * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}.
+         *
+         * @param callStateList the list of call states for each ongoing call. If there are
+         *                           a active call and a holding call, 1 call attributes for
+         *                           {@link PreciseCallState#PRECISE_CALL_STATE_ACTIVE}  and another
+         *                           for {@link PreciseCallState#PRECISE_CALL_STATE_HOLDING}
+         *                           will be in this list.
+         */
+        // Added as default for backward compatibility
+        @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
+        default void onCallStatesChanged(@NonNull List<CallState> callStateList) {
+            if (callStateList.size() > 0) {
+                int foregroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+                int backgroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+                int ringingCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+                for (CallState cs : callStateList) {
+                    switch (cs.getCallClassification()) {
+                        case CallState.CALL_CLASSIFICATION_FOREGROUND:
+                            foregroundCallState = cs.getCallState();
+                            break;
+                        case CallState.CALL_CLASSIFICATION_BACKGROUND:
+                            backgroundCallState = cs.getCallState();
+                            break;
+                        case CallState.CALL_CLASSIFICATION_RINGING:
+                            ringingCallState = cs.getCallState();
+                            break;
+                        default:
+                            break;
+                    }
+                }
+                onCallAttributesChanged(new CallAttributes(
+                        new PreciseCallState(
+                                ringingCallState, foregroundCallState, backgroundCallState,
+                                DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID),
+                        callStateList.get(0).getNetworkType(),
+                        callStateList.get(0).getCallQuality()));
+            } else {
+                onCallAttributesChanged(new CallAttributes(
+                        new PreciseCallState(PreciseCallState.PRECISE_CALL_STATE_IDLE,
+                                PreciseCallState.PRECISE_CALL_STATE_IDLE,
+                                PreciseCallState.PRECISE_CALL_STATE_IDLE,
+                                DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID),
+                        TelephonyManager.NETWORK_TYPE_UNKNOWN, new CallQuality()));
+            }
+        }
     }
 
     /**
@@ -1702,14 +1773,13 @@
                     () -> mExecutor.execute(() -> listener.onRadioPowerStateChanged(state)));
         }
 
-        public void onCallAttributesChanged(CallAttributes callAttributes) {
+        public void onCallStatesChanged(List<CallState> callStateList) {
             CallAttributesListener listener =
                     (CallAttributesListener) mTelephonyCallbackWeakRef.get();
             if (listener == null) return;
 
             Binder.withCleanCallingIdentity(
-                    () -> mExecutor.execute(() -> listener.onCallAttributesChanged(
-                            callAttributes)));
+                    () -> mExecutor.execute(() -> listener.onCallStatesChanged(callStateList)));
         }
 
         public void onActiveDataSubIdChanged(int subId) {
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index a3696e3..0a1538de 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -32,13 +32,13 @@
 import android.telephony.Annotation.DataActivityType;
 import android.telephony.Annotation.DisconnectCauses;
 import android.telephony.Annotation.NetworkType;
-import android.telephony.Annotation.PreciseCallStates;
 import android.telephony.Annotation.PreciseDisconnectCauses;
 import android.telephony.Annotation.RadioPowerState;
 import android.telephony.Annotation.SimActivationState;
 import android.telephony.Annotation.SrvccState;
 import android.telephony.TelephonyManager.CarrierPrivilegesCallback;
 import android.telephony.emergency.EmergencyNumber;
+import android.telephony.ims.ImsCallSession;
 import android.telephony.ims.ImsReasonInfo;
 import android.util.ArraySet;
 import android.util.Log;
@@ -741,17 +741,20 @@
      * @param slotIndex for which precise call state changed. Can be derived from subId except when
      * subId is invalid.
      * @param subId for which precise call state changed.
-     * @param ringCallPreciseState ringCall state.
-     * @param foregroundCallPreciseState foreground call state.
-     * @param backgroundCallPreciseState background call state.
+     * @param callStates Array of PreciseCallState of foreground, background & ringing calls.
+     * @param imsCallIds Array of IMS call session ID{@link ImsCallSession#getCallId} for
+     *                   ringing, foreground & background calls.
+     * @param imsServiceTypes Array of IMS call service type for ringing, foreground &
+     *                        background calls.
+     * @param imsCallTypes Array of IMS call type for ringing, foreground & background calls.
      */
     public void notifyPreciseCallState(int slotIndex, int subId,
-            @PreciseCallStates int ringCallPreciseState,
-            @PreciseCallStates int foregroundCallPreciseState,
-            @PreciseCallStates int backgroundCallPreciseState) {
+            @Annotation.PreciseCallStates int[] callStates, String[] imsCallIds,
+            @Annotation.ImsCallServiceType int[] imsServiceTypes,
+            @Annotation.ImsCallType int[] imsCallTypes) {
         try {
-            sRegistry.notifyPreciseCallState(slotIndex, subId, ringCallPreciseState,
-                foregroundCallPreciseState, backgroundCallPreciseState);
+            sRegistry.notifyPreciseCallState(slotIndex, subId, callStates,
+                    imsCallIds, imsServiceTypes, imsCallTypes);
         } catch (RemoteException ex) {
             // system process is dead
             throw ex.rethrowFromSystemServer();
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 608cbda..4277d01 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -140,6 +140,13 @@
     public static final String SETTINGS_ADB_METRICS_WRITER = "settings_adb_metrics_writer";
 
     /**
+     * Flag to show stylus-specific preferences in Connected Devices
+     * @hide
+     */
+    public static final String SETTINGS_SHOW_STYLUS_PREFERENCES =
+            "settings_show_stylus_preferences";
+
+    /**
      * Flag to enable/disable biometrics enrollment v2
      * @hide
      */
@@ -181,10 +188,12 @@
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "false");
         DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
+        DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "false");
         DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
     }
 
     private static final Set<String> PERSISTENT_FLAGS;
+
     static {
         PERSISTENT_FLAGS = new HashSet<>();
         PERSISTENT_FLAGS.add(SETTINGS_ALLOW_INTENT_REDIRECTION_FOR_CLONE_PROFILE);
diff --git a/core/java/android/util/IntArray.java b/core/java/android/util/IntArray.java
index 7b28b8a..bc0e35d 100644
--- a/core/java/android/util/IntArray.java
+++ b/core/java/android/util/IntArray.java
@@ -234,4 +234,23 @@
     public int[] toArray() {
         return Arrays.copyOf(mValues, mSize);
     }
+
+    @Override
+    public String toString() {
+        // Code below is copied from Arrays.toString(), but uses mSize in the lopp (it cannot call
+        // Arrays.toString() directly as it would return the unused elements as well)
+        int iMax = mSize - 1;
+        if (iMax == -1) {
+            return "[]";
+        }
+        StringBuilder b = new StringBuilder();
+        b.append('[');
+        for (int i = 0;; i++) {
+            b.append(mValues[i]);
+            if (i == iMax) {
+                return b.append(']').toString();
+            }
+            b.append(", ");
+        }
+    }
 }
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 5933ae4..2745858 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -319,6 +319,19 @@
     public static final int FLAG_TOUCH_FEEDBACK_DISABLED = 1 << 10;
 
     /**
+     * Flag: Indicates that the display maintains its own focus and touch mode.
+     *
+     * This flag is similar to {@link com.android.internal.R.bool.config_perDisplayFocusEnabled} in
+     * behavior, but only applies to the specific display instead of system-wide to all displays.
+     *
+     * Note: The display must be trusted in order to have its own focus.
+     *
+     * @see #FLAG_TRUSTED
+     * @hide
+     */
+    public static final int FLAG_OWN_FOCUS = 1 << 11;
+
+    /**
      * Display flag: Indicates that the contents of the display should not be scaled
      * to fit the physical screen dimensions.  Used for development only to emulate
      * devices with smaller physicals screens while preserving density.
@@ -996,6 +1009,28 @@
     }
 
     /**
+     * Returns the {@link DisplayShape} which is based on display coordinates.
+     *
+     * To get the {@link DisplayShape} based on the window frame, use
+     * {@link WindowInsets#getDisplayShape()} instead.
+     *
+     * @see DisplayShape
+     */
+    @SuppressLint("VisiblySynchronized")
+    @NonNull
+    public DisplayShape getShape() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            final DisplayShape displayShape = mDisplayInfo.displayShape;
+            final @Surface.Rotation int rotation = getLocalRotation();
+            if (displayShape != null && rotation != mDisplayInfo.rotation) {
+                return displayShape.setRotation(rotation);
+            }
+            return displayShape;
+        }
+    }
+
+    /**
      * Gets the pixel format of the display.
      * @return One of the constants defined in {@link android.graphics.PixelFormat}.
      *
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 0ba3072..138017c 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -323,6 +323,9 @@
     @Surface.Rotation
     public int installOrientation;
 
+    @Nullable
+    public DisplayShape displayShape;
+
     public static final @android.annotation.NonNull Creator<DisplayInfo> CREATOR = new Creator<DisplayInfo>() {
         @Override
         public DisplayInfo createFromParcel(Parcel source) {
@@ -395,7 +398,8 @@
                 && brightnessMaximum == other.brightnessMaximum
                 && brightnessDefault == other.brightnessDefault
                 && Objects.equals(roundedCorners, other.roundedCorners)
-                && installOrientation == other.installOrientation;
+                && installOrientation == other.installOrientation
+                && Objects.equals(displayShape, other.displayShape);
     }
 
     @Override
@@ -448,6 +452,7 @@
         brightnessDefault = other.brightnessDefault;
         roundedCorners = other.roundedCorners;
         installOrientation = other.installOrientation;
+        displayShape = other.displayShape;
     }
 
     public void readFromParcel(Parcel source) {
@@ -506,6 +511,7 @@
             userDisabledHdrTypes[i] = source.readInt();
         }
         installOrientation = source.readInt();
+        displayShape = source.readTypedObject(DisplayShape.CREATOR);
     }
 
     @Override
@@ -562,6 +568,7 @@
             dest.writeInt(userDisabledHdrTypes[i]);
         }
         dest.writeInt(installOrientation);
+        dest.writeTypedObject(displayShape, flags);
     }
 
     @Override
diff --git a/core/java/android/view/DisplayShape.aidl b/core/java/android/view/DisplayShape.aidl
new file mode 100644
index 0000000..af8b417
--- /dev/null
+++ b/core/java/android/view/DisplayShape.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2022, 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.view;
+
+parcelable DisplayShape;
diff --git a/core/java/android/view/DisplayShape.java b/core/java/android/view/DisplayShape.java
new file mode 100644
index 0000000..43bd773
--- /dev/null
+++ b/core/java/android/view/DisplayShape.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) 2022 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.view;
+
+import static android.view.Surface.ROTATION_0;
+
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.DisplayUtils;
+import android.util.PathParser;
+import android.util.RotationUtils;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+
+/**
+ * A class representing the shape of a display. It provides a {@link Path} of the display shape of
+ * the display shape.
+ *
+ * {@link DisplayShape} is immutable.
+ */
+public final class DisplayShape implements Parcelable {
+
+    /** @hide */
+    public static final DisplayShape NONE = new DisplayShape("" /* displayShapeSpec */,
+            0 /* displayWidth */, 0 /* displayHeight */, 0 /* physicalPixelDisplaySizeRatio */,
+            0 /* rotation */);
+
+    /** @hide */
+    @VisibleForTesting
+    public final String mDisplayShapeSpec;
+    private final float mPhysicalPixelDisplaySizeRatio;
+    private final int mDisplayWidth;
+    private final int mDisplayHeight;
+    private final int mRotation;
+    private final int mOffsetX;
+    private final int mOffsetY;
+    private final float mScale;
+
+    private DisplayShape(@NonNull String displayShapeSpec, int displayWidth, int displayHeight,
+            float physicalPixelDisplaySizeRatio, int rotation) {
+        this(displayShapeSpec, displayWidth, displayHeight, physicalPixelDisplaySizeRatio,
+                rotation, 0, 0, 1f);
+    }
+
+    private DisplayShape(@NonNull String displayShapeSpec, int displayWidth, int displayHeight,
+            float physicalPixelDisplaySizeRatio, int rotation, int offsetX, int offsetY,
+            float scale) {
+        mDisplayShapeSpec = displayShapeSpec;
+        mDisplayWidth = displayWidth;
+        mDisplayHeight = displayHeight;
+        mPhysicalPixelDisplaySizeRatio = physicalPixelDisplaySizeRatio;
+        mRotation = rotation;
+        mOffsetX = offsetX;
+        mOffsetY = offsetY;
+        mScale = scale;
+    }
+
+    /**
+     * @hide
+     */
+    @NonNull
+    public static DisplayShape fromResources(
+            @NonNull Resources res, @NonNull String displayUniqueId, int physicalDisplayWidth,
+            int physicalDisplayHeight, int displayWidth, int displayHeight) {
+        final boolean isScreenRound = RoundedCorners.getBuiltInDisplayIsRound(res, displayUniqueId);
+        final String spec = getSpecString(res, displayUniqueId);
+        if (spec == null || spec.isEmpty()) {
+            return createDefaultDisplayShape(displayWidth, displayHeight, isScreenRound);
+        }
+        final float physicalPixelDisplaySizeRatio = DisplayUtils.getPhysicalPixelDisplaySizeRatio(
+                physicalDisplayWidth, physicalDisplayHeight, displayWidth, displayHeight);
+        return fromSpecString(spec, physicalPixelDisplaySizeRatio, displayWidth, displayHeight);
+    }
+
+    /**
+     * @hide
+     */
+    @NonNull
+    public static DisplayShape createDefaultDisplayShape(
+            int displayWidth, int displayHeight, boolean isScreenRound) {
+        return fromSpecString(createDefaultSpecString(displayWidth, displayHeight, isScreenRound),
+                1f, displayWidth, displayHeight);
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    @NonNull
+    public static DisplayShape fromSpecString(@NonNull String spec,
+            float physicalPixelDisplaySizeRatio, int displayWidth, int displayHeight) {
+        return Cache.getDisplayShape(spec, physicalPixelDisplaySizeRatio, displayWidth,
+                    displayHeight);
+    }
+
+    private static String createDefaultSpecString(int displayWidth, int displayHeight,
+            boolean isCircular) {
+        final String spec;
+        if (isCircular) {
+            final float xRadius = displayWidth / 2f;
+            final float yRadius = displayHeight / 2f;
+            // Draw a circular display shape.
+            spec = "M0," + yRadius
+                    // Draw upper half circle with arcTo command.
+                    + " A" + xRadius + "," + yRadius + " 0 1,1 " + displayWidth + "," + yRadius
+                    // Draw lower half circle with arcTo command.
+                    + " A" + xRadius + "," + yRadius + " 0 1,1 0," + yRadius + " Z";
+        } else {
+            // Draw a rectangular display shape.
+            spec = "M0,0"
+                    // Draw top edge.
+                    + " L" + displayWidth + ",0"
+                    // Draw right edge.
+                    + " L" + displayWidth + "," + displayHeight
+                    // Draw bottom edge.
+                    + " L0," + displayHeight
+                    // Draw left edge by close command which draws a line from current position to
+                    // the initial points (0,0).
+                    + " Z";
+        }
+        return spec;
+    }
+
+    /**
+     * Gets the display shape svg spec string of a display which is determined by the given display
+     * unique id.
+     *
+     * Loads the default config {@link R.string#config_mainDisplayShape} if
+     * {@link R.array#config_displayUniqueIdArray} is not set.
+     *
+     * @hide
+     */
+    public static String getSpecString(Resources res, String displayUniqueId) {
+        final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId);
+        final TypedArray array = res.obtainTypedArray(R.array.config_displayShapeArray);
+        final String spec;
+        if (index >= 0 && index < array.length()) {
+            spec = array.getString(index);
+        } else {
+            spec = res.getString(R.string.config_mainDisplayShape);
+        }
+        array.recycle();
+        return spec;
+    }
+
+    /**
+     * @hide
+     */
+    public DisplayShape setRotation(int rotation) {
+        return new DisplayShape(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight,
+                mPhysicalPixelDisplaySizeRatio, rotation, mOffsetX, mOffsetY, mScale);
+    }
+
+    /**
+     * @hide
+     */
+    public DisplayShape setOffset(int offsetX, int offsetY) {
+        return new DisplayShape(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight,
+                mPhysicalPixelDisplaySizeRatio, mRotation, offsetX, offsetY, mScale);
+    }
+
+    /**
+     * @hide
+     */
+    public DisplayShape setScale(float scale) {
+        return new DisplayShape(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight,
+                mPhysicalPixelDisplaySizeRatio, mRotation, mOffsetX, mOffsetY, scale);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight,
+                mPhysicalPixelDisplaySizeRatio, mRotation, mOffsetX, mOffsetY, mScale);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o instanceof DisplayShape) {
+            DisplayShape ds = (DisplayShape) o;
+            return Objects.equals(mDisplayShapeSpec, ds.mDisplayShapeSpec)
+                    && mDisplayWidth == ds.mDisplayWidth && mDisplayHeight == ds.mDisplayHeight
+                    && mPhysicalPixelDisplaySizeRatio == ds.mPhysicalPixelDisplaySizeRatio
+                    && mRotation == ds.mRotation && mOffsetX == ds.mOffsetX
+                    && mOffsetY == ds.mOffsetY && mScale == ds.mScale;
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return "DisplayShape{"
+                + " spec=" + mDisplayShapeSpec
+                + " displayWidth=" + mDisplayWidth
+                + " displayHeight=" + mDisplayHeight
+                + " physicalPixelDisplaySizeRatio=" + mPhysicalPixelDisplaySizeRatio
+                + " rotation=" + mRotation
+                + " offsetX=" + mOffsetX
+                + " offsetY=" + mOffsetY
+                + " scale=" + mScale + "}";
+    }
+
+    /**
+     * Returns a {@link Path} of the display shape.
+     *
+     * @return a {@link Path} of the display shape.
+     */
+    @NonNull
+    public Path getPath() {
+        return Cache.getPath(this);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mDisplayShapeSpec);
+        dest.writeInt(mDisplayWidth);
+        dest.writeInt(mDisplayHeight);
+        dest.writeFloat(mPhysicalPixelDisplaySizeRatio);
+        dest.writeInt(mRotation);
+        dest.writeInt(mOffsetX);
+        dest.writeInt(mOffsetY);
+        dest.writeFloat(mScale);
+    }
+
+    public static final @NonNull Creator<DisplayShape> CREATOR = new Creator<DisplayShape>() {
+        @Override
+        public DisplayShape createFromParcel(Parcel in) {
+            final String spec = in.readString8();
+            final int displayWidth = in.readInt();
+            final int displayHeight = in.readInt();
+            final float ratio = in.readFloat();
+            final int rotation = in.readInt();
+            final int offsetX = in.readInt();
+            final int offsetY = in.readInt();
+            final float scale = in.readFloat();
+            return new DisplayShape(spec, displayWidth, displayHeight, ratio, rotation, offsetX,
+                    offsetY, scale);
+        }
+
+        @Override
+        public DisplayShape[] newArray(int size) {
+            return new DisplayShape[size];
+        }
+    };
+
+    private static final class Cache {
+        private static final Object CACHE_LOCK = new Object();
+
+        @GuardedBy("CACHE_LOCK")
+        private static String sCachedSpec;
+        @GuardedBy("CACHE_LOCK")
+        private static int sCachedDisplayWidth;
+        @GuardedBy("CACHE_LOCK")
+        private static int sCachedDisplayHeight;
+        @GuardedBy("CACHE_LOCK")
+        private static float sCachedPhysicalPixelDisplaySizeRatio;
+        @GuardedBy("CACHE_LOCK")
+        private static DisplayShape sCachedDisplayShape;
+
+        @GuardedBy("CACHE_LOCK")
+        private static DisplayShape sCacheForPath;
+        @GuardedBy("CACHE_LOCK")
+        private static Path sCachedPath;
+
+        static DisplayShape getDisplayShape(String spec, float physicalPixelDisplaySizeRatio,
+                int displayWidth, int displayHeight) {
+            synchronized (CACHE_LOCK) {
+                if (spec.equals(sCachedSpec)
+                        && sCachedDisplayWidth == displayWidth
+                        && sCachedDisplayHeight == displayHeight
+                        && sCachedPhysicalPixelDisplaySizeRatio == physicalPixelDisplaySizeRatio) {
+                    return sCachedDisplayShape;
+                }
+            }
+
+            final DisplayShape shape = new DisplayShape(spec, displayWidth, displayHeight,
+                    physicalPixelDisplaySizeRatio, ROTATION_0);
+
+            synchronized (CACHE_LOCK) {
+                sCachedSpec = spec;
+                sCachedDisplayWidth = displayWidth;
+                sCachedDisplayHeight = displayHeight;
+                sCachedPhysicalPixelDisplaySizeRatio = physicalPixelDisplaySizeRatio;
+                sCachedDisplayShape = shape;
+            }
+            return shape;
+        }
+
+        static Path getPath(@NonNull DisplayShape shape) {
+            synchronized (CACHE_LOCK) {
+                if (shape.equals(sCacheForPath)) {
+                    return sCachedPath;
+                }
+            }
+
+            final Path path = PathParser.createPathFromPathData(shape.mDisplayShapeSpec);
+
+            if (!path.isEmpty()) {
+                final Matrix matrix = new Matrix();
+                if (shape.mRotation != ROTATION_0) {
+                    RotationUtils.transformPhysicalToLogicalCoordinates(
+                            shape.mRotation, shape.mDisplayWidth, shape.mDisplayHeight, matrix);
+                }
+                if (shape.mPhysicalPixelDisplaySizeRatio != 1f) {
+                    matrix.preScale(shape.mPhysicalPixelDisplaySizeRatio,
+                            shape.mPhysicalPixelDisplaySizeRatio);
+                }
+                if (shape.mOffsetX != 0 || shape.mOffsetY != 0) {
+                    matrix.postTranslate(shape.mOffsetX, shape.mOffsetY);
+                }
+                if (shape.mScale != 1f) {
+                    matrix.postScale(shape.mScale, shape.mScale);
+                }
+                path.transform(matrix);
+            }
+
+            synchronized (CACHE_LOCK) {
+                sCacheForPath = shape;
+                sCachedPath = path;
+            }
+            return path;
+        }
+    }
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index e2bc566..0743ccb 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -738,9 +738,8 @@
      * If invoked through a package other than a launcher app, returns an empty list.
      *
      * @param displayId the id of the logical display
-     * @param packageName the name of the calling package
      */
-    List<DisplayInfo> getPossibleDisplayInfo(int displayId, String packageName);
+    List<DisplayInfo> getPossibleDisplayInfo(int displayId);
 
     /**
      * Called to show global actions.
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index a8cc9b6..c56d618 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -197,6 +197,9 @@
     private PrivacyIndicatorBounds mPrivacyIndicatorBounds =
             new PrivacyIndicatorBounds();
 
+    /** The display shape */
+    private DisplayShape mDisplayShape = DisplayShape.NONE;
+
     public InsetsState() {
     }
 
@@ -271,6 +274,7 @@
                 alwaysConsumeSystemBars, calculateRelativeCutout(frame),
                 calculateRelativeRoundedCorners(frame),
                 calculateRelativePrivacyIndicatorBounds(frame),
+                calculateRelativeDisplayShape(frame),
                 compatInsetsTypes, (legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0);
     }
 
@@ -335,6 +339,16 @@
         return mPrivacyIndicatorBounds.inset(insetLeft, insetTop, insetRight, insetBottom);
     }
 
+    private DisplayShape calculateRelativeDisplayShape(Rect frame) {
+        if (mDisplayFrame.equals(frame)) {
+            return mDisplayShape;
+        }
+        if (frame == null) {
+            return DisplayShape.NONE;
+        }
+        return mDisplayShape.setOffset(-frame.left, -frame.top);
+    }
+
     public Insets calculateInsets(Rect frame, @InsetsType int types, boolean ignoreVisibility) {
         Insets insets = Insets.NONE;
         for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
@@ -589,6 +603,14 @@
         return mPrivacyIndicatorBounds;
     }
 
+    public void setDisplayShape(DisplayShape displayShape) {
+        mDisplayShape = displayShape;
+    }
+
+    public DisplayShape getDisplayShape() {
+        return mDisplayShape;
+    }
+
     /**
      * Modifies the state of this class to exclude a certain type to make it ready for dispatching
      * to the client.
@@ -628,6 +650,7 @@
         mRoundedCorners = mRoundedCorners.scale(scale);
         mRoundedCornerFrame.scale(scale);
         mPrivacyIndicatorBounds = mPrivacyIndicatorBounds.scale(scale);
+        mDisplayShape = mDisplayShape.setScale(scale);
         for (int i = 0; i < SIZE; i++) {
             final InsetsSource source = mSources[i];
             if (source != null) {
@@ -650,6 +673,7 @@
         mRoundedCorners = other.getRoundedCorners();
         mRoundedCornerFrame.set(other.mRoundedCornerFrame);
         mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds();
+        mDisplayShape = other.getDisplayShape();
         if (copySources) {
             for (int i = 0; i < SIZE; i++) {
                 InsetsSource source = other.mSources[i];
@@ -675,6 +699,7 @@
         mRoundedCorners = other.getRoundedCorners();
         mRoundedCornerFrame.set(other.mRoundedCornerFrame);
         mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds();
+        mDisplayShape = other.getDisplayShape();
         final ArraySet<Integer> t = toInternalType(types);
         for (int i = t.size() - 1; i >= 0; i--) {
             final int type = t.valueAt(i);
@@ -807,6 +832,7 @@
         pw.println(newPrefix + "mRoundedCorners=" + mRoundedCorners);
         pw.println(newPrefix + "mRoundedCornerFrame=" + mRoundedCornerFrame);
         pw.println(newPrefix + "mPrivacyIndicatorBounds=" + mPrivacyIndicatorBounds);
+        pw.println(newPrefix + "mDisplayShape=" + mDisplayShape);
         for (int i = 0; i < SIZE; i++) {
             InsetsSource source = mSources[i];
             if (source == null) continue;
@@ -911,7 +937,8 @@
                 || !mDisplayCutout.equals(state.mDisplayCutout)
                 || !mRoundedCorners.equals(state.mRoundedCorners)
                 || !mRoundedCornerFrame.equals(state.mRoundedCornerFrame)
-                || !mPrivacyIndicatorBounds.equals(state.mPrivacyIndicatorBounds)) {
+                || !mPrivacyIndicatorBounds.equals(state.mPrivacyIndicatorBounds)
+                || !mDisplayShape.equals(state.mDisplayShape)) {
             return false;
         }
         for (int i = 0; i < SIZE; i++) {
@@ -941,7 +968,7 @@
     @Override
     public int hashCode() {
         return Objects.hash(mDisplayFrame, mDisplayCutout, Arrays.hashCode(mSources),
-                mRoundedCorners, mPrivacyIndicatorBounds, mRoundedCornerFrame);
+                mRoundedCorners, mPrivacyIndicatorBounds, mRoundedCornerFrame, mDisplayShape);
     }
 
     public InsetsState(Parcel in) {
@@ -961,6 +988,7 @@
         dest.writeTypedObject(mRoundedCorners, flags);
         mRoundedCornerFrame.writeToParcel(dest, flags);
         dest.writeTypedObject(mPrivacyIndicatorBounds, flags);
+        dest.writeTypedObject(mDisplayShape, flags);
     }
 
     public static final @NonNull Creator<InsetsState> CREATOR = new Creator<InsetsState>() {
@@ -981,6 +1009,7 @@
         mRoundedCorners = in.readTypedObject(RoundedCorners.CREATOR);
         mRoundedCornerFrame.readFromParcel(in);
         mPrivacyIndicatorBounds = in.readTypedObject(PrivacyIndicatorBounds.CREATOR);
+        mDisplayShape = in.readTypedObject(DisplayShape.CREATOR);
     }
 
     @Override
@@ -998,6 +1027,7 @@
                 + ", mRoundedCorners=" + mRoundedCorners
                 + "  mRoundedCornerFrame=" + mRoundedCornerFrame
                 + ", mPrivacyIndicatorBounds=" + mPrivacyIndicatorBounds
+                + ", mDisplayShape=" + mDisplayShape
                 + ", mSources= { " + joiner
                 + " }";
     }
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 57b2d39..33ea92d 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -947,108 +947,112 @@
                     + " left=" + (mWindowSpaceLeft != mLocation[0])
                     + " top=" + (mWindowSpaceTop != mLocation[1]));
 
-            mVisible = mRequestedVisible;
-            mWindowSpaceLeft = mLocation[0];
-            mWindowSpaceTop = mLocation[1];
-            mSurfaceWidth = myWidth;
-            mSurfaceHeight = myHeight;
-            mFormat = mRequestedFormat;
-            mAlpha = alpha;
-            mLastWindowVisibility = mWindowVisibility;
-            mTransformHint = viewRoot.getBufferTransformHint();
-            mSubLayer = mRequestedSubLayer;
-
-            mScreenRect.left = mWindowSpaceLeft;
-            mScreenRect.top = mWindowSpaceTop;
-            mScreenRect.right = mWindowSpaceLeft + getWidth();
-            mScreenRect.bottom = mWindowSpaceTop + getHeight();
-            if (translator != null) {
-                translator.translateRectInAppWindowToScreen(mScreenRect);
-            }
-
-            final Rect surfaceInsets = viewRoot.mWindowAttributes.surfaceInsets;
-            mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
-            // Collect all geometry changes and apply these changes on the RenderThread worker
-            // via the RenderNode.PositionUpdateListener.
-            final Transaction surfaceUpdateTransaction = new Transaction();
-            if (creating) {
-                updateOpaqueFlag();
-                final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
-                createBlastSurfaceControls(viewRoot, name, surfaceUpdateTransaction);
-            } else if (mSurfaceControl == null) {
-                return;
-            }
-
-            final boolean redrawNeeded = sizeChanged || creating || hintChanged
-                    || (mVisible && !mDrawFinished) || alphaChanged || relativeZChanged;
-            boolean shouldSyncBuffer =
-                    redrawNeeded && viewRoot.wasRelayoutRequested() && viewRoot.isInLocalSync();
-            SyncBufferTransactionCallback syncBufferTransactionCallback = null;
-            if (shouldSyncBuffer) {
-                syncBufferTransactionCallback = new SyncBufferTransactionCallback();
-                mBlastBufferQueue.syncNextTransaction(
-                        false /* acquireSingleBuffer */,
-                        syncBufferTransactionCallback::onTransactionReady);
-            }
-
-            final boolean realSizeChanged = performSurfaceTransaction(viewRoot, translator,
-                    creating, sizeChanged, hintChanged, relativeZChanged,
-                    surfaceUpdateTransaction);
-
             try {
-                SurfaceHolder.Callback[] callbacks = null;
+                mVisible = mRequestedVisible;
+                mWindowSpaceLeft = mLocation[0];
+                mWindowSpaceTop = mLocation[1];
+                mSurfaceWidth = myWidth;
+                mSurfaceHeight = myHeight;
+                mFormat = mRequestedFormat;
+                mAlpha = alpha;
+                mLastWindowVisibility = mWindowVisibility;
+                mTransformHint = viewRoot.getBufferTransformHint();
+                mSubLayer = mRequestedSubLayer;
 
-                final boolean surfaceChanged = creating;
-                if (mSurfaceCreated && (surfaceChanged || (!mVisible && visibleChanged))) {
-                    mSurfaceCreated = false;
-                    notifySurfaceDestroyed();
+                mScreenRect.left = mWindowSpaceLeft;
+                mScreenRect.top = mWindowSpaceTop;
+                mScreenRect.right = mWindowSpaceLeft + getWidth();
+                mScreenRect.bottom = mWindowSpaceTop + getHeight();
+                if (translator != null) {
+                    translator.translateRectInAppWindowToScreen(mScreenRect);
                 }
 
-                copySurface(creating /* surfaceControlCreated */, sizeChanged);
+                final Rect surfaceInsets = viewRoot.mWindowAttributes.surfaceInsets;
+                mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
+                // Collect all geometry changes and apply these changes on the RenderThread worker
+                // via the RenderNode.PositionUpdateListener.
+                final Transaction surfaceUpdateTransaction = new Transaction();
+                if (creating) {
+                    updateOpaqueFlag();
+                    final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
+                    createBlastSurfaceControls(viewRoot, name, surfaceUpdateTransaction);
+                } else if (mSurfaceControl == null) {
+                    return;
+                }
 
-                if (mVisible && mSurface.isValid()) {
-                    if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
-                        mSurfaceCreated = true;
-                        mIsCreating = true;
-                        if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
-                                + "visibleChanged -- surfaceCreated");
-                        callbacks = getSurfaceCallbacks();
-                        for (SurfaceHolder.Callback c : callbacks) {
-                            c.surfaceCreated(mSurfaceHolder);
-                        }
+                final boolean redrawNeeded = sizeChanged || creating || hintChanged
+                        || (mVisible && !mDrawFinished) || alphaChanged || relativeZChanged;
+                boolean shouldSyncBuffer =
+                        redrawNeeded && viewRoot.wasRelayoutRequested() && viewRoot.isInLocalSync();
+                SyncBufferTransactionCallback syncBufferTransactionCallback = null;
+                if (shouldSyncBuffer) {
+                    syncBufferTransactionCallback = new SyncBufferTransactionCallback();
+                    mBlastBufferQueue.syncNextTransaction(
+                            false /* acquireSingleBuffer */,
+                            syncBufferTransactionCallback::onTransactionReady);
+                }
+
+                final boolean realSizeChanged = performSurfaceTransaction(viewRoot, translator,
+                        creating, sizeChanged, hintChanged, relativeZChanged,
+                        surfaceUpdateTransaction);
+
+                try {
+                    SurfaceHolder.Callback[] callbacks = null;
+
+                    final boolean surfaceChanged = creating;
+                    if (mSurfaceCreated && (surfaceChanged || (!mVisible && visibleChanged))) {
+                        mSurfaceCreated = false;
+                        notifySurfaceDestroyed();
                     }
-                    if (creating || formatChanged || sizeChanged || hintChanged
-                            || visibleChanged || realSizeChanged) {
-                        if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
-                                + "surfaceChanged -- format=" + mFormat
-                                + " w=" + myWidth + " h=" + myHeight);
-                        if (callbacks == null) {
+
+                    copySurface(creating /* surfaceControlCreated */, sizeChanged);
+
+                    if (mVisible && mSurface.isValid()) {
+                        if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
+                            mSurfaceCreated = true;
+                            mIsCreating = true;
+                            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+                                    + "visibleChanged -- surfaceCreated");
                             callbacks = getSurfaceCallbacks();
+                            for (SurfaceHolder.Callback c : callbacks) {
+                                c.surfaceCreated(mSurfaceHolder);
+                            }
                         }
-                        for (SurfaceHolder.Callback c : callbacks) {
-                            c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
+                        if (creating || formatChanged || sizeChanged || hintChanged
+                                || visibleChanged || realSizeChanged) {
+                            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+                                    + "surfaceChanged -- format=" + mFormat
+                                    + " w=" + myWidth + " h=" + myHeight);
+                            if (callbacks == null) {
+                                callbacks = getSurfaceCallbacks();
+                            }
+                            for (SurfaceHolder.Callback c : callbacks) {
+                                c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
+                            }
                         }
-                    }
-                    if (redrawNeeded) {
-                        if (DEBUG) {
-                            Log.i(TAG, System.identityHashCode(this) + " surfaceRedrawNeeded");
-                        }
-                        if (callbacks == null) {
-                            callbacks = getSurfaceCallbacks();
-                        }
+                        if (redrawNeeded) {
+                            if (DEBUG) {
+                                Log.i(TAG, System.identityHashCode(this) + " surfaceRedrawNeeded");
+                            }
+                            if (callbacks == null) {
+                                callbacks = getSurfaceCallbacks();
+                            }
 
-                        if (shouldSyncBuffer) {
-                            handleSyncBufferCallback(callbacks, syncBufferTransactionCallback);
-                        } else {
-                            handleSyncNoBuffer(callbacks);
+                            if (shouldSyncBuffer) {
+                                handleSyncBufferCallback(callbacks, syncBufferTransactionCallback);
+                            } else {
+                                handleSyncNoBuffer(callbacks);
+                            }
                         }
                     }
+                } finally {
+                    mIsCreating = false;
+                    if (mSurfaceControl != null && !mSurfaceCreated) {
+                        releaseSurfaces(false /* releaseSurfacePackage*/);
+                    }
                 }
-            } finally {
-                mIsCreating = false;
-                if (mSurfaceControl != null && !mSurfaceCreated) {
-                    releaseSurfaces(false /* releaseSurfacePackage*/);
-                }
+            } catch (Exception ex) {
+                Log.e(TAG, "Exception configuring surface", ex);
             }
             if (DEBUG) Log.v(
                 TAG, "Layout: x=" + mScreenRect.left + " y=" + mScreenRect.top
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 03b25c2..8de15c1 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -42,7 +42,6 @@
 import android.content.Intent;
 import android.graphics.Insets;
 import android.graphics.Rect;
-import android.util.SparseArray;
 import android.view.View.OnApplyWindowInsetsListener;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.inputmethod.EditorInfo;
@@ -83,6 +82,7 @@
     @Nullable private final DisplayCutout mDisplayCutout;
     @Nullable private final RoundedCorners mRoundedCorners;
     @Nullable private final PrivacyIndicatorBounds mPrivacyIndicatorBounds;
+    @Nullable private final DisplayShape mDisplayShape;
 
     /**
      * In multi-window we force show the navigation bar. Because we don't want that the surface size
@@ -115,24 +115,9 @@
     public static final @NonNull WindowInsets CONSUMED;
 
     static {
-        CONSUMED = new WindowInsets((Rect) null, null, false, false, null);
-    }
-
-    /**
-     * Construct a new WindowInsets from individual insets.
-     *
-     * A {@code null} inset indicates that the respective inset is consumed.
-     *
-     * @hide
-     * @deprecated Use {@link WindowInsets(SparseArray, SparseArray, boolean, boolean, DisplayCutout)}
-     */
-    @Deprecated
-    public WindowInsets(Rect systemWindowInsetsRect, Rect stableInsetsRect, boolean isRound,
-            boolean alwaysConsumeSystemBars, DisplayCutout displayCutout) {
-        this(createCompatTypeMap(systemWindowInsetsRect), createCompatTypeMap(stableInsetsRect),
-                createCompatVisibilityMap(createCompatTypeMap(systemWindowInsetsRect)),
-                isRound, alwaysConsumeSystemBars, displayCutout, null, null,
-                systemBars(), false /* compatIgnoreVisibility */);
+        CONSUMED = new WindowInsets(createCompatTypeMap(null), createCompatTypeMap(null),
+                createCompatVisibilityMap(createCompatTypeMap(null)), false, false, null, null,
+                null, null, systemBars(), false);
     }
 
     /**
@@ -154,6 +139,7 @@
             boolean alwaysConsumeSystemBars, DisplayCutout displayCutout,
             RoundedCorners roundedCorners,
             PrivacyIndicatorBounds privacyIndicatorBounds,
+            DisplayShape displayShape,
             @InsetsType int compatInsetsTypes, boolean compatIgnoreVisibility) {
         mSystemWindowInsetsConsumed = typeInsetsMap == null;
         mTypeInsetsMap = mSystemWindowInsetsConsumed
@@ -177,6 +163,7 @@
 
         mRoundedCorners = roundedCorners;
         mPrivacyIndicatorBounds = privacyIndicatorBounds;
+        mDisplayShape = displayShape;
     }
 
     /**
@@ -191,6 +178,7 @@
                 src.mAlwaysConsumeSystemBars, displayCutoutCopyConstructorArgument(src),
                 src.mRoundedCorners,
                 src.mPrivacyIndicatorBounds,
+                src.mDisplayShape,
                 src.mCompatInsetsTypes,
                 src.mCompatIgnoreVisibility);
     }
@@ -244,15 +232,18 @@
     @UnsupportedAppUsage
     public WindowInsets(Rect systemWindowInsets) {
         this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, null,
-                null, null, systemBars(), false /* compatIgnoreVisibility */);
+                null, null, null, systemBars(), false /* compatIgnoreVisibility */);
     }
 
     /**
      * Creates a indexOf(type) -> inset map for which the {@code insets} is just mapped to
      * {@link Type#statusBars()} and {@link Type#navigationBars()}, depending on the
      * location of the inset.
+     *
+     * @hide
      */
-    private static Insets[] createCompatTypeMap(@Nullable Rect insets) {
+    @VisibleForTesting
+    public static Insets[] createCompatTypeMap(@Nullable Rect insets) {
         if (insets == null) {
             return null;
         }
@@ -271,6 +262,10 @@
                 Insets.of(insets.left, 0, insets.right, insets.bottom);
     }
 
+    /**
+     * @hide
+     */
+    @VisibleForTesting
     private static boolean[] createCompatVisibilityMap(@Nullable Insets[] typeInsetsMap) {
         boolean[] typeVisibilityMap = new boolean[SIZE];
         if (typeInsetsMap == null) {
@@ -533,6 +528,17 @@
     }
 
     /**
+     * Returns the display shape in the coordinate space of the window.
+     *
+     * @return the display shape
+     * @see DisplayShape
+     */
+    @Nullable
+    public DisplayShape getDisplayShape() {
+        return mDisplayShape;
+    }
+
+    /**
      * Returns a copy of this WindowInsets with the cutout fully consumed.
      *
      * @return A modified copy of this WindowInsets
@@ -547,7 +553,7 @@
                 mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
                 mTypeVisibilityMap,
                 mIsRound, mAlwaysConsumeSystemBars,
-                null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds,
+                null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape,
                 mCompatInsetsTypes, mCompatIgnoreVisibility);
     }
 
@@ -602,7 +608,7 @@
                 // it.
                 (mCompatInsetsTypes & displayCutout()) != 0
                         ? null : displayCutoutCopyConstructorArgument(this),
-                mRoundedCorners, mPrivacyIndicatorBounds, mCompatInsetsTypes,
+                mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape, mCompatInsetsTypes,
                 mCompatIgnoreVisibility);
     }
 
@@ -911,6 +917,8 @@
         result.append(mPrivacyIndicatorBounds != null ? "privacyIndicatorBounds="
                 + mPrivacyIndicatorBounds : "");
         result.append("\n    ");
+        result.append(mDisplayShape != null ? "displayShape=" + mDisplayShape : "");
+        result.append("\n    ");
         result.append("compatInsetsTypes=" + mCompatInsetsTypes);
         result.append("\n    ");
         result.append("compatIgnoreVisibility=" + mCompatIgnoreVisibility);
@@ -1018,6 +1026,7 @@
                 mPrivacyIndicatorBounds == null
                         ? null
                         : mPrivacyIndicatorBounds.inset(left, top, right, bottom),
+                mDisplayShape,
                 mCompatInsetsTypes, mCompatIgnoreVisibility);
     }
 
@@ -1037,7 +1046,8 @@
                 && Arrays.equals(mTypeVisibilityMap, that.mTypeVisibilityMap)
                 && Objects.equals(mDisplayCutout, that.mDisplayCutout)
                 && Objects.equals(mRoundedCorners, that.mRoundedCorners)
-                && Objects.equals(mPrivacyIndicatorBounds, that.mPrivacyIndicatorBounds);
+                && Objects.equals(mPrivacyIndicatorBounds, that.mPrivacyIndicatorBounds)
+                && Objects.equals(mDisplayShape, that.mDisplayShape);
     }
 
     @Override
@@ -1045,7 +1055,7 @@
         return Objects.hash(Arrays.hashCode(mTypeInsetsMap), Arrays.hashCode(mTypeMaxInsetsMap),
                 Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, mRoundedCorners,
                 mAlwaysConsumeSystemBars, mSystemWindowInsetsConsumed, mStableInsetsConsumed,
-                mDisplayCutoutConsumed, mPrivacyIndicatorBounds);
+                mDisplayCutoutConsumed, mPrivacyIndicatorBounds, mDisplayShape);
     }
 
 
@@ -1106,6 +1116,7 @@
 
         private DisplayCutout mDisplayCutout;
         private RoundedCorners mRoundedCorners = RoundedCorners.NO_ROUNDED_CORNERS;
+        private DisplayShape mDisplayShape = DisplayShape.NONE;
 
         private boolean mIsRound;
         private boolean mAlwaysConsumeSystemBars;
@@ -1137,6 +1148,7 @@
             mIsRound = insets.mIsRound;
             mAlwaysConsumeSystemBars = insets.mAlwaysConsumeSystemBars;
             mPrivacyIndicatorBounds = insets.mPrivacyIndicatorBounds;
+            mDisplayShape = insets.mDisplayShape;
         }
 
         /**
@@ -1381,6 +1393,19 @@
             return this;
         }
 
+        /**
+         * Sets the display shape.
+         *
+         * @see #getDisplayShape().
+         * @param displayShape the display shape.
+         * @return itself.
+         */
+        @NonNull
+        public Builder setDisplayShape(@NonNull DisplayShape displayShape) {
+            mDisplayShape = displayShape;
+            return this;
+        }
+
         /** @hide */
         @NonNull
         public Builder setRound(boolean round) {
@@ -1405,7 +1430,8 @@
             return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap,
                     mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap,
                     mIsRound, mAlwaysConsumeSystemBars, mDisplayCutout, mRoundedCorners,
-                    mPrivacyIndicatorBounds, systemBars(), false /* compatIgnoreVisibility */);
+                    mPrivacyIndicatorBounds, mDisplayShape, systemBars(),
+                    false /* compatIgnoreVisibility */);
         }
     }
 
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 6dc9011..5c4305c 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -366,7 +366,7 @@
         List<DisplayInfo> possibleDisplayInfos;
         try {
             possibleDisplayInfos = WindowManagerGlobal.getWindowManagerService()
-                    .getPossibleDisplayInfo(displayId, mContext.getPackageName());
+                    .getPossibleDisplayInfo(displayId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index c2da638..a35e13e 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -421,6 +421,53 @@
         return false;
     }
 
+    /**
+     * Releases temporary-for-animation surfaces referenced by this to potentially free up memory.
+     * This includes root-leash and snapshots.
+     */
+    public void releaseAnimSurfaces() {
+        for (int i = mChanges.size() - 1; i >= 0; --i) {
+            final Change c = mChanges.get(i);
+            if (c.mSnapshot != null) {
+                c.mSnapshot.release();
+                c.mSnapshot = null;
+            }
+        }
+        if (mRootLeash != null) {
+            mRootLeash.release();
+        }
+    }
+
+    /**
+     * Releases ALL the surfaces referenced by this to potentially free up memory. Do NOT use this
+     * if the surface-controls get stored and used elsewhere in the process. To just release
+     * temporary-for-animation surfaces, use {@link #releaseAnimSurfaces}.
+     */
+    public void releaseAllSurfaces() {
+        releaseAnimSurfaces();
+        for (int i = mChanges.size() - 1; i >= 0; --i) {
+            mChanges.get(i).getLeash().release();
+        }
+    }
+
+    /**
+     * Makes a copy of this as if it were parcel'd and unparcel'd. This implies that surfacecontrol
+     * refcounts are incremented which allows the "remote" receiver to release them without breaking
+     * the caller's references. Use this only if you need to "send" this to a local function which
+     * assumes it is being called from a remote caller.
+     */
+    public TransitionInfo localRemoteCopy() {
+        final TransitionInfo out = new TransitionInfo(mType, mFlags);
+        for (int i = 0; i < mChanges.size(); ++i) {
+            out.mChanges.add(mChanges.get(i).localRemoteCopy());
+        }
+        out.mRootLeash = mRootLeash != null ? new SurfaceControl(mRootLeash, "localRemote") : null;
+        // Doesn't have any native stuff, so no need for actual copy
+        out.mOptions = mOptions;
+        out.mRootOffset.set(mRootOffset);
+        return out;
+    }
+
     /** Represents the change a WindowContainer undergoes during a transition */
     public static final class Change implements Parcelable {
         private final WindowContainerToken mContainer;
@@ -473,6 +520,27 @@
             mSnapshotLuma = in.readFloat();
         }
 
+        private Change localRemoteCopy() {
+            final Change out = new Change(mContainer, new SurfaceControl(mLeash, "localRemote"));
+            out.mParent = mParent;
+            out.mLastParent = mLastParent;
+            out.mMode = mMode;
+            out.mFlags = mFlags;
+            out.mStartAbsBounds.set(mStartAbsBounds);
+            out.mEndAbsBounds.set(mEndAbsBounds);
+            out.mEndRelOffset.set(mEndRelOffset);
+            out.mTaskInfo = mTaskInfo;
+            out.mAllowEnterPip = mAllowEnterPip;
+            out.mStartRotation = mStartRotation;
+            out.mEndRotation = mEndRotation;
+            out.mEndFixedRotation = mEndFixedRotation;
+            out.mRotationAnimation = mRotationAnimation;
+            out.mBackgroundColor = mBackgroundColor;
+            out.mSnapshot = mSnapshot != null ? new SurfaceControl(mSnapshot, "localRemote") : null;
+            out.mSnapshotLuma = mSnapshotLuma;
+            return out;
+        }
+
         /** Sets the parent of this change's container. The parent must be a participant or null. */
         public void setParent(@Nullable WindowContainerToken parent) {
             mParent = parent;
diff --git a/core/java/com/android/internal/content/om/OverlayManagerImpl.java b/core/java/com/android/internal/content/om/OverlayManagerImpl.java
index 6ceccd1..260d1a2 100644
--- a/core/java/com/android/internal/content/om/OverlayManagerImpl.java
+++ b/core/java/com/android/internal/content/om/OverlayManagerImpl.java
@@ -192,7 +192,7 @@
      * @param name the non-check overlay name
      * @return the valid overlay name
      */
-    private static String checkOverlayNameValid(@NonNull String name) {
+    public static String checkOverlayNameValid(@NonNull String name) {
         final String overlayName =
                 Preconditions.checkStringNotEmpty(
                         name, "overlayName should be neither empty nor null string");
diff --git a/core/java/com/android/internal/inputmethod/IInputMethod.aidl b/core/java/com/android/internal/inputmethod/IInputMethod.aidl
index 1e3714e..8cb568d 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethod.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethod.aidl
@@ -40,7 +40,6 @@
     parcelable InitParams {
         IBinder token;
         IInputMethodPrivilegedOperations privilegedOperations;
-        int configChanges;
         int navigationBarFlags;
     }
 
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 4b1753a..9cb2e68 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -17,7 +17,7 @@
 package com.android.internal.telephony;
 
 import android.telephony.BarringInfo;
-import android.telephony.CallAttributes;
+import android.telephony.CallState;
 import android.telephony.CellIdentity;
 import android.telephony.CellInfo;
 import android.telephony.DataConnectionRealTimeInfo;
@@ -62,7 +62,7 @@
     void onPhoneCapabilityChanged(in PhoneCapability capability);
     void onActiveDataSubIdChanged(in int subId);
     void onRadioPowerStateChanged(in int state);
-    void onCallAttributesChanged(in CallAttributes callAttributes);
+    void onCallStatesChanged(in List<CallState> callStateList);
     @SuppressWarnings(value={"untyped-collection"})
     void onEmergencyNumberListChanged(in Map emergencyNumberList);
     void onOutgoingEmergencyCall(in EmergencyNumber placedEmergencyNumber, int subscriptionId);
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index c7fa757..7ba2686 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -66,8 +66,8 @@
     void notifyCellLocationForSubscriber(in int subId, in CellIdentity cellLocation);
     @UnsupportedAppUsage
     void notifyCellInfo(in List<CellInfo> cellInfo);
-    void notifyPreciseCallState(int phoneId, int subId, int ringingCallState,
-            int foregroundCallState, int backgroundCallState);
+    void notifyPreciseCallState(int phoneId, int subId, in int[] callStates, in String[] imsCallIds,
+            in int[] imsCallServiceTypes, in int[] imsCallTypes);
     void notifyDisconnectCause(int phoneId, int subId, int disconnectCause,
             int preciseDisconnectCause);
     void notifyCellInfoForSubscriber(in int subId, in List<CellInfo> cellInfo);
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index f140e79..1bc903a 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -40,6 +40,8 @@
 
     cppflags: ["-Wno-conversion-null"],
 
+    cpp_std: "gnu++20",
+
     srcs: [
         "android_animation_PropertyValuesHolder.cpp",
         "android_os_SystemClock.cpp",
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
index a8d7231..29560dc 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -96,7 +96,7 @@
   }
 
   bool ForEachFile(const std::string& /* root_path */,
-                   const std::function<void(const StringPiece&, FileType)>& /* f */) const {
+                   const std::function<void(StringPiece, FileType)>& /* f */) const {
     return true;
   }
 
@@ -402,7 +402,7 @@
     return reinterpret_cast<jlong>(apk_assets->GetLoadedArsc()->GetStringPool());
 }
 
-static jboolean NativeIsUpToDate(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
+static jboolean NativeIsUpToDate(jlong ptr) {
     auto scoped_apk_assets = ScopedLock(ApkAssetsFromLong(ptr));
     auto apk_assets = scoped_apk_assets->get();
     return apk_assets->IsUpToDate() ? JNI_TRUE : JNI_FALSE;
@@ -500,24 +500,28 @@
 
 // JNI registration.
 static const JNINativeMethod gApkAssetsMethods[] = {
-    {"nativeLoad", "(ILjava/lang/String;ILandroid/content/res/loader/AssetsProvider;)J",
-     (void*)NativeLoad},
-    {"nativeLoadEmpty", "(ILandroid/content/res/loader/AssetsProvider;)J", (void*)NativeLoadEmpty},
-    {"nativeLoadFd",
-     "(ILjava/io/FileDescriptor;Ljava/lang/String;ILandroid/content/res/loader/AssetsProvider;)J",
-     (void*)NativeLoadFromFd},
-    {"nativeLoadFdOffsets",
-     "(ILjava/io/FileDescriptor;Ljava/lang/String;JJILandroid/content/res/loader/AssetsProvider;)J",
-     (void*)NativeLoadFromFdOffset},
-    {"nativeDestroy", "(J)V", (void*)NativeDestroy},
-    {"nativeGetAssetPath", "(J)Ljava/lang/String;", (void*)NativeGetAssetPath},
-    {"nativeGetDebugName", "(J)Ljava/lang/String;", (void*)NativeGetDebugName},
-    {"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock},
-    {"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate},
-    {"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml},
-    {"nativeGetOverlayableInfo", "(JLjava/lang/String;)Landroid/content/om/OverlayableInfo;",
-     (void*)NativeGetOverlayableInfo},
-    {"nativeDefinesOverlayable", "(J)Z", (void*)NativeDefinesOverlayable},
+        {"nativeLoad", "(ILjava/lang/String;ILandroid/content/res/loader/AssetsProvider;)J",
+         (void*)NativeLoad},
+        {"nativeLoadEmpty", "(ILandroid/content/res/loader/AssetsProvider;)J",
+         (void*)NativeLoadEmpty},
+        {"nativeLoadFd",
+         "(ILjava/io/FileDescriptor;Ljava/lang/String;ILandroid/content/res/loader/"
+         "AssetsProvider;)J",
+         (void*)NativeLoadFromFd},
+        {"nativeLoadFdOffsets",
+         "(ILjava/io/FileDescriptor;Ljava/lang/String;JJILandroid/content/res/loader/"
+         "AssetsProvider;)J",
+         (void*)NativeLoadFromFdOffset},
+        {"nativeDestroy", "(J)V", (void*)NativeDestroy},
+        {"nativeGetAssetPath", "(J)Ljava/lang/String;", (void*)NativeGetAssetPath},
+        {"nativeGetDebugName", "(J)Ljava/lang/String;", (void*)NativeGetDebugName},
+        {"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock},
+        // @CriticalNative
+        {"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate},
+        {"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml},
+        {"nativeGetOverlayableInfo", "(JLjava/lang/String;)Landroid/content/om/OverlayableInfo;",
+         (void*)NativeGetOverlayableInfo},
+        {"nativeDefinesOverlayable", "(J)Z", (void*)NativeDefinesOverlayable},
 };
 
 int register_android_content_res_ApkAssets(JNIEnv* env) {
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index cb97698..939a0e4 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -243,6 +243,23 @@
     }
 }
 
+static void nativeGetRuntimeSensors(JNIEnv *env, jclass clazz, jlong sensorManager, jint deviceId,
+                                    jobject sensorList) {
+    SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager);
+    const ListOffsets &listOffsets(gListOffsets);
+
+    Vector<Sensor> nativeList;
+
+    mgr->getRuntimeSensorList(deviceId, nativeList);
+
+    ALOGI("DYNS native SensorManager.getRuntimeSensorList return %zu sensors", nativeList.size());
+    for (size_t i = 0; i < nativeList.size(); ++i) {
+        jobject sensor = translateNativeSensorToJavaSensor(env, NULL, nativeList[i]);
+        // add to list
+        env->CallBooleanMethod(sensorList, listOffsets.add, sensor);
+    }
+}
+
 static jboolean nativeIsDataInjectionEnabled(JNIEnv *_env, jclass _this, jlong sensorManager) {
     SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
     return mgr->isDataInjectionEnabled();
@@ -503,40 +520,26 @@
 //----------------------------------------------------------------------------
 
 static const JNINativeMethod gSystemSensorManagerMethods[] = {
-    {"nativeClassInit",
-            "()V",
-            (void*)nativeClassInit },
-    {"nativeCreate",
-             "(Ljava/lang/String;)J",
-             (void*)nativeCreate },
+        {"nativeClassInit", "()V", (void *)nativeClassInit},
+        {"nativeCreate", "(Ljava/lang/String;)J", (void *)nativeCreate},
 
-    {"nativeGetSensorAtIndex",
-            "(JLandroid/hardware/Sensor;I)Z",
-            (void*)nativeGetSensorAtIndex },
+        {"nativeGetSensorAtIndex", "(JLandroid/hardware/Sensor;I)Z",
+         (void *)nativeGetSensorAtIndex},
 
-    {"nativeGetDynamicSensors",
-            "(JLjava/util/List;)V",
-            (void*)nativeGetDynamicSensors },
+        {"nativeGetDynamicSensors", "(JLjava/util/List;)V", (void *)nativeGetDynamicSensors},
 
-    {"nativeIsDataInjectionEnabled",
-            "(J)Z",
-            (void*)nativeIsDataInjectionEnabled },
+        {"nativeGetRuntimeSensors", "(JILjava/util/List;)V", (void *)nativeGetRuntimeSensors},
 
-    {"nativeCreateDirectChannel",
-            "(JJIILandroid/hardware/HardwareBuffer;)I",
-            (void*)nativeCreateDirectChannel },
+        {"nativeIsDataInjectionEnabled", "(J)Z", (void *)nativeIsDataInjectionEnabled},
 
-    {"nativeDestroyDirectChannel",
-            "(JI)V",
-            (void*)nativeDestroyDirectChannel },
+        {"nativeCreateDirectChannel", "(JJIILandroid/hardware/HardwareBuffer;)I",
+         (void *)nativeCreateDirectChannel},
 
-    {"nativeConfigDirectChannel",
-            "(JIII)I",
-            (void*)nativeConfigDirectChannel },
+        {"nativeDestroyDirectChannel", "(JI)V", (void *)nativeDestroyDirectChannel},
 
-    {"nativeSetOperationParameter",
-            "(JII[F[I)I",
-            (void*)nativeSetOperationParameter },
+        {"nativeConfigDirectChannel", "(JIII)I", (void *)nativeConfigDirectChannel},
+
+        {"nativeSetOperationParameter", "(JII[F[I)I", (void *)nativeSetOperationParameter},
 };
 
 static const JNINativeMethod gBaseEventQueueMethods[] = {
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 4650000..7393c6f 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -977,12 +977,11 @@
         optional int32 profile = 2;
     }
     repeated UserProfile user_profile_group_ids = 4;
-    repeated int32 visible_users_array = 5;
 
     // current_user contains the id of the current user, while current_profiles contains the ids of
     // both the current user and its profiles (if any)
-    optional int32 current_user = 6;
-    repeated int32 current_profiles = 7;
+    optional int32 current_user = 5;
+    repeated int32 current_profiles = 6;
 }
 
 // sync with com.android.server.am.AppTimeTracker.java
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ecc3979..ad8f7fb 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6287,12 +6287,12 @@
 
     <!-- Allows a regular application to use {@link android.app.Service#startForeground
          Service.startForeground} with the type "specialUse".
-         <p>Protection level: signature|appop|instant
+         <p>Protection level: normal|appop|instant
     -->
     <permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"
         android:description="@string/permdesc_foregroundServiceSpecialUse"
         android:label="@string/permlab_foregroundServiceSpecialUse"
-        android:protectionLevel="signature|appop|instant" />
+        android:protectionLevel="normal|appop|instant" />
 
     <!-- @SystemApi Allows to access all app shortcuts.
          @hide -->
@@ -7291,6 +7291,10 @@
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
 
+        <service android:name="com.android.server.pm.GentleUpdateHelper$Service"
+            android:permission="android.permission.BIND_JOB_SERVICE" >
+        </service>
+
         <service
                 android:name="com.android.server.autofill.AutofillCompatAccessibilityService"
                 android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 2d832bc..7946493 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -8955,6 +8955,9 @@
         <!-- Flag indicating whether a recognition service can be selected as default. The default
              value of this flag is true. -->
         <attr name="selectableAsDefault" format="boolean" />
+        <!-- The maximal number of recognition sessions ongoing at the same time.
+             The default value is 1, meaning no concurrency. -->
+        <attr name="maxConcurrentSessionsCount" format="integer" />
     </declare-styleable>
 
     <!-- Use <code>voice-interaction-service</code> as the root tag of the XML resource that
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 6460007..eb70344 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1705,8 +1705,7 @@
         -->
         <flag name="systemExempted" value="0x400" />
         <!-- "Short service" foreground service type. See
-           TODO: Change it to a real link
-           {@code android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}.
+           {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}.
            for more details.
         -->
         <flag name="shortService" value="0x800" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ccce9ba..2ab5b75 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1828,6 +1828,14 @@
          config_enableFusedLocationOverlay is false. -->
     <string name="config_fusedLocationProviderPackageName" translatable="false">com.android.location.fused</string>
 
+    <!-- If true will use the GNSS hardware implementation to service the GPS_PROVIDER. If false
+         will allow the GPS_PROVIDER to be replaced by an app at run-time (restricted to the package
+         specified by config_gnssLocationProviderPackageName). -->
+    <bool name="config_useGnssHardwareProvider" translatable="false">true</bool>
+    <!-- Package name providing GNSS location support. Used only when
+         config_useGnssHardwareProvider is false. -->
+    <string name="config_gnssLocationProviderPackageName" translatable="false">@null</string>
+
     <!-- Default value for the ADAS GNSS Location Enabled setting if this setting has never been
          set before. -->
     <bool name="config_defaultAdasGnssLocationEnabled" translatable="false">false</bool>
@@ -5977,4 +5985,35 @@
     <string-array translatable="false" name="config_fontManagerServiceCerts">
     </string-array>
 
+    <!-- A string config in svg path format for the main display shape.
+         (@see https://www.w3.org/TR/SVG/paths.html#PathData).
+
+         This config must be set unless:
+         1. {@link Configuration#isScreenRound} is true which means the display shape is circular
+            and the system will auto-generate a circular shape.
+         2. The display has no rounded corner and the system will auto-generate a rectangular shape.
+         (@see DisplayShape#createDefaultDisplayShape)
+
+         Note: If the display supports multiple resolutions, please define the path config based on
+         the highest resolution so that it can be scaled correctly in each resolution. -->
+    <string name="config_mainDisplayShape" translatable="false"></string>
+
+    <!-- A string config in svg path format for the secondary display shape.
+         (@see https://www.w3.org/TR/SVG/paths.html#PathData).
+
+         This config must be set unless:
+         1. {@link Configuration#isScreenRound} is true which means the display shape is circular
+            and the system will auto-generate a circular shape.
+         2. The display has no rounded corner and the system will auto-generate a rectangular shape.
+         (@see DisplayShape#createDefaultDisplayShape)
+
+         Note: If the display supports multiple resolutions, please define the path config based on
+         the highest resolution so that it can be scaled correctly in each resolution. -->
+    <string name="config_secondaryDisplayShape" translatable="false"></string>
+
+    <!-- The display shape config for each display in a multi-display device. -->
+    <string-array name="config_displayShapeArray" translatable="false">
+        <item>@string/config_mainDisplayShape</item>
+        <item>@string/config_secondaryDisplayShape</item>
+    </string-array>
 </resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 61229cb..bc5878a 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -117,6 +117,7 @@
     <public name="accessibilityDataPrivate" />
     <public name="enableTextStylingShortcuts" />
     <public name="targetDisplayCategory"/>
+    <public name="maxConcurrentSessionsCount" />
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01cd0000">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ae033ca..168806a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1963,6 +1963,7 @@
   <java-symbol type="bool" name="config_enableActivityRecognitionHardwareOverlay" />
   <java-symbol type="bool" name="config_defaultAdasGnssLocationEnabled" />
   <java-symbol type="bool" name="config_enableFusedLocationOverlay" />
+  <java-symbol type="bool" name="config_useGnssHardwareProvider" />
   <java-symbol type="bool" name="config_enableGeocoderOverlay" />
   <java-symbol type="bool" name="config_enableGeofenceOverlay" />
   <java-symbol type="bool" name="config_enableNetworkLocationOverlay" />
@@ -2125,6 +2126,7 @@
   <java-symbol type="string" name="config_datause_iface" />
   <java-symbol type="string" name="config_activityRecognitionHardwarePackageName" />
   <java-symbol type="string" name="config_fusedLocationProviderPackageName" />
+  <java-symbol type="string" name="config_gnssLocationProviderPackageName" />
   <java-symbol type="string" name="config_geocoderProviderPackageName" />
   <java-symbol type="string" name="config_geofenceProviderPackageName" />
   <java-symbol type="string" name="config_networkLocationProviderPackageName" />
@@ -4913,4 +4915,8 @@
   <java-symbol type="dimen" name="status_bar_height_default" />
 
   <java-symbol type="string" name="default_card_name"/>
+
+  <java-symbol type="string" name="config_mainDisplayShape"/>
+  <java-symbol type="string" name="config_secondaryDisplayShape"/>
+  <java-symbol type="array" name="config_displayShapeArray" />
 </resources>
diff --git a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java
new file mode 100644
index 0000000..11afd04
--- /dev/null
+++ b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 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.companion.virtual.sensor;
+
+import static android.hardware.Sensor.TYPE_ACCELEROMETER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.os.Parcel;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.BackgroundThread;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.time.Duration;
+
+@RunWith(AndroidJUnit4.class)
+public class VirtualSensorConfigTest {
+
+    private static final String SENSOR_NAME = "VirtualSensorName";
+    private static final String SENSOR_VENDOR = "VirtualSensorVendor";
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private VirtualSensor.SensorStateChangeCallback mSensorCallback;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void parcelAndUnparcel_matches() {
+        final VirtualSensorConfig originalConfig =
+                new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
+                        .setVendor(SENSOR_VENDOR)
+                        .setStateChangeCallback(BackgroundThread.getExecutor(), mSensorCallback)
+                        .build();
+        final Parcel parcel = Parcel.obtain();
+        originalConfig.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+        final VirtualSensorConfig recreatedConfig =
+                VirtualSensorConfig.CREATOR.createFromParcel(parcel);
+        assertThat(recreatedConfig.getType()).isEqualTo(originalConfig.getType());
+        assertThat(recreatedConfig.getName()).isEqualTo(originalConfig.getName());
+        assertThat(recreatedConfig.getVendor()).isEqualTo(originalConfig.getVendor());
+        assertThat(recreatedConfig.getStateChangeCallback()).isNotNull();
+    }
+
+    @Test
+    public void sensorConfig_onlyRequiredFields() {
+        final VirtualSensorConfig config =
+                new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME).build();
+        assertThat(config.getVendor()).isNull();
+        assertThat(config.getStateChangeCallback()).isNull();
+    }
+
+    @Test
+    public void sensorConfig_sensorCallbackInvocation() throws Exception {
+        final VirtualSensorConfig config =
+                new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
+                        .setStateChangeCallback(BackgroundThread.getExecutor(), mSensorCallback)
+                        .build();
+
+        final Duration samplingPeriod = Duration.ofMillis(123);
+        final Duration batchLatency = Duration.ofMillis(456);
+
+        config.getStateChangeCallback().onStateChanged(true,
+                (int) MILLISECONDS.toMicros(samplingPeriod.toMillis()),
+                (int) MILLISECONDS.toMicros(batchLatency.toMillis()));
+
+        verify(mSensorCallback, timeout(1000)).onStateChanged(true, samplingPeriod, batchLatency);
+    }
+}
diff --git a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorEventTest.java b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorEventTest.java
new file mode 100644
index 0000000..a9583fd
--- /dev/null
+++ b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorEventTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 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.companion.virtual.sensor;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.os.Parcel;
+import android.os.SystemClock;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class VirtualSensorEventTest {
+
+    private static final long TIMESTAMP_NANOS = SystemClock.elapsedRealtimeNanos();
+    private static final float[] SENSOR_VALUES = new float[] {1.2f, 3.4f, 5.6f};
+
+    @Test
+    public void parcelAndUnparcel_matches() {
+        final VirtualSensorEvent originalEvent = new VirtualSensorEvent.Builder(SENSOR_VALUES)
+                .setTimestampNanos(TIMESTAMP_NANOS)
+                .build();
+        final Parcel parcel = Parcel.obtain();
+        originalEvent.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+        final VirtualSensorEvent recreatedEvent =
+                VirtualSensorEvent.CREATOR.createFromParcel(parcel);
+        assertThat(recreatedEvent.getValues()).isEqualTo(originalEvent.getValues());
+        assertThat(recreatedEvent.getTimestampNanos()).isEqualTo(originalEvent.getTimestampNanos());
+    }
+
+    @Test
+    public void sensorEvent_nullValues() {
+        assertThrows(
+                IllegalArgumentException.class, () -> new VirtualSensorEvent.Builder(null).build());
+    }
+
+    @Test
+    public void sensorEvent_noValues() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> new VirtualSensorEvent.Builder(new float[0]).build());
+    }
+
+    @Test
+    public void sensorEvent_noTimestamp_usesCurrentTime() {
+        final VirtualSensorEvent event = new VirtualSensorEvent.Builder(SENSOR_VALUES).build();
+        assertThat(event.getValues()).isEqualTo(SENSOR_VALUES);
+        assertThat(TIMESTAMP_NANOS).isLessThan(event.getTimestampNanos());
+        assertThat(event.getTimestampNanos()).isLessThan(SystemClock.elapsedRealtimeNanos());
+    }
+
+    @Test
+    public void sensorEvent_created() {
+        final VirtualSensorEvent event = new VirtualSensorEvent.Builder(SENSOR_VALUES)
+                .setTimestampNanos(TIMESTAMP_NANOS)
+                .build();
+        assertThat(event.getTimestampNanos()).isEqualTo(TIMESTAMP_NANOS);
+        assertThat(event.getValues()).isEqualTo(SENSOR_VALUES);
+    }
+}
diff --git a/core/tests/coretests/src/android/graphics/TypefaceTest.java b/core/tests/coretests/src/android/graphics/TypefaceTest.java
index a528c19..6bf8f56 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceTest.java
@@ -203,16 +203,22 @@
                 fallbackMap);
         SharedMemory sharedMemory = Typeface.serializeFontMap(systemFontMap);
         Map<String, Typeface> copiedFontMap = new ArrayMap<>();
-        Typeface.deserializeFontMap(sharedMemory.mapReadOnly().order(ByteOrder.BIG_ENDIAN),
-                copiedFontMap);
-        assertEquals(systemFontMap.size(), copiedFontMap.size());
-        for (String key : systemFontMap.keySet()) {
-            assertTrue(copiedFontMap.containsKey(key));
-            Typeface original = systemFontMap.get(key);
-            Typeface copied = copiedFontMap.get(key);
-            assertEquals(original.getStyle(), copied.getStyle());
-            assertEquals(original.getWeight(), copied.getWeight());
-            assertEquals(measureText(original, "hello"), measureText(copied, "hello"), 1e-6);
+        try {
+            Typeface.deserializeFontMap(sharedMemory.mapReadOnly().order(ByteOrder.BIG_ENDIAN),
+                    copiedFontMap);
+            assertEquals(systemFontMap.size(), copiedFontMap.size());
+            for (String key : systemFontMap.keySet()) {
+                assertTrue(copiedFontMap.containsKey(key));
+                Typeface original = systemFontMap.get(key);
+                Typeface copied = copiedFontMap.get(key);
+                assertEquals(original.getStyle(), copied.getStyle());
+                assertEquals(original.getWeight(), copied.getWeight());
+                assertEquals(measureText(original, "hello"), measureText(copied, "hello"), 1e-6);
+            }
+        } finally {
+            for (Typeface typeface : copiedFontMap.values()) {
+                typeface.releaseNativeObjectForTest();
+            }
         }
     }
 
diff --git a/core/tests/coretests/src/android/os/VibrationEffectTest.java b/core/tests/coretests/src/android/os/VibrationEffectTest.java
index f7ca822..0c7ff4a 100644
--- a/core/tests/coretests/src/android/os/VibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import static android.os.VibrationEffect.DEFAULT_AMPLITUDE;
 import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
 import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
 
@@ -48,6 +49,7 @@
 import org.mockito.junit.MockitoJUnitRunner;
 
 import java.time.Duration;
+import java.util.Arrays;
 
 @Presubmit
 @RunWith(MockitoJUnitRunner.class)
@@ -62,16 +64,363 @@
     private static final int TEST_AMPLITUDE = 100;
     private static final long[] TEST_TIMINGS = new long[] { 100, 100, 200 };
     private static final int[] TEST_AMPLITUDES =
-            new int[] { 255, 0, VibrationEffect.DEFAULT_AMPLITUDE };
+            new int[] { 255, 0, DEFAULT_AMPLITUDE };
 
     private static final VibrationEffect TEST_ONE_SHOT =
             VibrationEffect.createOneShot(TEST_TIMING, TEST_AMPLITUDE);
     private static final VibrationEffect DEFAULT_ONE_SHOT =
-            VibrationEffect.createOneShot(TEST_TIMING, VibrationEffect.DEFAULT_AMPLITUDE);
+            VibrationEffect.createOneShot(TEST_TIMING, DEFAULT_AMPLITUDE);
     private static final VibrationEffect TEST_WAVEFORM =
             VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1);
 
     @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesOnEvenIndices() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3, 4, 5},
+                /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {1, 2, 3, 4, 5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesOnOddIndices() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3, 4, 5},
+                /* amplitudes= */ new int[] {
+                        DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {0, 1, 2, 3, 4, 5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesAtTheStart() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3},
+                /* amplitudes= */ new int[] {0, 0, DEFAULT_AMPLITUDE},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {3, 3};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesAtTheEnd() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3},
+                /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, 0, 0},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {0, 1, 5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_allDefaultAmplitudes() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3},
+                /* amplitudes= */ new int[] {
+                        DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {0, 6};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_allZeroAmplitudes() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3},
+                /* amplitudes= */ new int[] {0, 0, 0},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {6};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_sparsedZeroAmplitudes() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3, 4, 5, 6, 7},
+                /* amplitudes= */ new int[] {
+                        0, 0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE, 0},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {3, 3, 4, 11, 7};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_oneTimingWithDefaultAmplitude() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1},
+                /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {0, 1};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_oneTimingWithZeroAmplitude() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1},
+                /* amplitudes= */ new int[] {0},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {1};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_repeating() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3, 4, 5},
+                /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0},
+                /* repeatIndex= */ 0);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+
+        effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3, 4, 5},
+                /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0},
+                /* repeatIndex= */ 3);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+
+        effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2},
+                /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE},
+                /* repeatIndex= */ 1);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_badAmplitude() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1},
+                /* amplitudes= */ new int[] {200},
+                /* repeatIndex= */ -1);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsOnly_nonZeroTimings() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {1, 2, 3};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsOnly_oneValue() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {5},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsOnly_zeroesAtTheEnd() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3, 0, 0},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {1, 2, 3, 0, 0};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsOnly_zeroesAtTheStart() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {0, 0, 1, 2, 3},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {0, 0, 1, 2, 3};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsOnly_zeroesAtTheMiddle() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 0, 0, 3, 4, 5},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {1, 2, 0, 0, 3, 4, 5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsOnly_sparsedZeroes() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {0, 1, 2, 0, 0, 3, 4, 5, 0},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {0, 1, 2, 0, 0, 3, 4, 5, 0};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsOnly_repeating() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {0, 1, 2, 0, 0, 3, 4, 5, 0},
+                /* repeatIndex= */ 0);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+
+        effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3, 4},
+                /* repeatIndex= */ 2);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_notPatternPased() {
+        VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_oneShot_defaultAmplitude() {
+        VibrationEffect effect = VibrationEffect.createOneShot(
+                /* milliseconds= */ 5, /* ampliutde= */ DEFAULT_AMPLITUDE);
+        long[] expectedPattern = new long[] {0, 5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_oneShot_badAmplitude() {
+        VibrationEffect effect = VibrationEffect.createOneShot(
+                /* milliseconds= */ 5, /* ampliutde= */ 50);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_composition_noOffDuration() {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {5},
+                                /* repeatIndex= */ -1))
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {2, 3},
+                                /* repeatIndex= */ -1))
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {10, 20},
+                                /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE},
+                                /* repeatIndex= */ -1))
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {4, 5},
+                                /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE},
+                                /* repeatIndex= */ -1))
+                .compose();
+        long[] expectedPattern = new long[] {7, 33, 4, 5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_composition_withOffDuration() {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addOffDuration(Duration.ofMillis(20))
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {10, 20},
+                                /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE},
+                                /* repeatIndex= */ -1))
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {30, 40},
+                                /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE},
+                                /* repeatIndex= */ -1))
+                .addOffDuration(Duration.ofMillis(10))
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {4, 5},
+                                /* repeatIndex= */ -1))
+                .addOffDuration(Duration.ofMillis(5))
+                .compose();
+        long[] expectedPattern = new long[] {30, 90, 14, 5, 5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_composition_withPrimitives() {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+                .addOffDuration(Duration.ofMillis(20))
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {5},
+                                /* repeatIndex= */ -1))
+                .compose();
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_composition_repeating() {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {5},
+                                /* repeatIndex= */ -1))
+                .repeatEffectIndefinitely(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {2, 3},
+                                /* repeatIndex= */ -1))
+                .compose();
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_effectsViaStartWaveform() {
+        // Effects created via startWaveform are not expected to be converted to long[] patterns, as
+        // they are not configured to always play with the default amplitude.
+        VibrationEffect effect = VibrationEffect.startWaveform(targetFrequency(60))
+                .addTransition(Duration.ofMillis(100), targetAmplitude(1), targetFrequency(120))
+                .addSustain(Duration.ofMillis(200))
+                .addTransition(Duration.ofMillis(100), targetAmplitude(0), targetFrequency(60))
+                .build();
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+
+        effect = VibrationEffect.startWaveform(targetFrequency(60))
+                .addTransition(Duration.ofMillis(80), targetAmplitude(1))
+                .addSustain(Duration.ofMillis(200))
+                .addTransition(Duration.ofMillis(100), targetAmplitude(0))
+                .build();
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+
+        effect = VibrationEffect.startWaveform(targetFrequency(60))
+                .addTransition(Duration.ofMillis(100), targetFrequency(50))
+                .addSustain(Duration.ofMillis(50))
+                .addTransition(Duration.ofMillis(20), targetFrequency(75))
+                .build();
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
     public void getRingtones_noPrebakedRingtones() {
         Resources r = mockRingtoneResources(new String[0]);
         Context context = mockContext(r);
@@ -100,7 +449,7 @@
     @Test
     public void testValidateOneShot() {
         VibrationEffect.createOneShot(1, 255).validate();
-        VibrationEffect.createOneShot(1, VibrationEffect.DEFAULT_AMPLITUDE).validate();
+        VibrationEffect.createOneShot(1, DEFAULT_AMPLITUDE).validate();
 
         assertThrows(IllegalArgumentException.class,
                 () -> VibrationEffect.createOneShot(-1, 255).validate());
@@ -501,6 +850,13 @@
         assertTrue(VibrationEffect.get(VibrationEffect.EFFECT_TICK).isHapticFeedbackCandidate());
     }
 
+    private void assertArrayEq(long[] expected, long[] actual) {
+        assertTrue(
+                String.format("Expected pattern %s, but was %s",
+                        Arrays.toString(expected), Arrays.toString(actual)),
+                Arrays.equals(expected, actual));
+    }
+
     private Resources mockRingtoneResources() {
         return mockRingtoneResources(new String[]{
                 RINGTONE_URI_1,
diff --git a/core/tests/coretests/src/android/view/DisplayShapeTest.java b/core/tests/coretests/src/android/view/DisplayShapeTest.java
new file mode 100644
index 0000000..77dd8bd
--- /dev/null
+++ b/core/tests/coretests/src/android/view/DisplayShapeTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2022 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.view;
+
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertEquals;
+
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link DisplayShape}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class DisplayShapeTest {
+    // Rectangle with w=100, height=200
+    private static final String SPEC_RECTANGULAR_SHAPE = "M0,0 L100,0 L100,200 L0,200 Z";
+
+    @Test
+    public void testGetPath() {
+        final DisplayShape displayShape = DisplayShape.fromSpecString(
+                SPEC_RECTANGULAR_SHAPE, 1f, 100, 200);
+        final Path path = displayShape.getPath();
+        final RectF actualRect = new RectF();
+        path.computeBounds(actualRect, false);
+
+        final RectF expectRect = new RectF(0f, 0f, 100f, 200f);
+        assertEquals(actualRect, expectRect);
+    }
+
+    @Test
+    public void testDefaultShape_screenIsRound() {
+        final DisplayShape displayShape = DisplayShape.createDefaultDisplayShape(100, 100, true);
+
+        // A circle with radius = 50.
+        final String expect = "M0,50.0 A50.0,50.0 0 1,1 100,50.0 A50.0,50.0 0 1,1 0,50.0 Z";
+        assertEquals(displayShape.mDisplayShapeSpec, expect);
+    }
+
+    @Test
+    public void testDefaultShape_screenIsNotRound() {
+        final DisplayShape displayShape = DisplayShape.createDefaultDisplayShape(100, 200, false);
+
+        // A rectangle with width/height = 100/200.
+        final String expect = "M0,0 L100,0 L100,200 L0,200 Z";
+        assertEquals(displayShape.mDisplayShapeSpec, expect);
+    }
+
+    @Test
+    public void testFromSpecString_cache() {
+        final DisplayShape cached = DisplayShape.fromSpecString(
+                SPEC_RECTANGULAR_SHAPE, 1f, 100, 200);
+        assertThat(DisplayShape.fromSpecString(SPEC_RECTANGULAR_SHAPE, 1f, 100, 200),
+                sameInstance(cached));
+    }
+
+    @Test
+    public void testGetPath_cache() {
+        final Path cached = DisplayShape.fromSpecString(
+                SPEC_RECTANGULAR_SHAPE, 1f, 100, 200).getPath();
+        assertThat(DisplayShape.fromSpecString(
+                SPEC_RECTANGULAR_SHAPE, 1f, 100, 200).getPath(),
+                sameInstance(cached));
+    }
+
+    @Test
+    public void testRotate_90() {
+        DisplayShape displayShape = DisplayShape.fromSpecString(
+                SPEC_RECTANGULAR_SHAPE, 1f, 100, 200);
+        displayShape = displayShape.setRotation(ROTATION_90);
+        final Path path = displayShape.getPath();
+        final RectF actualRect = new RectF();
+        path.computeBounds(actualRect, false);
+
+        final RectF expectRect = new RectF(0f, 0f, 200f, 100f);
+        assertEquals(actualRect, expectRect);
+    }
+
+    @Test
+    public void testRotate_270() {
+        DisplayShape displayShape = DisplayShape.fromSpecString(
+                SPEC_RECTANGULAR_SHAPE, 1f, 100, 200);
+        displayShape = displayShape.setRotation(ROTATION_270);
+        final Path path = displayShape.getPath();
+        final RectF actualRect = new RectF();
+        path.computeBounds(actualRect, false);
+
+        final RectF expectRect = new RectF(0f, 0f, 200f, 100f);
+        assertEquals(actualRect, expectRect);
+    }
+
+    @Test
+    public void testOffset() {
+        DisplayShape displayShape = DisplayShape.fromSpecString(
+                SPEC_RECTANGULAR_SHAPE, 1f, 100, 200);
+        displayShape = displayShape.setOffset(-10, -20);
+        final Path path = displayShape.getPath();
+        final RectF actualRect = new RectF();
+        path.computeBounds(actualRect, false);
+
+        final RectF expectRect = new RectF(-10f, -20f, 90f, 180f);
+        assertEquals(actualRect, expectRect);
+    }
+
+    @Test
+    public void testPhysicalPixelDisplaySizeRatio() {
+        final DisplayShape displayShape = DisplayShape.fromSpecString(
+                SPEC_RECTANGULAR_SHAPE, 0.5f, 100, 200);
+        final Path path = displayShape.getPath();
+        final RectF actualRect = new RectF();
+        path.computeBounds(actualRect, false);
+
+        final RectF expectRect = new RectF(0f, 0f, 50f, 100f);
+        assertEquals(actualRect, expectRect);
+    }
+}
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index be9da11..6a96f28 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -517,6 +517,19 @@
                 windowInsets.getRoundedCorner(POSITION_BOTTOM_LEFT));
     }
 
+    @Test
+    public void testCalculateRelativeDisplayShape() {
+        mState.setDisplayFrame(new Rect(0, 0, 200, 400));
+        mState.setDisplayShape(DisplayShape.createDefaultDisplayShape(200, 400, false));
+        WindowInsets windowInsets = mState.calculateInsets(new Rect(10, 20, 200, 400), null, false,
+                false, SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION,
+                WINDOWING_MODE_UNDEFINED, new SparseIntArray());
+
+        final DisplayShape expect =
+                DisplayShape.createDefaultDisplayShape(200, 400, false).setOffset(-10, -20);
+        assertEquals(expect, windowInsets.getDisplayShape());
+    }
+
     private void assertEqualsAndHashCode() {
         assertEquals(mState, mState2);
         assertEquals(mState.hashCode(), mState2.hashCode());
diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java
index dd9634b..4fed396 100644
--- a/core/tests/coretests/src/android/view/WindowInsetsTest.java
+++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java
@@ -39,13 +39,16 @@
 
     @Test
     public void systemWindowInsets_afterConsuming_isConsumed() {
-        assertTrue(new WindowInsets(new Rect(1, 2, 3, 4), null, false, false, null)
+        assertTrue(new WindowInsets(WindowInsets.createCompatTypeMap(new Rect(1, 2, 3, 4)), null,
+                null, false, false, null, null, null, null,
+                WindowInsets.Type.systemBars(), false)
                 .consumeSystemWindowInsets().isConsumed());
     }
 
     @Test
     public void multiNullConstructor_isConsumed() {
-        assertTrue(new WindowInsets((Rect) null, null, false, false, null).isConsumed());
+        assertTrue(new WindowInsets(null, null, null, false, false, null, null, null, null,
+                WindowInsets.Type.systemBars(), false).isConsumed());
     }
 
     @Test
@@ -61,7 +64,8 @@
         WindowInsets.assignCompatInsets(maxInsets, new Rect(0, 10, 0, 0));
         WindowInsets.assignCompatInsets(insets, new Rect(0, 0, 0, 0));
         WindowInsets windowInsets = new WindowInsets(insets, maxInsets, visible, false, false, null,
-                null, null, systemBars(), true /* compatIgnoreVisibility */);
+                null, null, DisplayShape.NONE, systemBars(),
+                true /* compatIgnoreVisibility */);
         assertEquals(Insets.of(0, 10, 0, 0), windowInsets.getSystemWindowInsets());
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
index d10f173..4d4ec35 100644
--- a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
@@ -168,7 +168,8 @@
     }
 
     private WindowInsets insetsWith(Insets content, DisplayCutout cutout) {
-        return new WindowInsets(content.toRect(), null, false, false, cutout);
+        return new WindowInsets(WindowInsets.createCompatTypeMap(content.toRect()), null, null,
+                false, false, cutout, null, null, null, WindowInsets.Type.systemBars(), false);
     }
 
     private ViewGroup createViewGroupWithId(int id) {
diff --git a/core/tests/utiltests/src/android/util/IntArrayTest.java b/core/tests/utiltests/src/android/util/IntArrayTest.java
index a76c640..caa7312 100644
--- a/core/tests/utiltests/src/android/util/IntArrayTest.java
+++ b/core/tests/utiltests/src/android/util/IntArrayTest.java
@@ -16,8 +16,8 @@
 
 package android.util;
 
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -25,6 +25,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Arrays;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class IntArrayTest {
@@ -35,51 +37,65 @@
         a.add(1);
         a.add(2);
         a.add(3);
-        verify(new int[]{1, 2, 3}, a);
+        verify(a, 1, 2, 3);
 
         IntArray b = IntArray.fromArray(new int[]{4, 5, 6, 7, 8}, 3);
         a.addAll(b);
-        verify(new int[]{1, 2, 3, 4, 5, 6}, a);
+        verify(a, 1, 2, 3, 4, 5, 6);
 
         a.resize(2);
-        verify(new int[]{1, 2}, a);
+        verify(a, 1, 2);
 
         a.resize(8);
-        verify(new int[]{1, 2, 0, 0, 0, 0, 0, 0}, a);
+        verify(a, 1, 2, 0, 0, 0, 0, 0, 0);
 
         a.set(5, 10);
-        verify(new int[]{1, 2, 0, 0, 0, 10, 0, 0}, a);
+        verify(a, 1, 2, 0, 0, 0, 10, 0, 0);
 
         a.add(5, 20);
-        assertEquals(20, a.get(5));
-        assertEquals(5, a.indexOf(20));
-        verify(new int[]{1, 2, 0, 0, 0, 20, 10, 0, 0}, a);
+        assertThat(a.get(5)).isEqualTo(20);
+        assertThat(a.indexOf(20)).isEqualTo(5);
+        verify(a, 1, 2, 0, 0, 0, 20, 10, 0, 0);
 
-        assertEquals(-1, a.indexOf(99));
+        assertThat(a.indexOf(99)).isEqualTo(-1);
 
         a.resize(15);
         a.set(14, 30);
-        verify(new int[]{1, 2, 0, 0, 0, 20, 10, 0, 0, 0, 0, 0, 0, 0, 30}, a);
+        verify(a, 1, 2, 0, 0, 0, 20, 10, 0, 0, 0, 0, 0, 0, 0, 30);
 
         int[] backingArray = new int[]{1, 2, 3, 4};
         a = IntArray.wrap(backingArray);
         a.set(0, 10);
-        assertEquals(10, backingArray[0]);
+        assertThat(backingArray[0]).isEqualTo(10);
         backingArray[1] = 20;
         backingArray[2] = 30;
-        verify(backingArray, a);
-        assertEquals(2, a.indexOf(30));
+        verify(a, backingArray);
+        assertThat(a.indexOf(30)).isEqualTo(2);
 
         a.resize(2);
-        assertEquals(0, backingArray[2]);
-        assertEquals(0, backingArray[3]);
+        assertThat(backingArray[2]).isEqualTo(0);
+        assertThat(backingArray[3]).isEqualTo(0);
 
         a.add(50);
-        verify(new int[]{10, 20, 50}, a);
+        verify(a, 10, 20, 50);
     }
 
-    public void verify(int[] expected, IntArray intArray) {
-        assertEquals(expected.length, intArray.size());
-        assertArrayEquals(expected, intArray.toArray());
+    @Test
+    public void testToString() {
+        IntArray a = new IntArray(10);
+        a.add(4);
+        a.add(8);
+        a.add(15);
+        a.add(16);
+        a.add(23);
+        a.add(42);
+
+        assertWithMessage("toString()").that(a.toString()).contains("4, 8, 15, 16, 23, 42");
+        assertWithMessage("toString()").that(a.toString()).doesNotContain("0");
+    }
+
+    public void verify(IntArray intArray, int... expected) {
+        assertWithMessage("contents of %s", intArray).that(intArray.toArray()).asList()
+                .containsExactlyElementsIn(Arrays.stream(expected).boxed().toList());
     }
 }
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index 215b60e..a43e225 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -371,6 +371,10 @@
 # key 413 "KEY_DIGITS"
 # key 414 "KEY_TEEN"
 # key 415 "KEY_TWEN"
+# key 418 "KEY_ZOOM_IN"
+key 418   ZOOM_IN
+# key 419 "KEY_ZOOM_OUT"
+key 419   ZOOM_OUT
 key 528 FOCUS
 
 key 429   CONTACTS
diff --git a/graphics/java/android/graphics/PathIterator.java b/graphics/java/android/graphics/PathIterator.java
index 33b9a47..bfda690 100644
--- a/graphics/java/android/graphics/PathIterator.java
+++ b/graphics/java/android/graphics/PathIterator.java
@@ -281,7 +281,7 @@
             return mConicWeight;
         }
 
-        public Segment(@NonNull @Verb int verb, @NonNull float[] points, float conicWeight) {
+        Segment(@NonNull @Verb int verb, @NonNull float[] points, float conicWeight) {
             mVerb = verb;
             mPoints = points;
             mConicWeight = conicWeight;
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 3b7d0e1..9fb627f 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -1207,6 +1207,7 @@
      * It is safe to call this method twice or more on the same instance.
      * @hide
      */
+    @TestApi
     public void releaseNativeObjectForTest() {
         mCleaner.run();
     }
@@ -1294,6 +1295,13 @@
     /**
      * Deserialize the font mapping from the serialized byte buffer.
      *
+     * <p>Warning: the given {@code buffer} must outlive generated Typeface
+     * objects in {@code out}. In production code, this is guaranteed by
+     * storing the buffer in {@link #sSystemFontMapBuffer}.
+     * If you call this method in a test, please make sure to destroy the
+     * generated Typeface objects by calling
+     * {@link #releaseNativeObjectForTest()}.
+     *
      * @hide
      */
     @TestApi
diff --git a/graphics/java/android/view/PixelCopy.java b/graphics/java/android/view/PixelCopy.java
index 82ced43..0e198d5 100644
--- a/graphics/java/android/view/PixelCopy.java
+++ b/graphics/java/android/view/PixelCopy.java
@@ -382,9 +382,9 @@
             }
 
             /**
-             * Creates a PixelCopy request for the given {@link Window}
+             * Creates a PixelCopy Builder for the given {@link Window}
              * @param source The Window to copy from
-             * @return A {@link Builder} builder to set the optional params & execute the request
+             * @return A {@link Builder} builder to set the optional params & build the request
              */
             @SuppressLint("BuilderSetStyle")
             public static @NonNull Builder ofWindow(@NonNull Window source) {
@@ -394,7 +394,7 @@
             }
 
             /**
-             * Creates a PixelCopy request for the {@link Window} that the given {@link View} is
+             * Creates a PixelCopy Builder for the {@link Window} that the given {@link View} is
              * attached to.
              *
              * Note that this copy request is not cropped to the area the View occupies by default.
@@ -404,7 +404,7 @@
              *
              * @param source A View that {@link View#isAttachedToWindow() is attached} to a window
              *               that will be used to retrieve the window to copy from.
-             * @return A {@link Builder} builder to set the optional params & execute the request
+             * @return A {@link Builder} builder to set the optional params & build the request
              */
             @SuppressLint("BuilderSetStyle")
             public static @NonNull Builder ofWindow(@NonNull View source) {
@@ -427,10 +427,10 @@
             }
 
             /**
-             * Creates a PixelCopy request for the given {@link Surface}
+             * Creates a PixelCopy Builder for the given {@link Surface}
              *
              * @param source The Surface to copy from. Must be {@link Surface#isValid() valid}.
-             * @return A {@link Builder} builder to set the optional params & execute the request
+             * @return A {@link Builder} builder to set the optional params & build the request
              */
             @SuppressLint("BuilderSetStyle")
             public static @NonNull Builder ofSurface(@NonNull Surface source) {
@@ -441,12 +441,12 @@
             }
 
             /**
-             * Creates a PixelCopy request for the {@link Surface} belonging to the
+             * Creates a PixelCopy Builder for the {@link Surface} belonging to the
              * given {@link SurfaceView}
              *
              * @param source The SurfaceView to copy from. The backing surface must be
              *               {@link Surface#isValid() valid}
-             * @return A {@link Builder} builder to set the optional params & execute the request
+             * @return A {@link Builder} builder to set the optional params & build the request
              */
             @SuppressLint("BuilderSetStyle")
             public static @NonNull Builder ofSurface(@NonNull SurfaceView source) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
index a0dde6a..2ec9e8b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.animation;
 
+import android.graphics.Path;
 import android.view.animation.Interpolator;
 import android.view.animation.LinearInterpolator;
 import android.view.animation.PathInterpolator;
@@ -53,6 +54,11 @@
     public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
 
     /**
+     * The default emphasized interpolator. Used for hero / emphasized movement of content.
+     */
+    public static final Interpolator EMPHASIZED = createEmphasizedInterpolator();
+
+    /**
      * The accelerated emphasized interpolator. Used for hero / emphasized movement of content that
      * is disappearing e.g. when moving off screen.
      */
@@ -81,4 +87,14 @@
 
     public static final PathInterpolator DIM_INTERPOLATOR =
             new PathInterpolator(.23f, .87f, .52f, -0.11f);
+
+    // Create the default emphasized interpolator
+    private static PathInterpolator createEmphasizedInterpolator() {
+        Path path = new Path();
+        // Doing the same as fast_out_extra_slow_in
+        path.moveTo(0f, 0f);
+        path.cubicTo(0.05f, 0f, 0.133333f, 0.06f, 0.166666f, 0.4f);
+        path.cubicTo(0.208333f, 0.82f, 0.25f, 1f, 1f, 1f);
+        return new PathInterpolator(path);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
new file mode 100644
index 0000000..36cf29a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 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.wm.shell.back;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.annotation.NonNull;
+import android.graphics.Color;
+import android.view.SurfaceControl;
+
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+
+/**
+ * Controls background surface for the back animations
+ */
+public class BackAnimationBackground {
+    private static final int BACKGROUND_LAYER = -1;
+    private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+    private SurfaceControl mBackgroundSurface;
+
+    public BackAnimationBackground(RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+        mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+    }
+
+    void ensureBackground(int color, @NonNull SurfaceControl.Transaction transaction) {
+        if (mBackgroundSurface != null) {
+            return;
+        }
+
+        final float[] colorComponents = new float[] { Color.red(color) / 255.f,
+                Color.green(color) / 255.f, Color.blue(color) / 255.f };
+
+        final SurfaceControl.Builder colorLayerBuilder = new SurfaceControl.Builder()
+                .setName("back-animation-background")
+                .setCallsite("BackAnimationBackground")
+                .setColorLayer();
+
+        mRootTaskDisplayAreaOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, colorLayerBuilder);
+        mBackgroundSurface = colorLayerBuilder.build();
+        transaction.setColor(mBackgroundSurface, colorComponents)
+                .setLayer(mBackgroundSurface, BACKGROUND_LAYER)
+                .show(mBackgroundSurface);
+    }
+
+    void removeBackground(@NonNull SurfaceControl.Transaction transaction) {
+        if (mBackgroundSurface == null) {
+            return;
+        }
+
+        if (mBackgroundSurface.isValid()) {
+            transaction.remove(mBackgroundSurface);
+        }
+        mBackgroundSurface = null;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index f811940..0133f6b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -138,14 +138,18 @@
         }
     };
 
+    private final BackAnimationBackground mAnimationBackground;
+
     public BackAnimationController(
             @NonNull ShellInit shellInit,
             @NonNull ShellController shellController,
             @NonNull @ShellMainThread ShellExecutor shellExecutor,
             @NonNull @ShellBackgroundThread Handler backgroundHandler,
-            Context context) {
+            Context context,
+            @NonNull BackAnimationBackground backAnimationBackground) {
         this(shellInit, shellController, shellExecutor, backgroundHandler,
-                ActivityTaskManager.getService(), context, context.getContentResolver());
+                ActivityTaskManager.getService(), context, context.getContentResolver(),
+                backAnimationBackground);
     }
 
     @VisibleForTesting
@@ -155,7 +159,8 @@
             @NonNull @ShellMainThread ShellExecutor shellExecutor,
             @NonNull @ShellBackgroundThread Handler bgHandler,
             @NonNull IActivityTaskManager activityTaskManager,
-            Context context, ContentResolver contentResolver) {
+            Context context, ContentResolver contentResolver,
+            @NonNull BackAnimationBackground backAnimationBackground) {
         mShellController = shellController;
         mShellExecutor = shellExecutor;
         mActivityTaskManager = activityTaskManager;
@@ -163,6 +168,7 @@
         mContentResolver = contentResolver;
         mBgHandler = bgHandler;
         shellInit.addInitCallback(this::onInit, this);
+        mAnimationBackground = backAnimationBackground;
     }
 
     @VisibleForTesting
@@ -184,10 +190,14 @@
             return;
         }
 
-        final CrossTaskBackAnimation crossTaskAnimation = new CrossTaskBackAnimation(mContext);
+        final CrossTaskBackAnimation crossTaskAnimation =
+                new CrossTaskBackAnimation(mContext, mAnimationBackground);
         mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_TASK,
-                new BackAnimationRunner(crossTaskAnimation.mCallback, crossTaskAnimation.mRunner));
-        // TODO (238474994): register cross activity animation when it's completed.
+                crossTaskAnimation.mBackAnimationRunner);
+        final CrossActivityAnimation crossActivityAnimation =
+                new CrossActivityAnimation(mContext, mAnimationBackground);
+        mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+                crossActivityAnimation.mBackAnimationRunner);
         // TODO (236760237): register dialog close animation when it's completed.
     }
 
@@ -275,7 +285,8 @@
         @Override
         public void clearBackToLauncherCallback() {
             executeRemoteCallWithTaskPermission(mController, "clearBackToLauncherCallback",
-                    (controller) -> controller.clearBackToLauncherCallback());
+                    (controller) -> controller.unregisterAnimation(
+                            BackNavigationInfo.TYPE_RETURN_TO_HOME));
         }
 
         @Override
@@ -289,8 +300,8 @@
         mAnimationDefinition.set(type, runner);
     }
 
-    private void clearBackToLauncherCallback() {
-        mAnimationDefinition.remove(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+    void unregisterAnimation(@BackNavigationInfo.BackTargetType int type) {
+        mAnimationDefinition.remove(type);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
new file mode 100644
index 0000000..9f6bc5d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2022 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.wm.shell.back;
+
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
+
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.RemoteException;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.window.BackEvent;
+import android.window.BackProgressAnimator;
+import android.window.IOnBackInvokedCallback;
+
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+
+/** Class that defines cross-activity animation. */
+@ShellMainThread
+class CrossActivityAnimation {
+    /**
+     * Minimum scale of the entering/closing window.
+     */
+    private static final float MIN_WINDOW_SCALE = 0.9f;
+
+    /**
+     * Minimum alpha of the closing/entering window.
+     */
+    private static final float CLOSING_MIN_WINDOW_ALPHA = 0.5f;
+
+    /**
+     * Progress value to fly out closing window and fly in entering window.
+     */
+    private static final float SWITCH_ENTERING_WINDOW_PROGRESS = 0.5f;
+
+    /** Max window translation in the Y axis. */
+    private static final int WINDOW_MAX_DELTA_Y = 160;
+
+    /** Duration of fade in/out entering window. */
+    private static final int FADE_IN_DURATION = 100;
+    /** Duration of post animation after gesture committed. */
+    private static final int POST_ANIMATION_DURATION = 350;
+    private static final Interpolator INTERPOLATOR = Interpolators.EMPHASIZED;
+
+    private final Rect mStartTaskRect = new Rect();
+    private final float mCornerRadius;
+
+    // The closing window properties.
+    private final RectF mClosingRect = new RectF();
+
+    // The entering window properties.
+    private final Rect mEnteringStartRect = new Rect();
+    private final RectF mEnteringRect = new RectF();
+
+    private float mCurrentAlpha = 1.0f;
+
+    private float mEnteringMargin = 0;
+    private ValueAnimator mEnteringAnimator;
+    private boolean mEnteringWindowShow = false;
+
+    private final PointF mInitialTouchPos = new PointF();
+
+    private final Matrix mTransformMatrix = new Matrix();
+
+    private final float[] mTmpFloat9 = new float[9];
+
+    private RemoteAnimationTarget mEnteringTarget;
+    private RemoteAnimationTarget mClosingTarget;
+    private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+
+    private boolean mBackInProgress = false;
+
+    private PointF mTouchPos = new PointF();
+    private IRemoteAnimationFinishedCallback mFinishCallback;
+
+    private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+    final BackAnimationRunner mBackAnimationRunner;
+
+    private final BackAnimationBackground mBackground;
+
+    CrossActivityAnimation(Context context, BackAnimationBackground background) {
+        mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
+        mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+        mBackground = background;
+    }
+
+    private static float mapRange(float value, float min, float max) {
+        return min + (value * (max - min));
+    }
+
+    private float getInterpolatedProgress(float backProgress) {
+        return INTERPOLATOR.getInterpolation(backProgress);
+    }
+
+    private void startBackAnimation() {
+        if (mEnteringTarget == null || mClosingTarget == null) {
+            ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Entering target or closing target is null.");
+            return;
+        }
+        mTransaction.setAnimationTransaction();
+
+        // Offset start rectangle to align task bounds.
+        mStartTaskRect.set(mClosingTarget.windowConfiguration.getBounds());
+        mStartTaskRect.offsetTo(0, 0);
+
+        // Draw background with task background color.
+        mBackground.ensureBackground(
+                mEnteringTarget.taskInfo.taskDescription.getBackgroundColor(), mTransaction);
+    }
+
+    private void applyTransform(SurfaceControl leash, RectF targetRect, float targetAlpha) {
+        final float scale = targetRect.width() / mStartTaskRect.width();
+        mTransformMatrix.reset();
+        mTransformMatrix.setScale(scale, scale);
+        mTransformMatrix.postTranslate(targetRect.left, targetRect.top);
+        mTransaction.setAlpha(leash, targetAlpha)
+                .setMatrix(leash, mTransformMatrix, mTmpFloat9)
+                .setWindowCrop(leash, mStartTaskRect)
+                .setCornerRadius(leash, mCornerRadius);
+    }
+
+    private void finishAnimation() {
+        if (mEnteringTarget != null) {
+            mEnteringTarget.leash.release();
+            mEnteringTarget = null;
+        }
+        if (mClosingTarget != null) {
+            mClosingTarget.leash.release();
+            mClosingTarget = null;
+        }
+        if (mBackground != null) {
+            mBackground.removeBackground(mTransaction);
+        }
+
+        mTransaction.apply();
+        mBackInProgress = false;
+        mTransformMatrix.reset();
+        mInitialTouchPos.set(0, 0);
+        mEnteringWindowShow = false;
+        mEnteringMargin = 0;
+
+        if (mFinishCallback != null) {
+            try {
+                mFinishCallback.onAnimationFinished();
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+            mFinishCallback = null;
+        }
+    }
+
+    private void onGestureProgress(@NonNull BackEvent backEvent) {
+        if (!mBackInProgress) {
+            mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
+            mBackInProgress = true;
+        }
+        mTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
+
+        if (mEnteringTarget == null || mClosingTarget == null) {
+            return;
+        }
+
+        final float progress = getInterpolatedProgress(backEvent.getProgress());
+        final float touchY = mTouchPos.y;
+
+        final int width = mStartTaskRect.width();
+        final int height = mStartTaskRect.height();
+
+        final float closingScale = mapRange(progress, 1, MIN_WINDOW_SCALE);
+
+        final float closingWidth = closingScale * width;
+        final float closingHeight = (float) height / width * closingWidth;
+
+        // Move the window along the X axis.
+        final float closingLeft = mStartTaskRect.left + (width - closingWidth) / 2;
+
+        // Move the window along the Y axis.
+        final float deltaYRatio = (touchY - mInitialTouchPos.y) / height;
+        final float deltaY = (float) Math.sin(deltaYRatio * Math.PI * 0.5f) * WINDOW_MAX_DELTA_Y;
+        final float closingTop = (height - closingHeight) * 0.5f + deltaY;
+        mClosingRect.set(
+                closingLeft, closingTop, closingLeft + closingWidth, closingTop + closingHeight);
+        mEnteringRect.set(mClosingRect);
+
+        // Switch closing/entering targets while reach to the threshold progress.
+        if (showEnteringWindow(progress > SWITCH_ENTERING_WINDOW_PROGRESS)) {
+            return;
+        }
+
+        // Present windows and update the alpha.
+        mCurrentAlpha = Math.max(mapRange(progress, 1.0f, 0), CLOSING_MIN_WINDOW_ALPHA);
+        mClosingRect.offset(mEnteringMargin, 0);
+        mEnteringRect.offset(mEnteringMargin - width, 0);
+
+        applyTransform(
+                mClosingTarget.leash, mClosingRect, mEnteringWindowShow ? 0.01f : mCurrentAlpha);
+        applyTransform(
+                mEnteringTarget.leash, mEnteringRect, mEnteringWindowShow ? mCurrentAlpha : 0.01f);
+        mTransaction.apply();
+    }
+
+    private boolean showEnteringWindow(boolean show) {
+        if (mEnteringAnimator == null) {
+            mEnteringAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(FADE_IN_DURATION);
+            mEnteringAnimator.setInterpolator(new AccelerateInterpolator());
+            mEnteringAnimator.addUpdateListener(animation -> {
+                float progress = animation.getAnimatedFraction();
+                final int width = mStartTaskRect.width();
+                mEnteringMargin = width * progress;
+                // We don't animate to 0 or the surface would become invisible and lose focus.
+                final float alpha = progress >= 0.5f ? 0.01f
+                        : mapRange(progress * 2, mCurrentAlpha, 0.01f);
+                mClosingRect.offset(mEnteringMargin, 0);
+                mEnteringRect.offset(mEnteringMargin - width, 0);
+
+                applyTransform(mClosingTarget.leash, mClosingRect, alpha);
+                applyTransform(mEnteringTarget.leash, mEnteringRect, mCurrentAlpha);
+                mTransaction.apply();
+            });
+        }
+
+        if (mEnteringAnimator.isRunning()) {
+            return true;
+        }
+
+        if (mEnteringWindowShow == show) {
+            return false;
+        }
+
+        mEnteringWindowShow = show;
+        if (show) {
+            mEnteringAnimator.start();
+        } else {
+            mEnteringAnimator.reverse();
+        }
+        return true;
+    }
+
+    private void onGestureCommitted() {
+        if (mEnteringTarget == null || mClosingTarget == null) {
+            finishAnimation();
+            return;
+        }
+
+        // End the fade in animation.
+        if (mEnteringAnimator.isRunning()) {
+            mEnteringAnimator.cancel();
+        }
+
+        // We enter phase 2 of the animation, the starting coordinates for phase 2 are the current
+        // coordinate of the gesture driven phase.
+        mEnteringRect.round(mEnteringStartRect);
+        mTransaction.hide(mClosingTarget.leash);
+
+        ValueAnimator valueAnimator =
+                ValueAnimator.ofFloat(1f, 0f).setDuration(POST_ANIMATION_DURATION);
+        valueAnimator.setInterpolator(new DecelerateInterpolator());
+        valueAnimator.addUpdateListener(animation -> {
+            float progress = animation.getAnimatedFraction();
+            updatePostCommitEnteringAnimation(progress);
+            mTransaction.apply();
+        });
+
+        valueAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                finishAnimation();
+            }
+        });
+        valueAnimator.start();
+    }
+
+    private void updatePostCommitEnteringAnimation(float progress) {
+        float left = mapRange(progress, mEnteringStartRect.left, mStartTaskRect.left);
+        float top = mapRange(progress, mEnteringStartRect.top, mStartTaskRect.top);
+        float width = mapRange(progress, mEnteringStartRect.width(), mStartTaskRect.width());
+        float height = mapRange(progress, mEnteringStartRect.height(), mStartTaskRect.height());
+        float alpha = mapRange(progress, mCurrentAlpha, 1.0f);
+
+        mEnteringRect.set(left, top, left + width, top + height);
+        applyTransform(mEnteringTarget.leash, mEnteringRect, alpha);
+    }
+
+    private final class Callback extends IOnBackInvokedCallback.Default {
+        @Override
+        public void onBackStarted(BackEvent backEvent) {
+            mProgressAnimator.onBackStarted(backEvent,
+                    CrossActivityAnimation.this::onGestureProgress);
+        }
+
+        @Override
+        public void onBackProgressed(@NonNull BackEvent backEvent) {
+            mProgressAnimator.onBackProgressed(backEvent);
+        }
+
+        @Override
+        public void onBackCancelled() {
+            // End the fade in animation.
+            if (mEnteringAnimator.isRunning()) {
+                mEnteringAnimator.cancel();
+            }
+            // TODO (b259608500): Let BackProgressAnimator could play cancel animation.
+            mProgressAnimator.reset();
+            finishAnimation();
+        }
+
+        @Override
+        public void onBackInvoked() {
+            mProgressAnimator.reset();
+            onGestureCommitted();
+        }
+    }
+
+    private final class Runner extends IRemoteAnimationRunner.Default {
+        @Override
+        public void onAnimationStart(
+                int transit,
+                RemoteAnimationTarget[] apps,
+                RemoteAnimationTarget[] wallpapers,
+                RemoteAnimationTarget[] nonApps,
+                IRemoteAnimationFinishedCallback finishedCallback) {
+            ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to activity animation.");
+            for (RemoteAnimationTarget a : apps) {
+                if (a.mode == MODE_CLOSING) {
+                    mClosingTarget = a;
+                }
+                if (a.mode == MODE_OPENING) {
+                    mEnteringTarget = a;
+                }
+            }
+
+            startBackAnimation();
+            mFinishCallback = finishedCallback;
+        }
+
+        @Override
+        public void onAnimationCancelled(boolean isKeyguardOccluded) {
+            finishAnimation();
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index 2074b6a..a9a7b77 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -61,7 +61,7 @@
  */
 @ShellMainThread
 class CrossTaskBackAnimation {
-    private static final float[] BACKGROUNDCOLOR = {0.263f, 0.263f, 0.227f};
+    private static final int BACKGROUNDCOLOR = 0x43433A;
 
     /**
      * Minimum scale of the entering window.
@@ -106,7 +106,6 @@
 
     private RemoteAnimationTarget mEnteringTarget;
     private RemoteAnimationTarget mClosingTarget;
-    private SurfaceControl mBackgroundSurface;
     private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
 
     private boolean mBackInProgress = false;
@@ -115,56 +114,15 @@
     private float mProgress = 0;
     private PointF mTouchPos = new PointF();
     private IRemoteAnimationFinishedCallback mFinishCallback;
-
     private BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+    final BackAnimationRunner mBackAnimationRunner;
 
-    final IOnBackInvokedCallback mCallback = new IOnBackInvokedCallback.Default() {
-        @Override
-        public void onBackStarted(BackEvent backEvent) {
-            mProgressAnimator.onBackStarted(backEvent,
-                    CrossTaskBackAnimation.this::onGestureProgress);
-        }
+    private final BackAnimationBackground mBackground;
 
-        @Override
-        public void onBackProgressed(@NonNull BackEvent backEvent) {
-            mProgressAnimator.onBackProgressed(backEvent);
-        }
-
-        @Override
-        public void onBackCancelled() {
-            mProgressAnimator.reset();
-            finishAnimation();
-        }
-
-        @Override
-        public void onBackInvoked() {
-            mProgressAnimator.reset();
-            onGestureCommitted();
-        }
-    };
-
-    final IRemoteAnimationRunner mRunner = new IRemoteAnimationRunner.Default() {
-        @Override
-        public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
-                RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
-                IRemoteAnimationFinishedCallback finishedCallback) {
-            ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to task animation.");
-            for (RemoteAnimationTarget a : apps) {
-                if (a.mode == MODE_CLOSING) {
-                    mClosingTarget = a;
-                }
-                if (a.mode == MODE_OPENING) {
-                    mEnteringTarget = a;
-                }
-            }
-
-            startBackAnimation();
-            mFinishCallback = finishedCallback;
-        }
-    };
-
-    CrossTaskBackAnimation(Context context) {
+    CrossTaskBackAnimation(Context context, BackAnimationBackground background) {
         mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
+        mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+        mBackground = background;
     }
 
     private float getInterpolatedProgress(float backProgress) {
@@ -182,14 +140,7 @@
         mStartTaskRect.offsetTo(0, 0);
 
         // Draw background.
-        mBackgroundSurface = new SurfaceControl.Builder()
-                .setName("Background of Back Navigation")
-                .setColorLayer()
-                .setHidden(false)
-                .build();
-        mTransaction.setColor(mBackgroundSurface, BACKGROUNDCOLOR)
-                .setLayer(mBackgroundSurface, -1);
-        mTransaction.apply();
+        mBackground.ensureBackground(BACKGROUNDCOLOR, mTransaction);
     }
 
     private void updateGestureBackProgress(float progress, BackEvent event) {
@@ -300,11 +251,11 @@
             mClosingTarget = null;
         }
 
-        if (mBackgroundSurface != null) {
-            mBackgroundSurface.release();
-            mBackgroundSurface = null;
+        if (mBackground != null) {
+            mBackground.removeBackground(mTransaction);
         }
 
+        mTransaction.apply();
         mBackInProgress = false;
         mTransformMatrix.reset();
         mClosingCurrentRect.setEmpty();
@@ -362,4 +313,49 @@
     private static float mapRange(float value, float min, float max) {
         return min + (value * (max - min));
     }
+
+    private final class Callback extends IOnBackInvokedCallback.Default  {
+        @Override
+        public void onBackStarted(BackEvent backEvent) {
+            mProgressAnimator.onBackStarted(backEvent,
+                    CrossTaskBackAnimation.this::onGestureProgress);
+        }
+
+        @Override
+        public void onBackProgressed(@NonNull BackEvent backEvent) {
+            mProgressAnimator.onBackProgressed(backEvent);
+        }
+
+        @Override
+        public void onBackCancelled() {
+            mProgressAnimator.reset();
+            finishAnimation();
+        }
+
+        @Override
+        public void onBackInvoked() {
+            mProgressAnimator.reset();
+            onGestureCommitted();
+        }
+    };
+
+    private final class Runner extends IRemoteAnimationRunner.Default {
+        @Override
+        public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+                RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+                IRemoteAnimationFinishedCallback finishedCallback) {
+            ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to task animation.");
+            for (RemoteAnimationTarget a : apps) {
+                if (a.mode == MODE_CLOSING) {
+                    mClosingTarget = a;
+                }
+                if (a.mode == MODE_OPENING) {
+                    mEnteringTarget = a;
+                }
+            }
+
+            startBackAnimation();
+            mFinishCallback = finishedCallback;
+        }
+    };
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 962be9d..4ea8a5d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -38,6 +38,7 @@
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
 import com.android.wm.shell.back.BackAnimation;
+import com.android.wm.shell.back.BackAnimationBackground;
 import com.android.wm.shell.back.BackAnimationController;
 import com.android.wm.shell.bubbles.BubbleController;
 import com.android.wm.shell.bubbles.Bubbles;
@@ -93,13 +94,13 @@
 import com.android.wm.shell.unfold.UnfoldTransitionHandler;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
-import java.util.Optional;
-
 import dagger.BindsOptionalOf;
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
+import java.util.Optional;
+
 /**
  * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
  * accessible from components within the WM subcomponent (can be explicitly exposed to the
@@ -255,21 +256,30 @@
 
     @WMSingleton
     @Provides
+    static BackAnimationBackground provideBackAnimationBackground(
+            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+        return new BackAnimationBackground(rootTaskDisplayAreaOrganizer);
+    }
+
+    @WMSingleton
+    @Provides
     static Optional<BackAnimationController> provideBackAnimationController(
             Context context,
             ShellInit shellInit,
             ShellController shellController,
             @ShellMainThread ShellExecutor shellExecutor,
-            @ShellBackgroundThread Handler backgroundHandler
+            @ShellBackgroundThread Handler backgroundHandler,
+            BackAnimationBackground backAnimationBackground
     ) {
         if (BackAnimationController.IS_ENABLED) {
             return Optional.of(
                     new BackAnimationController(shellInit, shellController, shellExecutor,
-                            backgroundHandler, context));
+                            backgroundHandler, context, backAnimationBackground));
         }
         return Optional.empty();
     }
 
+
     //
     // Bubbles (optional feature)
     //
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index f170e77..8ba2583 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -63,6 +63,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.Log;
+import android.view.Choreographer;
 import android.view.Display;
 import android.view.Surface;
 import android.view.SurfaceControl;
@@ -179,8 +180,10 @@
                 // This is necessary in case there was a resize animation ongoing when exit PIP
                 // started, in which case the first resize will be skipped to let the exit
                 // operation handle the final resize out of PIP mode. See b/185306679.
-                finishResize(tx, destinationBounds, direction, animationType);
-                sendOnPipTransitionFinished(direction);
+                finishResizeDelayedIfNeeded(() -> {
+                    finishResize(tx, destinationBounds, direction, animationType);
+                    sendOnPipTransitionFinished(direction);
+                });
             }
         }
 
@@ -196,6 +199,39 @@
         }
     };
 
+    /**
+     * Finishes resizing the PiP, delaying the operation if it has to be synced with the PiP menu.
+     *
+     * This is done to avoid a race condition between the last transaction applied in
+     * onPipAnimationUpdate and the finishResize in onPipAnimationEnd. The transaction in
+     * onPipAnimationUpdate is applied directly from WmShell, while onPipAnimationEnd creates a
+     * WindowContainerTransaction in finishResize, which is to be applied by WmCore later. Normally,
+     * the WCT should be the last transaction to finish the animation. However, it  may happen that
+     * it gets applied *before* the transaction created by the last onPipAnimationUpdate. This
+     * happens only when the PiP surface transaction has to be synced with the PiP menu due to the
+     * necessity for a delay when syncing the PiP surface animation with the PiP menu surface
+     * animation and redrawing the PiP menu contents. As a result, the PiP surface gets scaled after
+     * the new bounds are applied by WmCore, which makes the PiP surface have unexpected bounds.
+     *
+     * To avoid this, we delay the finishResize operation until
+     * the next frame. This aligns the last onAnimationUpdate transaction with the WCT application.
+     */
+    private void finishResizeDelayedIfNeeded(Runnable finishResizeRunnable) {
+        if (!shouldSyncPipTransactionWithMenu()) {
+            finishResizeRunnable.run();
+            return;
+        }
+
+        // Delay the finishResize to the next frame
+        Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, () -> {
+            mMainExecutor.execute(finishResizeRunnable);
+        }, null);
+    }
+
+    private boolean shouldSyncPipTransactionWithMenu() {
+        return mPipMenuController.isMenuVisible();
+    }
+
     @VisibleForTesting
     final PipTransitionController.PipTransitionCallback mPipTransitionCallback =
             new PipTransitionController.PipTransitionCallback() {
@@ -221,7 +257,7 @@
                 @Override
                 public boolean handlePipTransaction(SurfaceControl leash,
                         SurfaceControl.Transaction tx, Rect destinationBounds) {
-                    if (mPipMenuController.isMenuVisible()) {
+                    if (shouldSyncPipTransactionWithMenu()) {
                         mPipMenuController.movePipMenu(leash, tx, destinationBounds);
                         return true;
                     }
@@ -1223,7 +1259,7 @@
         mSurfaceTransactionHelper
                 .crop(tx, mLeash, toBounds)
                 .round(tx, mLeash, mPipTransitionState.isInPip());
-        if (mPipMenuController.isMenuVisible()) {
+        if (shouldSyncPipTransactionWithMenu()) {
             mPipMenuController.resizePipMenu(mLeash, tx, toBounds);
         } else {
             tx.apply();
@@ -1265,7 +1301,7 @@
         mSurfaceTransactionHelper
                 .scale(tx, mLeash, startBounds, toBounds, degrees)
                 .round(tx, mLeash, startBounds, toBounds);
-        if (mPipMenuController.isMenuVisible()) {
+        if (shouldSyncPipTransactionWithMenu()) {
             mPipMenuController.movePipMenu(mLeash, tx, toBounds);
         } else {
             tx.apply();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index 4e1fa29..485b400 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -77,10 +77,10 @@
                 if (mRemote.asBinder() != null) {
                     mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */);
                 }
+                if (sct != null) {
+                    finishTransaction.merge(sct);
+                }
                 mMainExecutor.execute(() -> {
-                    if (sct != null) {
-                        finishTransaction.merge(sct);
-                    }
                     finishCallback.onTransitionFinished(wct, null /* wctCB */);
                 });
             }
@@ -90,7 +90,13 @@
             if (mRemote.asBinder() != null) {
                 mRemote.asBinder().linkToDeath(remoteDied, 0 /* flags */);
             }
-            mRemote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb);
+            // If the remote is actually in the same process, then make a copy of parameters since
+            // remote impls assume that they have to clean-up native references.
+            final SurfaceControl.Transaction remoteStartT = RemoteTransitionHandler.copyIfLocal(
+                    startTransaction, mRemote.getRemoteTransition());
+            final TransitionInfo remoteInfo =
+                    remoteStartT == startTransaction ? info : info.localRemoteCopy();
+            mRemote.getRemoteTransition().startAnimation(transition, remoteInfo, remoteStartT, cb);
             // assume that remote will apply the start transaction.
             startTransaction.clear();
         } catch (RemoteException e) {
@@ -124,7 +130,13 @@
             }
         };
         try {
-            mRemote.getRemoteTransition().mergeAnimation(transition, info, t, mergeTarget, cb);
+            // If the remote is actually in the same process, then make a copy of parameters since
+            // remote impls assume that they have to clean-up native references.
+            final SurfaceControl.Transaction remoteT =
+                    RemoteTransitionHandler.copyIfLocal(t, mRemote.getRemoteTransition());
+            final TransitionInfo remoteInfo = remoteT == t ? info : info.localRemoteCopy();
+            mRemote.getRemoteTransition().mergeAnimation(
+                    transition, remoteInfo, remoteT, mergeTarget, cb);
         } catch (RemoteException e) {
             Log.e(Transitions.TAG, "Error merging remote transition.", e);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index 9469529..b4e0584 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.IBinder;
+import android.os.Parcel;
 import android.os.RemoteException;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -120,10 +121,10 @@
             public void onTransitionFinished(WindowContainerTransaction wct,
                     SurfaceControl.Transaction sct) {
                 unhandleDeath(remote.asBinder(), finishCallback);
+                if (sct != null) {
+                    finishTransaction.merge(sct);
+                }
                 mMainExecutor.execute(() -> {
-                    if (sct != null) {
-                        finishTransaction.merge(sct);
-                    }
                     mRequestedRemotes.remove(transition);
                     finishCallback.onTransitionFinished(wct, null /* wctCB */);
                 });
@@ -131,8 +132,14 @@
         };
         Transitions.setRunningRemoteTransitionDelegate(remote.getAppThread());
         try {
+            // If the remote is actually in the same process, then make a copy of parameters since
+            // remote impls assume that they have to clean-up native references.
+            final SurfaceControl.Transaction remoteStartT =
+                    copyIfLocal(startTransaction, remote.getRemoteTransition());
+            final TransitionInfo remoteInfo =
+                    remoteStartT == startTransaction ? info : info.localRemoteCopy();
             handleDeath(remote.asBinder(), finishCallback);
-            remote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb);
+            remote.getRemoteTransition().startAnimation(transition, remoteInfo, remoteStartT, cb);
             // assume that remote will apply the start transaction.
             startTransaction.clear();
         } catch (RemoteException e) {
@@ -145,6 +152,28 @@
         return true;
     }
 
+    static SurfaceControl.Transaction copyIfLocal(SurfaceControl.Transaction t,
+            IRemoteTransition remote) {
+        // We care more about parceling than local (though they should be the same); so, use
+        // queryLocalInterface since that's what Binder uses to decide if it needs to parcel.
+        if (remote.asBinder().queryLocalInterface(IRemoteTransition.DESCRIPTOR) == null) {
+            // No local interface, so binder itself will parcel and thus we don't need to.
+            return t;
+        }
+        // Binder won't be parceling; however, the remotes assume they have their own native
+        // objects (and don't know if caller is local or not), so we need to make a COPY here so
+        // that the remote can clean it up without clearing the original transaction.
+        // Since there's no direct `copy` for Transaction, we have to parcel/unparcel instead.
+        final Parcel p = Parcel.obtain();
+        try {
+            t.writeToParcel(p, 0);
+            p.setDataPosition(0);
+            return SurfaceControl.Transaction.CREATOR.createFromParcel(p);
+        } finally {
+            p.recycle();
+        }
+    }
+
     @Override
     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@@ -175,7 +204,11 @@
             }
         };
         try {
-            remote.mergeAnimation(transition, info, t, mergeTarget, cb);
+            // If the remote is actually in the same process, then make a copy of parameters since
+            // remote impls assume that they have to clean-up native references.
+            final SurfaceControl.Transaction remoteT = copyIfLocal(t, remote);
+            final TransitionInfo remoteInfo = remoteT == t ? info : info.localRemoteCopy();
+            remote.mergeAnimation(transition, remoteInfo, remoteT, mergeTarget, cb);
         } catch (RemoteException e) {
             Log.e(Transitions.TAG, "Error attempting to merge remote transition.", e);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 56d51bd..c6935c0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -503,6 +503,7 @@
             // Treat this as an abort since we are bypassing any merge logic and effectively
             // finishing immediately.
             onAbort(transitionToken);
+            releaseSurfaces(info);
             return;
         }
 
@@ -607,6 +608,15 @@
         onFinish(transition, wct, wctCB, false /* abort */);
     }
 
+    /**
+     * Releases an info's animation-surfaces. These don't need to persist and we need to release
+     * them asap so that SF can free memory sooner.
+     */
+    private void releaseSurfaces(@Nullable TransitionInfo info) {
+        if (info == null) return;
+        info.releaseAnimSurfaces();
+    }
+
     private void onFinish(IBinder transition,
             @Nullable WindowContainerTransaction wct,
             @Nullable WindowContainerTransactionCallback wctCB,
@@ -645,6 +655,11 @@
         }
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                 "Transition animation finished (abort=%b), notifying core %s", abort, transition);
+        if (active.mStartT != null) {
+            // Applied by now, so close immediately. Do not set to null yet, though, since nullness
+            // is used later to disambiguate malformed transitions.
+            active.mStartT.close();
+        }
         // Merge all relevant transactions together
         SurfaceControl.Transaction fullFinish = active.mFinishT;
         for (int iA = activeIdx + 1; iA < mActiveTransitions.size(); ++iA) {
@@ -664,12 +679,14 @@
             fullFinish.apply();
         }
         // Now perform all the finishes.
+        releaseSurfaces(active.mInfo);
         mActiveTransitions.remove(activeIdx);
         mOrganizer.finishTransition(transition, wct, wctCB);
         while (activeIdx < mActiveTransitions.size()) {
             if (!mActiveTransitions.get(activeIdx).mMerged) break;
             ActiveTransition merged = mActiveTransitions.remove(activeIdx);
             mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */);
+            releaseSurfaces(merged.mInfo);
         }
         // sift through aborted transitions
         while (mActiveTransitions.size() > activeIdx
@@ -682,8 +699,9 @@
             }
             mOrganizer.finishTransition(aborted.mToken, null /* wct */, null /* wctCB */);
             for (int i = 0; i < mObservers.size(); ++i) {
-                mObservers.get(i).onTransitionFinished(active.mToken, true);
+                mObservers.get(i).onTransitionFinished(aborted.mToken, true);
             }
+            releaseSurfaces(aborted.mInfo);
         }
         if (mActiveTransitions.size() <= activeIdx) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations "
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index d75c36c..bee9a90 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -110,6 +110,9 @@
     @Mock
     private ShellController mShellController;
 
+    @Mock
+    private BackAnimationBackground mAnimationBackground;
+
     private BackAnimationController mController;
     private TestableContentResolver mContentResolver;
     private TestableLooper mTestableLooper;
@@ -127,7 +130,7 @@
         mController = new BackAnimationController(mShellInit, mShellController,
                 mShellExecutor, new Handler(mTestableLooper.getLooper()),
                 mActivityTaskManager, mContext,
-                mContentResolver);
+                mContentResolver, mAnimationBackground);
         mController.setEnableUAnimation(true);
         mShellInit.init();
         mShellExecutor.flushAll();
@@ -239,7 +242,7 @@
         mController = new BackAnimationController(shellInit, mShellController,
                 mShellExecutor, new Handler(mTestableLooper.getLooper()),
                 mActivityTaskManager, mContext,
-                mContentResolver);
+                mContentResolver, mAnimationBackground);
         shellInit.init();
         registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
 
@@ -354,6 +357,10 @@
                 BackNavigationInfo.TYPE_DIALOG_CLOSE};
 
         for (int type: testTypes) {
+            unregisterAnimation(type);
+        }
+
+        for (int type: testTypes) {
             final ResultListener result = new ResultListener();
             createNavigationInfo(new BackNavigationInfo.Builder()
                     .setType(type)
@@ -431,6 +438,10 @@
                 new BackAnimationRunner(mAnimatorCallback, mBackAnimationRunner));
     }
 
+    private void unregisterAnimation(int type) {
+        mController.unregisterAnimation(type);
+    }
+
     private static class ResultListener implements RemoteCallback.OnResultListener {
         boolean mBackNavigationDone = false;
         boolean mTriggerBack = false;
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
old mode 100755
new mode 100644
index 9aa3787..15aaae2
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -18,6 +18,7 @@
 
 #include "android-base/errors.h"
 #include "android-base/logging.h"
+#include "android-base/utf8.h"
 
 namespace android {
 
@@ -83,15 +84,16 @@
     return {};
   }
 
+  std::string overlay_path(loaded_idmap->OverlayApkPath());
+  auto fd = unique_fd(base::utf8::open(overlay_path.c_str(), O_RDONLY | O_CLOEXEC));
   std::unique_ptr<AssetsProvider> overlay_assets;
-  const std::string overlay_path(loaded_idmap->OverlayApkPath());
-  if (IsFabricatedOverlay(overlay_path)) {
+  if (IsFabricatedOverlay(fd)) {
     // Fabricated overlays do not contain resource definitions. All of the overlay resource values
     // are defined inline in the idmap.
-    overlay_assets = EmptyAssetsProvider::Create(overlay_path);
+    overlay_assets = EmptyAssetsProvider::Create(std::move(overlay_path));
   } else {
     // The overlay should be an APK.
-    overlay_assets = ZipAssetsProvider::Create(overlay_path, flags);
+    overlay_assets = ZipAssetsProvider::Create(std::move(overlay_path), flags, std::move(fd));
   }
   if (overlay_assets == nullptr) {
     return {};
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 3fa369d..cc7e871 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -22,6 +22,7 @@
 #include <iterator>
 #include <map>
 #include <set>
+#include <span>
 
 #include "android-base/logging.h"
 #include "android-base/stringprintf.h"
@@ -111,7 +112,7 @@
   // A mapping from path of apk assets that could be target packages of overlays to the runtime
   // package id of its first loaded package. Overlays currently can only override resources in the
   // first package in the target resource table.
-  std::unordered_map<std::string, uint8_t> target_assets_package_ids;
+  std::unordered_map<std::string_view, uint8_t> target_assets_package_ids;
 
   // Overlay resources are not directly referenced by an application so their resource ids
   // can change throughout the application's lifetime. Assign overlay package ids last.
@@ -134,7 +135,7 @@
     if (auto loaded_idmap = apk_assets->GetLoadedIdmap(); loaded_idmap != nullptr) {
       // The target package must precede the overlay package in the apk assets paths in order
       // to take effect.
-      auto iter = target_assets_package_ids.find(std::string(loaded_idmap->TargetApkPath()));
+      auto iter = target_assets_package_ids.find(loaded_idmap->TargetApkPath());
       if (iter == target_assets_package_ids.end()) {
          LOG(INFO) << "failed to find target package for overlay "
                    << loaded_idmap->OverlayApkPath();
@@ -179,7 +180,7 @@
         if (overlay_ref_table != nullptr) {
           // If this package is from an overlay, use a dynamic reference table that can rewrite
           // overlay resource ids to their corresponding target resource ids.
-          new_group.dynamic_ref_table = overlay_ref_table;
+          new_group.dynamic_ref_table = std::move(overlay_ref_table);
         }
 
         DynamicRefTable* ref_table = new_group.dynamic_ref_table.get();
@@ -187,9 +188,9 @@
         ref_table->mAppAsLib = package->IsDynamic() && package->GetPackageId() == 0x7f;
       }
 
-      // Add the package and to the set of packages with the same ID.
+      // Add the package to the set of packages with the same ID.
       PackageGroup* package_group = &package_groups_[idx];
-      package_group->packages_.push_back(ConfiguredPackage{package.get(), {}});
+      package_group->packages_.emplace_back().loaded_package_ = package.get();
       package_group->cookies_.push_back(apk_assets_cookies[apk_assets]);
 
       // Add the package name -> build time ID mappings.
@@ -201,29 +202,38 @@
 
       if (auto apk_assets_path = apk_assets->GetPath()) {
         // Overlay target ApkAssets must have been created using path based load apis.
-        target_assets_package_ids.insert(std::make_pair(std::string(*apk_assets_path), package_id));
+        target_assets_package_ids.emplace(*apk_assets_path, package_id);
       }
     }
   }
 
   // Now assign the runtime IDs so that we have a build-time to runtime ID map.
-  const auto package_groups_end = package_groups_.end();
-  for (auto iter = package_groups_.begin(); iter != package_groups_end; ++iter) {
-    const std::string& package_name = iter->packages_[0].loaded_package_->GetPackageName();
-    for (auto iter2 = package_groups_.begin(); iter2 != package_groups_end; ++iter2) {
-      iter2->dynamic_ref_table->addMapping(String16(package_name.c_str(), package_name.size()),
-                                           iter->dynamic_ref_table->mAssignedPackageId);
-
-      // Add the alias resources to the dynamic reference table of every package group. Since
-      // staging aliases can only be defined by the framework package (which is not a shared
-      // library), the compile-time package id of the framework is the same across all packages
-      // that compile against the framework.
-      for (const auto& package : iter->packages_) {
-        for (const auto& entry : package.loaded_package_->GetAliasResourceIdMap()) {
-          iter2->dynamic_ref_table->addAlias(entry.first, entry.second);
-        }
-      }
+  DynamicRefTable::AliasMap aliases;
+  for (const auto& group : package_groups_) {
+    const std::string& package_name = group.packages_[0].loaded_package_->GetPackageName();
+    const auto name_16 = String16(package_name.c_str(), package_name.size());
+    for (auto&& inner_group : package_groups_) {
+      inner_group.dynamic_ref_table->addMapping(name_16,
+                                                group.dynamic_ref_table->mAssignedPackageId);
     }
+
+    for (const auto& package : group.packages_) {
+      const auto& package_aliases = package.loaded_package_->GetAliasResourceIdMap();
+      aliases.insert(aliases.end(), package_aliases.begin(), package_aliases.end());
+    }
+  }
+
+  if (!aliases.empty()) {
+    std::sort(aliases.begin(), aliases.end(), [](auto&& l, auto&& r) { return l.first < r.first; });
+
+    // Add the alias resources to the dynamic reference table of every package group. Since
+    // staging aliases can only be defined by the framework package (which is not a shared
+    // library), the compile-time package id of the framework is the same across all packages
+    // that compile against the framework.
+    for (auto& group : std::span(package_groups_.data(), package_groups_.size() - 1)) {
+      group.dynamic_ref_table->setAliases(aliases);
+    }
+    package_groups_.back().dynamic_ref_table->setAliases(std::move(aliases));
   }
 }
 
@@ -317,7 +327,7 @@
   return &loaded_package->GetOverlayableMap();
 }
 
-bool AssetManager2::GetOverlayablesToString(const android::StringPiece& package_name,
+bool AssetManager2::GetOverlayablesToString(android::StringPiece package_name,
                                             std::string* out) const {
   uint8_t package_id = 0U;
   for (const auto& apk_assets : apk_assets_) {
@@ -364,7 +374,7 @@
         const std::string name = ToFormattedResourceString(*res_name);
         output.append(base::StringPrintf(
             "resource='%s' overlayable='%s' actor='%s' policy='0x%08x'\n",
-            name.c_str(), info->name.c_str(), info->actor.c_str(), info->policy_flags));
+            name.c_str(), info->name.data(), info->actor.data(), info->policy_flags));
       }
     }
   }
@@ -492,7 +502,7 @@
       continue;
     }
 
-    auto func = [&](const StringPiece& name, FileType type) {
+    auto func = [&](StringPiece name, FileType type) {
       AssetDir::FileInfo info;
       info.setFileName(String8(name.data(), name.size()));
       info.setFileType(type);
@@ -1271,7 +1281,7 @@
   return result;
 }
 
-static bool Utf8ToUtf16(const StringPiece& str, std::u16string* out) {
+static bool Utf8ToUtf16(StringPiece str, std::u16string* out) {
   ssize_t len =
       utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(str.data()), str.size(), false);
   if (len < 0) {
@@ -1346,22 +1356,22 @@
 
 void AssetManager2::RebuildFilterList() {
   for (PackageGroup& group : package_groups_) {
-    for (ConfiguredPackage& impl : group.packages_) {
-      // Destroy it.
-      impl.filtered_configs_.~ByteBucketArray();
-
-      // Re-create it.
-      new (&impl.filtered_configs_) ByteBucketArray<FilteredConfigGroup>();
-
+    for (ConfiguredPackage& package : group.packages_) {
+      package.filtered_configs_.forEachItem([](auto, auto& fcg) { fcg.type_entries.clear(); });
       // Create the filters here.
-      impl.loaded_package_->ForEachTypeSpec([&](const TypeSpec& type_spec, uint8_t type_id) {
-        FilteredConfigGroup& group = impl.filtered_configs_.editItemAt(type_id - 1);
+      package.loaded_package_->ForEachTypeSpec([&](const TypeSpec& type_spec, uint8_t type_id) {
+        FilteredConfigGroup* group = nullptr;
         for (const auto& type_entry : type_spec.type_entries) {
           if (type_entry.config.match(configuration_)) {
-            group.type_entries.push_back(&type_entry);
+            if (!group) {
+              group = &package.filtered_configs_.editItemAt(type_id - 1);
+            }
+            group->type_entries.push_back(&type_entry);
           }
         }
       });
+      package.filtered_configs_.trimBuckets(
+          [](const auto& fcg) { return fcg.type_entries.empty(); });
     }
   }
 }
@@ -1402,30 +1412,34 @@
 std::unique_ptr<Theme> AssetManager2::NewTheme() {
   constexpr size_t kInitialReserveSize = 32;
   auto theme = std::unique_ptr<Theme>(new Theme(this));
+  theme->keys_.reserve(kInitialReserveSize);
   theme->entries_.reserve(kInitialReserveSize);
   return theme;
 }
 
+void AssetManager2::ForEachPackage(base::function_ref<bool(const std::string&, uint8_t)> func,
+                                   package_property_t excluded_property_flags) const {
+  for (const PackageGroup& package_group : package_groups_) {
+    const auto loaded_package = package_group.packages_.front().loaded_package_;
+    if ((loaded_package->GetPropertyFlags() & excluded_property_flags) == 0U
+        && !func(loaded_package->GetPackageName(),
+                 package_group.dynamic_ref_table->mAssignedPackageId)) {
+      return;
+    }
+  }
+}
+
 Theme::Theme(AssetManager2* asset_manager) : asset_manager_(asset_manager) {
 }
 
 Theme::~Theme() = default;
 
 struct Theme::Entry {
-  uint32_t attr_res_id;
   ApkAssetsCookie cookie;
   uint32_t type_spec_flags;
   Res_value value;
 };
 
-namespace {
-struct ThemeEntryKeyComparer {
-  bool operator() (const Theme::Entry& entry, uint32_t attr_res_id) const noexcept {
-    return entry.attr_res_id < attr_res_id;
-  }
-};
-} // namespace
-
 base::expected<std::monostate, NullOrIOError> Theme::ApplyStyle(uint32_t resid, bool force) {
   ATRACE_NAME("Theme::ApplyStyle");
 
@@ -1454,18 +1468,20 @@
       continue;
     }
 
-    auto entry_it = std::lower_bound(entries_.begin(), entries_.end(), attr_res_id,
-                                     ThemeEntryKeyComparer{});
-    if (entry_it != entries_.end() && entry_it->attr_res_id == attr_res_id) {
+    const auto key_it = std::lower_bound(keys_.begin(), keys_.end(), attr_res_id);
+    const auto entry_it = entries_.begin() + (key_it - keys_.begin());
+    if (key_it != keys_.end() && *key_it == attr_res_id) {
       if (is_undefined) {
         // DATA_NULL_UNDEFINED clears the value of the attribute in the theme only when `force` is
-        /// true.
+        // true.
+        keys_.erase(key_it);
         entries_.erase(entry_it);
       } else if (force) {
-        *entry_it = Entry{attr_res_id, it->cookie, (*bag)->type_spec_flags, it->value};
+        *entry_it = Entry{it->cookie, (*bag)->type_spec_flags, it->value};
       }
     } else {
-      entries_.insert(entry_it, Entry{attr_res_id, it->cookie, (*bag)->type_spec_flags, it->value});
+      keys_.insert(key_it, attr_res_id);
+      entries_.insert(entry_it, Entry{it->cookie, (*bag)->type_spec_flags, it->value});
     }
   }
   return {};
@@ -1476,6 +1492,7 @@
   ATRACE_NAME("Theme::Rebase");
   // Reset the entries without changing the vector capacity to prevent reallocations during
   // ApplyStyle.
+  keys_.clear();
   entries_.clear();
   asset_manager_ = am;
   for (size_t i = 0; i < style_count; i++) {
@@ -1484,16 +1501,14 @@
 }
 
 std::optional<AssetManager2::SelectedValue> Theme::GetAttribute(uint32_t resid) const {
-
   constexpr const uint32_t kMaxIterations = 20;
   uint32_t type_spec_flags = 0u;
   for (uint32_t i = 0; i <= kMaxIterations; i++) {
-    auto entry_it = std::lower_bound(entries_.begin(), entries_.end(), resid,
-                                     ThemeEntryKeyComparer{});
-    if (entry_it == entries_.end() || entry_it->attr_res_id != resid) {
+    const auto key_it = std::lower_bound(keys_.begin(), keys_.end(), resid);
+    if (key_it == keys_.end() || *key_it != resid) {
       return std::nullopt;
     }
-
+    const auto entry_it = entries_.begin() + (key_it - keys_.begin());
     type_spec_flags |= entry_it->type_spec_flags;
     if (entry_it->value.dataType == Res_value::TYPE_ATTRIBUTE) {
       resid = entry_it->value.data;
@@ -1527,6 +1542,7 @@
 }
 
 void Theme::Clear() {
+  keys_.clear();
   entries_.clear();
 }
 
@@ -1538,11 +1554,12 @@
   type_spec_flags_ = source.type_spec_flags_;
 
   if (asset_manager_ == source.asset_manager_) {
+    keys_ = source.keys_;
     entries_ = source.entries_;
   } else {
-    std::map<ApkAssetsCookie, ApkAssetsCookie> src_to_dest_asset_cookies;
-    typedef std::map<int, int> SourceToDestinationRuntimePackageMap;
-    std::map<ApkAssetsCookie, SourceToDestinationRuntimePackageMap> src_asset_cookie_id_map;
+    std::unordered_map<ApkAssetsCookie, ApkAssetsCookie> src_to_dest_asset_cookies;
+    using SourceToDestinationRuntimePackageMap = std::unordered_map<int, int>;
+    std::unordered_map<ApkAssetsCookie, SourceToDestinationRuntimePackageMap> src_asset_cookie_id_map;
 
     // Determine which ApkAssets are loaded in both theme AssetManagers.
     const auto src_assets = source.asset_manager_->GetApkAssets();
@@ -1570,15 +1587,17 @@
         }
 
         src_to_dest_asset_cookies.insert(std::make_pair(i, j));
-        src_asset_cookie_id_map.insert(std::make_pair(i, package_map));
+        src_asset_cookie_id_map.insert(std::make_pair(i, std::move(package_map)));
         break;
       }
     }
 
     // Reset the data in the destination theme.
+    keys_.clear();
     entries_.clear();
 
-    for (const auto& entry : source.entries_) {
+    for (size_t i = 0, size = source.entries_.size(); i != size; ++i) {
+      const auto& entry = source.entries_[i];
       bool is_reference = (entry.value.dataType == Res_value::TYPE_ATTRIBUTE
                            || entry.value.dataType == Res_value::TYPE_REFERENCE
                            || entry.value.dataType == Res_value::TYPE_DYNAMIC_ATTRIBUTE
@@ -1618,13 +1637,15 @@
         }
       }
 
+      const auto source_res_id = source.keys_[i];
+
       // The package id of the attribute needs to be rewritten to the package id of the
       // attribute in the destination.
-      int attribute_dest_package_id = get_package_id(entry.attr_res_id);
+      int attribute_dest_package_id = get_package_id(source_res_id);
       if (attribute_dest_package_id != 0x01) {
         // Find the cookie of the attribute resource id in the source AssetManager
         base::expected<FindEntryResult, NullOrIOError> attribute_entry_result =
-            source.asset_manager_->FindEntry(entry.attr_res_id, 0 /* density_override */ ,
+            source.asset_manager_->FindEntry(source_res_id, 0 /* density_override */ ,
                                              true /* stop_at_first_match */,
                                              true /* ignore_configuration */);
         if (UNLIKELY(IsIOError(attribute_entry_result))) {
@@ -1648,16 +1669,15 @@
         attribute_dest_package_id = attribute_dest_package->second;
       }
 
-      auto dest_attr_id = make_resid(attribute_dest_package_id, get_type_id(entry.attr_res_id),
-                                     get_entry_id(entry.attr_res_id));
-      Theme::Entry new_entry{dest_attr_id, data_dest_cookie, entry.type_spec_flags,
-                                            Res_value{.dataType = entry.value.dataType,
-                                                      .data = attribute_data}};
-
+      auto dest_attr_id = make_resid(attribute_dest_package_id, get_type_id(source_res_id),
+                                     get_entry_id(source_res_id));
+      const auto key_it = std::lower_bound(keys_.begin(), keys_.end(), dest_attr_id);
+      const auto entry_it = entries_.begin() + (key_it - keys_.begin());
       // Since the entries were cleared, the attribute resource id has yet been mapped to any value.
-      auto entry_it = std::lower_bound(entries_.begin(), entries_.end(), dest_attr_id,
-                                       ThemeEntryKeyComparer{});
-      entries_.insert(entry_it, new_entry);
+      keys_.insert(key_it, dest_attr_id);
+      entries_.insert(entry_it, Entry{data_dest_cookie, entry.type_spec_flags,
+                                      Res_value{.dataType = entry.value.dataType,
+                                                .data = attribute_data}});
     }
   }
   return {};
@@ -1665,9 +1685,11 @@
 
 void Theme::Dump() const {
   LOG(INFO) << base::StringPrintf("Theme(this=%p, AssetManager2=%p)", this, asset_manager_);
-  for (auto& entry : entries_) {
+  for (size_t i = 0, size = keys_.size(); i != size; ++i) {
+    auto res_id = keys_[i];
+    const auto& entry = entries_[i];
     LOG(INFO) << base::StringPrintf("  entry(0x%08x)=(0x%08x) type=(0x%02x), cookie(%d)",
-                                    entry.attr_res_id, entry.value.data, entry.value.dataType,
+                                    res_id, entry.value.data, entry.value.dataType,
                                     entry.cookie);
   }
 }
diff --git a/libs/androidfw/AssetsProvider.cpp b/libs/androidfw/AssetsProvider.cpp
index bce34d3..b9264c5 100644
--- a/libs/androidfw/AssetsProvider.cpp
+++ b/libs/androidfw/AssetsProvider.cpp
@@ -92,21 +92,27 @@
       last_mod_time_(last_mod_time) {}
 
 std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path,
-                                                             package_property_t flags) {
+                                                             package_property_t flags,
+                                                             base::unique_fd fd) {
+  const auto released_fd = fd.ok() ? fd.release() : -1;
   ZipArchiveHandle handle;
-  if (int32_t result = OpenArchive(path.c_str(), &handle); result != 0) {
+  if (int32_t result = released_fd < 0 ? OpenArchive(path.c_str(), &handle)
+                                       : OpenArchiveFd(released_fd, path.c_str(), &handle)) {
     LOG(ERROR) << "Failed to open APK '" << path << "': " << ::ErrorCodeString(result);
     CloseArchive(handle);
     return {};
   }
 
   struct stat sb{.st_mtime = -1};
-  if (stat(path.c_str(), &sb) < 0) {
-    // Stat requires execute permissions on all directories path to the file. If the process does
-    // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
-    // always have to return true.
-    LOG(WARNING) << "Failed to stat file '" << path << "': "
-                 << base::SystemErrorCodeToString(errno);
+  // Skip all up-to-date checks if the file won't ever change.
+  if (!isReadonlyFilesystem(path.c_str())) {
+    if ((released_fd < 0 ? stat(path.c_str(), &sb) : fstat(released_fd, &sb)) < 0) {
+      // Stat requires execute permissions on all directories path to the file. If the process does
+      // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
+      // always have to return true.
+      LOG(WARNING) << "Failed to stat file '" << path << "': "
+                   << base::SystemErrorCodeToString(errno);
+    }
   }
 
   return std::unique_ptr<ZipAssetsProvider>(
@@ -133,12 +139,15 @@
   }
 
   struct stat sb{.st_mtime = -1};
-  if (fstat(released_fd, &sb) < 0) {
-    // Stat requires execute permissions on all directories path to the file. If the process does
-    // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
-    // always have to return true.
-    LOG(WARNING) << "Failed to fstat file '" << friendly_name << "': "
-                 << base::SystemErrorCodeToString(errno);
+  // Skip all up-to-date checks if the file won't ever change.
+  if (!isReadonlyFilesystem(released_fd)) {
+    if (fstat(released_fd, &sb) < 0) {
+      // Stat requires execute permissions on all directories path to the file. If the process does
+      // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
+      // always have to return true.
+      LOG(WARNING) << "Failed to fstat file '" << friendly_name
+                   << "': " << base::SystemErrorCodeToString(errno);
+    }
   }
 
   return std::unique_ptr<ZipAssetsProvider>(
@@ -211,8 +220,7 @@
 }
 
 bool ZipAssetsProvider::ForEachFile(const std::string& root_path,
-                                    const std::function<void(const StringPiece&, FileType)>& f)
-                                    const {
+                                    const std::function<void(StringPiece, FileType)>& f) const {
     std::string root_path_full = root_path;
     if (root_path_full.back() != '/') {
       root_path_full += '/';
@@ -238,8 +246,7 @@
       if (!leaf_file_path.empty()) {
         auto iter = std::find(leaf_file_path.begin(), leaf_file_path.end(), '/');
         if (iter != leaf_file_path.end()) {
-          std::string dir =
-              leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter)).to_string();
+          std::string dir(leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter)));
           dirs.insert(std::move(dir));
         } else {
           f(leaf_file_path, kFileTypeRegular);
@@ -277,6 +284,9 @@
 }
 
 bool ZipAssetsProvider::IsUpToDate() const {
+  if (last_mod_time_ == -1) {
+    return true;
+  }
   struct stat sb{};
   if (fstat(GetFileDescriptor(zip_handle_.get()), &sb) < 0) {
     // If fstat fails on the zip archive, return true so the zip archive the resource system does
@@ -290,7 +300,7 @@
     : dir_(std::forward<std::string>(path)), last_mod_time_(last_mod_time) {}
 
 std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::string path) {
-  struct stat sb{};
+  struct stat sb;
   const int result = stat(path.c_str(), &sb);
   if (result == -1) {
     LOG(ERROR) << "Failed to find directory '" << path << "'.";
@@ -306,8 +316,9 @@
     path += OS_PATH_SEPARATOR;
   }
 
-  return std::unique_ptr<DirectoryAssetsProvider>(new DirectoryAssetsProvider(std::move(path),
-                                                                              sb.st_mtime));
+  const bool isReadonly = isReadonlyFilesystem(path.c_str());
+  return std::unique_ptr<DirectoryAssetsProvider>(
+      new DirectoryAssetsProvider(std::move(path), isReadonly ? -1 : sb.st_mtime));
 }
 
 std::unique_ptr<Asset> DirectoryAssetsProvider::OpenInternal(const std::string& path,
@@ -324,8 +335,7 @@
 
 bool DirectoryAssetsProvider::ForEachFile(
     const std::string& /* root_path */,
-    const std::function<void(const StringPiece&, FileType)>& /* f */)
-    const {
+    const std::function<void(StringPiece, FileType)>& /* f */) const {
   return true;
 }
 
@@ -338,7 +348,10 @@
 }
 
 bool DirectoryAssetsProvider::IsUpToDate() const {
-  struct stat sb{};
+  if (last_mod_time_ == -1) {
+    return true;
+  }
+  struct stat sb;
   if (stat(dir_.c_str(), &sb) < 0) {
     // If stat fails on the zip archive, return true so the zip archive the resource system does
     // attempt to refresh the ApkAsset.
@@ -373,8 +386,7 @@
 }
 
 bool MultiAssetsProvider::ForEachFile(const std::string& root_path,
-                                      const std::function<void(const StringPiece&, FileType)>& f)
-                                      const {
+                                      const std::function<void(StringPiece, FileType)>& f) const {
   return primary_->ForEachFile(root_path, f) && secondary_->ForEachFile(root_path, f);
 }
 
@@ -397,8 +409,8 @@
   return std::unique_ptr<EmptyAssetsProvider>(new EmptyAssetsProvider({}));
 }
 
-std::unique_ptr<AssetsProvider> EmptyAssetsProvider::Create(const std::string& path) {
-  return std::unique_ptr<EmptyAssetsProvider>(new EmptyAssetsProvider(path));
+std::unique_ptr<AssetsProvider> EmptyAssetsProvider::Create(std::string path) {
+  return std::unique_ptr<EmptyAssetsProvider>(new EmptyAssetsProvider(std::move(path)));
 }
 
 std::unique_ptr<Asset> EmptyAssetsProvider::OpenInternal(const std::string& /* path */,
@@ -412,7 +424,7 @@
 
 bool EmptyAssetsProvider::ForEachFile(
     const std::string& /* root_path */,
-    const std::function<void(const StringPiece&, FileType)>& /* f */) const {
+    const std::function<void(StringPiece, FileType)>& /* f */) const {
   return true;
 }
 
@@ -435,4 +447,4 @@
   return true;
 }
 
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/libs/androidfw/ConfigDescription.cpp b/libs/androidfw/ConfigDescription.cpp
index 19ead95..93a7d17 100644
--- a/libs/androidfw/ConfigDescription.cpp
+++ b/libs/androidfw/ConfigDescription.cpp
@@ -637,7 +637,7 @@
   return true;
 }
 
-bool ConfigDescription::Parse(const StringPiece& str, ConfigDescription* out) {
+bool ConfigDescription::Parse(StringPiece str, ConfigDescription* out) {
   std::vector<std::string> parts = util::SplitAndLowercase(str, '-');
 
   ConfigDescription config;
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index e122d48..f3d2443 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -274,8 +274,7 @@
        target_apk_path_(target_apk_path),
        idmap_last_mod_time_(getFileModDate(idmap_path_.data())) {}
 
-std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_path,
-                                               const StringPiece& idmap_data) {
+std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPiece idmap_data) {
   ATRACE_CALL();
   size_t data_size = idmap_data.size();
   auto data_ptr = reinterpret_cast<const uint8_t*>(idmap_data.data());
@@ -365,7 +364,7 @@
 
   // Can't use make_unique because LoadedIdmap constructor is private.
   return std::unique_ptr<LoadedIdmap>(
-      new LoadedIdmap(idmap_path.to_string(), header, data_header, target_entries,
+      new LoadedIdmap(std::string(idmap_path), header, data_header, target_entries,
                       target_inline_entries, target_inline_entry_values, configurations,
                       overlay_entries, std::move(idmap_string_pool), *target_path, *overlay_path));
 }
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index 386f718..c0fdfe2 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -645,16 +645,16 @@
         }
 
         std::string name;
-        util::ReadUtf16StringFromDevice(overlayable->name, arraysize(overlayable->name), &name);
+        util::ReadUtf16StringFromDevice(overlayable->name, std::size(overlayable->name), &name);
         std::string actor;
-        util::ReadUtf16StringFromDevice(overlayable->actor, arraysize(overlayable->actor), &actor);
-
-        if (loaded_package->overlayable_map_.find(name) !=
-            loaded_package->overlayable_map_.end()) {
-          LOG(ERROR) << "Multiple <overlayable> blocks with the same name '" << name << "'.";
+        util::ReadUtf16StringFromDevice(overlayable->actor, std::size(overlayable->actor), &actor);
+        auto [name_to_actor_it, inserted] =
+            loaded_package->overlayable_map_.emplace(std::move(name), std::move(actor));
+        if (!inserted) {
+          LOG(ERROR) << "Multiple <overlayable> blocks with the same name '"
+                     << name_to_actor_it->first << "'.";
           return {};
         }
-        loaded_package->overlayable_map_.emplace(name, actor);
 
         // Iterate over the overlayable policy chunks contained within the overlayable chunk data
         ChunkIterator overlayable_iter(child_chunk.data_ptr(), child_chunk.data_size());
@@ -669,7 +669,6 @@
                 LOG(ERROR) << "RES_TABLE_OVERLAYABLE_POLICY_TYPE too small.";
                 return {};
               }
-
               if ((overlayable_child_chunk.data_size() / sizeof(ResTable_ref))
                   < dtohl(policy_header->entry_count)) {
                 LOG(ERROR) <<  "RES_TABLE_OVERLAYABLE_POLICY_TYPE too small to hold entries.";
@@ -691,8 +690,8 @@
 
               // Add the pairing of overlayable properties and resource ids to the package
               OverlayableInfo overlayable_info {
-                .name = name,
-                .actor = actor,
+                .name = name_to_actor_it->first,
+                .actor = name_to_actor_it->second,
                 .policy_flags = policy_header->policy_flags
               };
               loaded_package->overlayable_infos_.emplace_back(std::move(overlayable_info), std::move(ids));
@@ -736,6 +735,7 @@
         const auto entry_end = entry_begin + dtohl(lib_alias->count);
         std::unordered_set<uint32_t> finalized_ids;
         finalized_ids.reserve(entry_end - entry_begin);
+        loaded_package->alias_id_map_.reserve(entry_end - entry_begin);
         for (auto entry_iter = entry_begin; entry_iter != entry_end; ++entry_iter) {
           if (!entry_iter) {
             LOG(ERROR) << "NULL ResTable_staged_alias_entry record??";
@@ -749,13 +749,20 @@
           }
 
           auto staged_id = dtohl(entry_iter->stagedResId);
-          auto [_, success] = loaded_package->alias_id_map_.emplace(staged_id, finalized_id);
-          if (!success) {
+          loaded_package->alias_id_map_.emplace_back(staged_id, finalized_id);
+        }
+
+        std::sort(loaded_package->alias_id_map_.begin(), loaded_package->alias_id_map_.end(),
+            [](auto&& l, auto&& r) { return l.first < r.first; });
+        const auto duplicate_it =
+            std::adjacent_find(loaded_package->alias_id_map_.begin(),
+                               loaded_package->alias_id_map_.end(),
+                               [](auto&& l, auto&& r) { return l.first == r.first; });
+          if (duplicate_it != loaded_package->alias_id_map_.end()) {
             LOG(ERROR) << StringPrintf("Repeated staged resource id '%08x' in staged aliases.",
-                                       staged_id);
+                                       duplicate_it->first);
             return {};
           }
-        }
       } break;
 
       default:
diff --git a/libs/androidfw/Locale.cpp b/libs/androidfw/Locale.cpp
index d87a3ce..272a988 100644
--- a/libs/androidfw/Locale.cpp
+++ b/libs/androidfw/Locale.cpp
@@ -66,7 +66,7 @@
   return std::all_of(std::begin(str), std::end(str), ::isdigit);
 }
 
-bool LocaleValue::InitFromFilterString(const StringPiece& str) {
+bool LocaleValue::InitFromFilterString(StringPiece str) {
   // A locale (as specified in the filter) is an underscore separated name such
   // as "en_US", "en_Latn_US", or "en_US_POSIX".
   std::vector<std::string> parts = util::SplitAndLowercase(str, '_');
@@ -132,11 +132,11 @@
   return true;
 }
 
-bool LocaleValue::InitFromBcp47Tag(const StringPiece& bcp47tag) {
+bool LocaleValue::InitFromBcp47Tag(StringPiece bcp47tag) {
   return InitFromBcp47TagImpl(bcp47tag, '-');
 }
 
-bool LocaleValue::InitFromBcp47TagImpl(const StringPiece& bcp47tag, const char separator) {
+bool LocaleValue::InitFromBcp47TagImpl(StringPiece bcp47tag, const char separator) {
   std::vector<std::string> subtags = util::SplitAndLowercase(bcp47tag, separator);
   if (subtags.size() == 1) {
     set_language(subtags[0].c_str());
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 267190a..31516dc 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -33,7 +33,9 @@
 #include <type_traits>
 #include <vector>
 
+#include <android-base/file.h>
 #include <android-base/macros.h>
+#include <android-base/utf8.h>
 #include <androidfw/ByteBucketArray.h>
 #include <androidfw/ResourceTypes.h>
 #include <androidfw/TypeWrappers.h>
@@ -236,12 +238,23 @@
 }
 
 bool IsFabricatedOverlay(const std::string& path) {
-  std::ifstream fin(path);
-  uint32_t magic;
-  if (fin.read(reinterpret_cast<char*>(&magic), sizeof(uint32_t))) {
-    return magic == kFabricatedOverlayMagic;
+  return IsFabricatedOverlay(path.c_str());
+}
+
+bool IsFabricatedOverlay(const char* path) {
+  auto fd = base::unique_fd(base::utf8::open(path, O_RDONLY|O_CLOEXEC));
+  if (fd < 0) {
+    return false;
   }
-  return false;
+  return IsFabricatedOverlay(fd);
+}
+
+bool IsFabricatedOverlay(base::borrowed_fd fd) {
+  uint32_t magic;
+  if (!base::ReadFullyAtOffset(fd, &magic, sizeof(magic), 0)) {
+    return false;
+  }
+  return magic == kFabricatedOverlayMagic;
 }
 
 static bool assertIdmapHeader(const void* idmap, size_t size) {
@@ -6988,11 +7001,10 @@
 DynamicRefTable::DynamicRefTable() : DynamicRefTable(0, false) {}
 
 DynamicRefTable::DynamicRefTable(uint8_t packageId, bool appAsLib)
-    : mAssignedPackageId(packageId)
+    : mLookupTable()
+    , mAssignedPackageId(packageId)
     , mAppAsLib(appAsLib)
 {
-    memset(mLookupTable, 0, sizeof(mLookupTable));
-
     // Reserved package ids
     mLookupTable[APP_PACKAGE_ID] = APP_PACKAGE_ID;
     mLookupTable[SYS_PACKAGE_ID] = SYS_PACKAGE_ID;
@@ -7073,10 +7085,6 @@
     mLookupTable[buildPackageId] = runtimePackageId;
 }
 
-void DynamicRefTable::addAlias(uint32_t stagedId, uint32_t finalizedId) {
-  mAliasId[stagedId] = finalizedId;
-}
-
 status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const {
     uint32_t res = *resId;
     size_t packageId = Res_GETPACKAGE(res) + 1;
@@ -7086,11 +7094,12 @@
         return NO_ERROR;
     }
 
-    auto alias_id = mAliasId.find(res);
-    if (alias_id != mAliasId.end()) {
+    const auto alias_it = std::lower_bound(mAliasId.begin(), mAliasId.end(), res,
+        [](const AliasMap::value_type& pair, uint32_t val) { return pair.first < val; });
+    if (alias_it != mAliasId.end() && alias_it->first == res) {
       // Rewrite the resource id to its alias resource id. Since the alias resource id is a
       // compile-time id, it still needs to be resolved further.
-      res = alias_id->second;
+      res = alias_it->second;
     }
 
     if (packageId == SYS_PACKAGE_ID || (packageId == APP_PACKAGE_ID && !mAppAsLib)) {
diff --git a/libs/androidfw/ResourceUtils.cpp b/libs/androidfw/ResourceUtils.cpp
index 87fb2c0..ccb6156 100644
--- a/libs/androidfw/ResourceUtils.cpp
+++ b/libs/androidfw/ResourceUtils.cpp
@@ -18,7 +18,7 @@
 
 namespace android {
 
-bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, StringPiece* out_type,
+bool ExtractResourceName(StringPiece str, StringPiece* out_package, StringPiece* out_type,
                          StringPiece* out_entry) {
   *out_package = "";
   *out_type = "";
@@ -33,16 +33,16 @@
   while (current != end) {
     if (out_type->size() == 0 && *current == '/') {
       has_type_separator = true;
-      out_type->assign(start, current - start);
+      *out_type = StringPiece(start, current - start);
       start = current + 1;
     } else if (out_package->size() == 0 && *current == ':') {
       has_package_separator = true;
-      out_package->assign(start, current - start);
+      *out_package = StringPiece(start, current - start);
       start = current + 1;
     }
     current++;
   }
-  out_entry->assign(start, end - start);
+  *out_entry = StringPiece(start, end - start);
 
   return !(has_package_separator && out_package->empty()) &&
          !(has_type_separator && out_type->empty());
@@ -50,7 +50,7 @@
 
 base::expected<AssetManager2::ResourceName, NullOrIOError> ToResourceName(
     const StringPoolRef& type_string_ref, const StringPoolRef& entry_string_ref,
-    const StringPiece& package_name) {
+    StringPiece package_name) {
   AssetManager2::ResourceName name{
     .package = package_name.data(),
     .package_len = package_name.size(),
diff --git a/libs/androidfw/StringPool.cpp b/libs/androidfw/StringPool.cpp
index b59e906..1cb8df3 100644
--- a/libs/androidfw/StringPool.cpp
+++ b/libs/androidfw/StringPool.cpp
@@ -161,16 +161,15 @@
   return entry_->context;
 }
 
-StringPool::Ref StringPool::MakeRef(const StringPiece& str) {
+StringPool::Ref StringPool::MakeRef(StringPiece str) {
   return MakeRefImpl(str, Context{}, true);
 }
 
-StringPool::Ref StringPool::MakeRef(const StringPiece& str, const Context& context) {
+StringPool::Ref StringPool::MakeRef(StringPiece str, const Context& context) {
   return MakeRefImpl(str, context, true);
 }
 
-StringPool::Ref StringPool::MakeRefImpl(const StringPiece& str, const Context& context,
-                                        bool unique) {
+StringPool::Ref StringPool::MakeRefImpl(StringPiece str, const Context& context, bool unique) {
   if (unique) {
     auto range = indexed_strings_.equal_range(str);
     for (auto iter = range.first; iter != range.second; ++iter) {
@@ -181,7 +180,7 @@
   }
 
   std::unique_ptr<Entry> entry(new Entry());
-  entry->value = str.to_string();
+  entry->value = std::string(str);
   entry->context = context;
   entry->index_ = strings_.size();
   entry->ref_ = 0;
diff --git a/libs/androidfw/Util.cpp b/libs/androidfw/Util.cpp
index 52ad0dc..be55fe8 100644
--- a/libs/androidfw/Util.cpp
+++ b/libs/androidfw/Util.cpp
@@ -42,7 +42,7 @@
   }
 }
 
-std::u16string Utf8ToUtf16(const StringPiece& utf8) {
+std::u16string Utf8ToUtf16(StringPiece utf8) {
   ssize_t utf16_length =
       utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length());
   if (utf16_length <= 0) {
@@ -56,7 +56,7 @@
   return utf16;
 }
 
-std::string Utf16ToUtf8(const StringPiece16& utf16) {
+std::string Utf16ToUtf8(StringPiece16 utf16) {
   ssize_t utf8_length = utf16_to_utf8_length(utf16.data(), utf16.length());
   if (utf8_length <= 0) {
     return {};
@@ -68,7 +68,7 @@
   return utf8;
 }
 
-std::string Utf8ToModifiedUtf8(const std::string& utf8) {
+std::string Utf8ToModifiedUtf8(std::string_view utf8) {
   // Java uses Modified UTF-8 which only supports the 1, 2, and 3 byte formats of UTF-8. To encode
   // 4 byte UTF-8 codepoints, Modified UTF-8 allows the use of surrogate pairs in the same format
   // of CESU-8 surrogate pairs. Calculate the size of the utf8 string with all 4 byte UTF-8
@@ -86,7 +86,7 @@
 
   // Early out if no 4 byte codepoints are found
   if (size == modified_size) {
-    return utf8;
+    return std::string(utf8);
   }
 
   std::string output;
@@ -115,7 +115,7 @@
   return output;
 }
 
-std::string ModifiedUtf8ToUtf8(const std::string& modified_utf8) {
+std::string ModifiedUtf8ToUtf8(std::string_view modified_utf8) {
   // The UTF-8 representation will have a byte length less than or equal to the Modified UTF-8
   // representation.
   std::string output;
@@ -170,30 +170,28 @@
   return output;
 }
 
-static std::vector<std::string> SplitAndTransform(
-    const StringPiece& str, char sep, const std::function<char(char)>& f) {
+template <class Func>
+static std::vector<std::string> SplitAndTransform(StringPiece str, char sep, Func&& f) {
   std::vector<std::string> parts;
   const StringPiece::const_iterator end = std::end(str);
   StringPiece::const_iterator start = std::begin(str);
   StringPiece::const_iterator current;
   do {
     current = std::find(start, end, sep);
-    parts.emplace_back(str.substr(start, current).to_string());
-    if (f) {
-      std::string& part = parts.back();
-      std::transform(part.begin(), part.end(), part.begin(), f);
-    }
+    parts.emplace_back(StringPiece(start, current - start));
+    std::string& part = parts.back();
+    std::transform(part.begin(), part.end(), part.begin(), f);
     start = current + 1;
   } while (current != end);
   return parts;
 }
 
-std::vector<std::string> SplitAndLowercase(const StringPiece& str, char sep) {
-  return SplitAndTransform(str, sep, ::tolower);
+std::vector<std::string> SplitAndLowercase(StringPiece str, char sep) {
+  return SplitAndTransform(str, sep, [](char c) { return ::tolower(c); });
 }
 
 std::unique_ptr<uint8_t[]> Copy(const BigBuffer& buffer) {
-  std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>(new uint8_t[buffer.size()]);
+  auto data = std::unique_ptr<uint8_t[]>(new uint8_t[buffer.size()]);
   uint8_t* p = data.get();
   for (const auto& block : buffer) {
     memcpy(p, block.buffer.get(), block.size);
@@ -211,7 +209,7 @@
 
 std::string GetString(const android::ResStringPool& pool, size_t idx) {
   if (auto str = pool.string8At(idx); str.ok()) {
-    return ModifiedUtf8ToUtf8(str->to_string());
+    return ModifiedUtf8ToUtf8(*str);
   }
   return Utf16ToUtf8(GetString16(pool, idx));
 }
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index 1bde792..e4d1218 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -17,6 +17,7 @@
 #ifndef ANDROIDFW_ASSETMANAGER2_H_
 #define ANDROIDFW_ASSETMANAGER2_H_
 
+#include "android-base/function_ref.h"
 #include "android-base/macros.h"
 
 #include <array>
@@ -124,8 +125,7 @@
   uint8_t GetAssignedPackageId(const LoadedPackage* package) const;
 
   // Returns a string representation of the overlayable API of a package.
-  bool GetOverlayablesToString(const android::StringPiece& package_name,
-                               std::string* out) const;
+  bool GetOverlayablesToString(android::StringPiece package_name, std::string* out) const;
 
   const std::unordered_map<std::string, std::string>* GetOverlayableMapForPackage(
       uint32_t package_id) const;
@@ -321,17 +321,8 @@
   // Creates a new Theme from this AssetManager.
   std::unique_ptr<Theme> NewTheme();
 
-  void ForEachPackage(const std::function<bool(const std::string&, uint8_t)> func,
-                      package_property_t excluded_property_flags = 0U) const {
-    for (const PackageGroup& package_group : package_groups_) {
-      const auto loaded_package = package_group.packages_.front().loaded_package_;
-      if ((loaded_package->GetPropertyFlags() & excluded_property_flags) == 0U
-          && !func(loaded_package->GetPackageName(),
-                   package_group.dynamic_ref_table->mAssignedPackageId)) {
-        return;
-      }
-    }
-  }
+  void ForEachPackage(base::function_ref<bool(const std::string&, uint8_t)> func,
+                      package_property_t excluded_property_flags = 0U) const;
 
   void DumpToLog() const;
 
@@ -572,6 +563,7 @@
   AssetManager2* asset_manager_ = nullptr;
   uint32_t type_spec_flags_ = 0u;
 
+  std::vector<uint32_t> keys_;
   std::vector<Entry> entries_;
 };
 
diff --git a/libs/androidfw/include/androidfw/AssetsProvider.h b/libs/androidfw/include/androidfw/AssetsProvider.h
index 966ec74..7891194 100644
--- a/libs/androidfw/include/androidfw/AssetsProvider.h
+++ b/libs/androidfw/include/androidfw/AssetsProvider.h
@@ -46,7 +46,7 @@
   // Iterate over all files and directories provided by the interface. The order of iteration is
   // stable.
   virtual bool ForEachFile(const std::string& path,
-                           const std::function<void(const StringPiece&, FileType)>& f) const = 0;
+                           const std::function<void(StringPiece, FileType)>& f) const = 0;
 
   // Retrieves the path to the contents of the AssetsProvider on disk. The path could represent an
   // APk, a directory, or some other file type.
@@ -80,8 +80,8 @@
 
 // Supplies assets from a zip archive.
 struct ZipAssetsProvider : public AssetsProvider {
-  static std::unique_ptr<ZipAssetsProvider> Create(std::string path,
-                                                   package_property_t flags);
+  static std::unique_ptr<ZipAssetsProvider> Create(std::string path, package_property_t flags,
+                                                   base::unique_fd fd = {});
 
   static std::unique_ptr<ZipAssetsProvider> Create(base::unique_fd fd,
                                                    std::string friendly_name,
@@ -90,7 +90,7 @@
                                                    off64_t len = kUnknownLength);
 
   bool ForEachFile(const std::string& root_path,
-                   const std::function<void(const StringPiece&, FileType)>& f) const override;
+                   const std::function<void(StringPiece, FileType)>& f) const override;
 
   WARN_UNUSED std::optional<std::string_view> GetPath() const override;
   WARN_UNUSED const std::string& GetDebugName() const override;
@@ -132,7 +132,7 @@
   static std::unique_ptr<DirectoryAssetsProvider> Create(std::string root_dir);
 
   bool ForEachFile(const std::string& path,
-                   const std::function<void(const StringPiece&, FileType)>& f) const override;
+                   const std::function<void(StringPiece, FileType)>& f) const override;
 
   WARN_UNUSED std::optional<std::string_view> GetPath() const override;
   WARN_UNUSED const std::string& GetDebugName() const override;
@@ -157,7 +157,7 @@
                                                 std::unique_ptr<AssetsProvider>&& secondary);
 
   bool ForEachFile(const std::string& root_path,
-                   const std::function<void(const StringPiece&, FileType)>& f) const override;
+                   const std::function<void(StringPiece, FileType)>& f) const override;
 
   WARN_UNUSED std::optional<std::string_view> GetPath() const override;
   WARN_UNUSED const std::string& GetDebugName() const override;
@@ -181,10 +181,10 @@
 // Does not provide any assets.
 struct EmptyAssetsProvider : public AssetsProvider {
   static std::unique_ptr<AssetsProvider> Create();
-  static std::unique_ptr<AssetsProvider> Create(const std::string& path);
+  static std::unique_ptr<AssetsProvider> Create(std::string path);
 
   bool ForEachFile(const std::string& path,
-                  const std::function<void(const StringPiece&, FileType)>& f) const override;
+                   const std::function<void(StringPiece, FileType)>& f) const override;
 
   WARN_UNUSED std::optional<std::string_view> GetPath() const override;
   WARN_UNUSED const std::string& GetDebugName() const override;
diff --git a/libs/androidfw/include/androidfw/ByteBucketArray.h b/libs/androidfw/include/androidfw/ByteBucketArray.h
index 949c9445..ca0a9ed 100644
--- a/libs/androidfw/include/androidfw/ByteBucketArray.h
+++ b/libs/androidfw/include/androidfw/ByteBucketArray.h
@@ -17,6 +17,7 @@
 #ifndef __BYTE_BUCKET_ARRAY_H
 #define __BYTE_BUCKET_ARRAY_H
 
+#include <algorithm>
 #include <cstdint>
 #include <cstring>
 
@@ -31,14 +32,16 @@
 template <typename T>
 class ByteBucketArray {
  public:
-  ByteBucketArray() : default_() { memset(buckets_, 0, sizeof(buckets_)); }
+  ByteBucketArray() {
+    memset(buckets_, 0, sizeof(buckets_));
+  }
 
   ~ByteBucketArray() {
-    for (size_t i = 0; i < kNumBuckets; i++) {
-      if (buckets_[i] != NULL) {
-        delete[] buckets_[i];
-      }
-    }
+    deleteBuckets();
+  }
+
+  void clear() {
+    deleteBuckets();
     memset(buckets_, 0, sizeof(buckets_));
   }
 
@@ -53,7 +56,7 @@
 
     uint8_t bucket_index = static_cast<uint8_t>(index) >> 4;
     T* bucket = buckets_[bucket_index];
-    if (bucket == NULL) {
+    if (bucket == nullptr) {
       return default_;
     }
     return bucket[0x0f & static_cast<uint8_t>(index)];
@@ -64,9 +67,9 @@
                           << ") with size=" << size();
 
     uint8_t bucket_index = static_cast<uint8_t>(index) >> 4;
-    T* bucket = buckets_[bucket_index];
-    if (bucket == NULL) {
-      bucket = buckets_[bucket_index] = new T[kBucketSize]();
+    T*& bucket = buckets_[bucket_index];
+    if (bucket == nullptr) {
+      bucket = new T[kBucketSize]();
     }
     return bucket[0x0f & static_cast<uint8_t>(index)];
   }
@@ -80,11 +83,44 @@
     return true;
   }
 
+  template <class Func>
+  void forEachItem(Func f) {
+    for (size_t i = 0; i < kNumBuckets; i++) {
+      const auto bucket = buckets_[i];
+      if (bucket != nullptr) {
+        for (size_t j = 0; j < kBucketSize; j++) {
+          f((i << 4) | j, bucket[j]);
+        }
+      }
+    }
+  }
+
+  template <class Func>
+  void trimBuckets(Func isEmptyFunc) {
+    for (size_t i = 0; i < kNumBuckets; i++) {
+      const auto bucket = buckets_[i];
+      if (bucket != nullptr) {
+        if (std::all_of(bucket, bucket + kBucketSize, isEmptyFunc)) {
+          delete[] bucket;
+          buckets_[i] = nullptr;
+        }
+      }
+    }
+  }
+
  private:
   enum { kNumBuckets = 16, kBucketSize = 16 };
 
+  void deleteBuckets() {
+    for (size_t i = 0; i < kNumBuckets; i++) {
+      if (buckets_[i] != nullptr) {
+        delete[] buckets_[i];
+      }
+    }
+  }
+
   T* buckets_[kNumBuckets];
-  T default_;
+  static inline const T default_ = {};
 };
 
 }  // namespace android
diff --git a/libs/androidfw/include/androidfw/ConfigDescription.h b/libs/androidfw/include/androidfw/ConfigDescription.h
index 61d10cd..71087cd 100644
--- a/libs/androidfw/include/androidfw/ConfigDescription.h
+++ b/libs/androidfw/include/androidfw/ConfigDescription.h
@@ -72,7 +72,7 @@
    * The resulting configuration has the appropriate sdkVersion defined
    * for backwards compatibility.
    */
-  static bool Parse(const android::StringPiece& str, ConfigDescription* out = nullptr);
+  static bool Parse(android::StringPiece str, ConfigDescription* out = nullptr);
 
   /**
    * If the configuration uses an axis that was added after
diff --git a/libs/androidfw/include/androidfw/IDiagnostics.h b/libs/androidfw/include/androidfw/IDiagnostics.h
index 273b05a..4d5844e 100644
--- a/libs/androidfw/include/androidfw/IDiagnostics.h
+++ b/libs/androidfw/include/androidfw/IDiagnostics.h
@@ -35,7 +35,7 @@
  public:
   DiagMessage() = default;
 
-  explicit DiagMessage(const android::StringPiece& src) : source_(src) {
+  explicit DiagMessage(android::StringPiece src) : source_(src) {
   }
 
   explicit DiagMessage(const Source& src) : source_(src) {
@@ -61,7 +61,7 @@
 
 template <>
 inline DiagMessage& DiagMessage::operator<<(const ::std::u16string& value) {
-  message_ << android::StringPiece16(value);
+  message_ << value;
   return *this;
 }
 
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index a1cbbbf..f173e6e 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -157,8 +157,7 @@
 class LoadedIdmap {
  public:
   // Loads an IDMAP from a chunk of memory. Returns nullptr if the IDMAP data was malformed.
-  static std::unique_ptr<LoadedIdmap> Load(const StringPiece& idmap_path,
-                                           const StringPiece& idmap_data);
+  static std::unique_ptr<LoadedIdmap> Load(StringPiece idmap_path, StringPiece idmap_data);
 
   // Returns the path to the IDMAP.
   std::string_view IdmapPath() const {
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index 79d96282..4d12885 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -99,8 +99,8 @@
 };
 
 struct OverlayableInfo {
-  std::string name;
-  std::string actor;
+  std::string_view name;
+  std::string_view actor;
   uint32_t policy_flags;
 };
 
@@ -275,7 +275,7 @@
     return overlayable_map_;
   }
 
-  const std::map<uint32_t, uint32_t>& GetAliasResourceIdMap() const {
+  const std::vector<std::pair<uint32_t, uint32_t>>& GetAliasResourceIdMap() const {
     return alias_id_map_;
   }
 
@@ -295,8 +295,8 @@
   std::unordered_map<uint8_t, TypeSpec> type_specs_;
   ByteBucketArray<uint32_t> resource_ids_;
   std::vector<DynamicPackageEntry> dynamic_package_map_;
-  std::vector<const std::pair<OverlayableInfo, std::unordered_set<uint32_t>>> overlayable_infos_;
-  std::map<uint32_t, uint32_t> alias_id_map_;
+  std::vector<std::pair<OverlayableInfo, std::unordered_set<uint32_t>>> overlayable_infos_;
+  std::vector<std::pair<uint32_t, uint32_t>> alias_id_map_;
 
   // A map of overlayable name to actor
   std::unordered_map<std::string, std::string> overlayable_map_;
diff --git a/libs/androidfw/include/androidfw/Locale.h b/libs/androidfw/include/androidfw/Locale.h
index 484ed79..8934bed 100644
--- a/libs/androidfw/include/androidfw/Locale.h
+++ b/libs/androidfw/include/androidfw/Locale.h
@@ -39,10 +39,10 @@
   /**
    * Initialize this LocaleValue from a config string.
    */
-  bool InitFromFilterString(const android::StringPiece& config);
+  bool InitFromFilterString(android::StringPiece config);
 
   // Initializes this LocaleValue from a BCP-47 locale tag.
-  bool InitFromBcp47Tag(const android::StringPiece& bcp47tag);
+  bool InitFromBcp47Tag(android::StringPiece bcp47tag);
 
   /**
    * Initialize this LocaleValue from parts of a vector.
@@ -70,7 +70,7 @@
   inline bool operator>(const LocaleValue& o) const;
 
  private:
-  bool InitFromBcp47TagImpl(const android::StringPiece& bcp47tag, const char separator);
+  bool InitFromBcp47TagImpl(android::StringPiece bcp47tag, const char separator);
 
   void set_language(const char* language);
   void set_region(const char* language);
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index b2b95b7..52321da 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -21,6 +21,7 @@
 #define _LIBS_UTILS_RESOURCE_TYPES_H
 
 #include <android-base/expected.h>
+#include <android-base/unique_fd.h>
 
 #include <androidfw/Asset.h>
 #include <androidfw/Errors.h>
@@ -58,6 +59,8 @@
 
 // Returns whether or not the path represents a fabricated overlay.
 bool IsFabricatedOverlay(const std::string& path);
+bool IsFabricatedOverlay(const char* path);
+bool IsFabricatedOverlay(android::base::borrowed_fd fd);
 
 /**
  * In C++11, char16_t is defined as *at least* 16 bits. We do a lot of
@@ -1882,7 +1885,10 @@
 
     void addMapping(uint8_t buildPackageId, uint8_t runtimePackageId);
 
-    void addAlias(uint32_t stagedId, uint32_t finalizedId);
+    using AliasMap = std::vector<std::pair<uint32_t, uint32_t>>;
+    void setAliases(AliasMap aliases) {
+        mAliasId = std::move(aliases);
+    }
 
     // Returns whether or not the value must be looked up.
     bool requiresLookup(const Res_value* value) const;
@@ -1896,12 +1902,12 @@
         return mEntries;
     }
 
-private:
-    uint8_t                         mAssignedPackageId;
-    uint8_t                         mLookupTable[256];
-    KeyedVector<String16, uint8_t>  mEntries;
-    bool                            mAppAsLib;
-    std::map<uint32_t, uint32_t>    mAliasId;
+   private:
+    uint8_t mLookupTable[256];
+    uint8_t mAssignedPackageId;
+    bool mAppAsLib;
+    KeyedVector<String16, uint8_t> mEntries;
+    AliasMap mAliasId;
 };
 
 bool U16StringToInt(const char16_t* s, size_t len, Res_value* outValue);
diff --git a/libs/androidfw/include/androidfw/ResourceUtils.h b/libs/androidfw/include/androidfw/ResourceUtils.h
index bd1c440..2d90a52 100644
--- a/libs/androidfw/include/androidfw/ResourceUtils.h
+++ b/libs/androidfw/include/androidfw/ResourceUtils.h
@@ -25,14 +25,14 @@
 // Extracts the package, type, and name from a string of the format: [[package:]type/]name
 // Validation must be performed on each extracted piece.
 // Returns false if there was a syntax error.
-bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, StringPiece* out_type,
+bool ExtractResourceName(StringPiece str, StringPiece* out_package, StringPiece* out_type,
                          StringPiece* out_entry);
 
 // Convert a type_string_ref, entry_string_ref, and package to AssetManager2::ResourceName.
 // Useful for getting resource name without re-running AssetManager2::FindEntry searches.
 base::expected<AssetManager2::ResourceName, NullOrIOError> ToResourceName(
     const StringPoolRef& type_string_ref, const StringPoolRef& entry_string_ref,
-    const StringPiece& package_name);
+    StringPiece package_name);
 
 // Formats a ResourceName to "package:type/entry_name".
 std::string ToFormattedResourceString(const AssetManager2::ResourceName& resource_name);
diff --git a/libs/androidfw/include/androidfw/Source.h b/libs/androidfw/include/androidfw/Source.h
index 0421a91..ddc9ba4 100644
--- a/libs/androidfw/include/androidfw/Source.h
+++ b/libs/androidfw/include/androidfw/Source.h
@@ -34,15 +34,14 @@
 
   Source() = default;
 
-  inline Source(const android::StringPiece& path) : path(path.to_string()) {  // NOLINT(implicit)
+  inline Source(android::StringPiece path) : path(path) {  // NOLINT(implicit)
   }
 
-  inline Source(const android::StringPiece& path, const android::StringPiece& archive)
-      : path(path.to_string()), archive(archive.to_string()) {
+  inline Source(android::StringPiece path, android::StringPiece archive)
+      : path(path), archive(archive) {
   }
 
-  inline Source(const android::StringPiece& path, size_t line)
-      : path(path.to_string()), line(line) {
+  inline Source(android::StringPiece path, size_t line) : path(path), line(line) {
   }
 
   inline Source WithLine(size_t line) const {
diff --git a/libs/androidfw/include/androidfw/StringPiece.h b/libs/androidfw/include/androidfw/StringPiece.h
index fac2fa4..f6cc64e 100644
--- a/libs/androidfw/include/androidfw/StringPiece.h
+++ b/libs/androidfw/include/androidfw/StringPiece.h
@@ -19,307 +19,37 @@
 
 #include <ostream>
 #include <string>
+#include <string_view>
 
-#include "utils/JenkinsHash.h"
 #include "utils/Unicode.h"
 
 namespace android {
 
-// Read only wrapper around basic C strings. Prevents excessive copying.
-// StringPiece does not own the data it is wrapping. The lifetime of the underlying
-// data must outlive this StringPiece.
-//
-// WARNING: When creating from std::basic_string<>, moving the original
-// std::basic_string<> will invalidate the data held in a BasicStringPiece<>.
-// BasicStringPiece<> should only be used transitively.
-//
-// NOTE: When creating an std::pair<StringPiece, T> using std::make_pair(),
-// passing an std::string will first copy the string, then create a StringPiece
-// on the copy, which is then immediately destroyed.
-// Instead, create a StringPiece explicitly:
-//
-// std::string my_string = "foo";
-// std::make_pair<StringPiece, T>(StringPiece(my_string), ...);
-template <typename TChar>
-class BasicStringPiece {
- public:
-  using const_iterator = const TChar*;
-  using difference_type = size_t;
-  using size_type = size_t;
-
-  // End of string marker.
-  constexpr static const size_t npos = static_cast<size_t>(-1);
-
-  BasicStringPiece();
-  BasicStringPiece(const BasicStringPiece<TChar>& str);
-  BasicStringPiece(const std::basic_string<TChar>& str);  // NOLINT(google-explicit-constructor)
-  BasicStringPiece(const TChar* str);                     // NOLINT(google-explicit-constructor)
-  BasicStringPiece(const TChar* str, size_t len);
-
-  BasicStringPiece<TChar>& operator=(const BasicStringPiece<TChar>& rhs);
-  BasicStringPiece<TChar>& assign(const TChar* str, size_t len);
-
-  BasicStringPiece<TChar> substr(size_t start, size_t len = npos) const;
-  BasicStringPiece<TChar> substr(BasicStringPiece<TChar>::const_iterator begin,
-                                 BasicStringPiece<TChar>::const_iterator end) const;
-
-  const TChar* data() const;
-  size_t length() const;
-  size_t size() const;
-  bool empty() const;
-  std::basic_string<TChar> to_string() const;
-
-  bool contains(const BasicStringPiece<TChar>& rhs) const;
-  int compare(const BasicStringPiece<TChar>& rhs) const;
-  bool operator<(const BasicStringPiece<TChar>& rhs) const;
-  bool operator>(const BasicStringPiece<TChar>& rhs) const;
-  bool operator==(const BasicStringPiece<TChar>& rhs) const;
-  bool operator!=(const BasicStringPiece<TChar>& rhs) const;
-
-  const_iterator begin() const;
-  const_iterator end() const;
-
- private:
-  const TChar* data_;
-  size_t length_;
-};
+template <class T>
+using BasicStringPiece = std::basic_string_view<T>;
 
 using StringPiece = BasicStringPiece<char>;
 using StringPiece16 = BasicStringPiece<char16_t>;
 
-//
-// BasicStringPiece implementation.
-//
-
-template <typename TChar>
-constexpr const size_t BasicStringPiece<TChar>::npos;
-
-template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece() : data_(nullptr), length_(0) {}
-
-template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece(const BasicStringPiece<TChar>& str)
-    : data_(str.data_), length_(str.length_) {}
-
-template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece(const std::basic_string<TChar>& str)
-    : data_(str.data()), length_(str.length()) {}
-
-template <>
-inline BasicStringPiece<char>::BasicStringPiece(const char* str)
-    : data_(str), length_(str != nullptr ? strlen(str) : 0) {}
-
-template <>
-inline BasicStringPiece<char16_t>::BasicStringPiece(const char16_t* str)
-    : data_(str), length_(str != nullptr ? strlen16(str) : 0) {}
-
-template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece(const TChar* str, size_t len)
-    : data_(str), length_(len) {}
-
-template <typename TChar>
-inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::operator=(
-    const BasicStringPiece<TChar>& rhs) {
-  data_ = rhs.data_;
-  length_ = rhs.length_;
-  return *this;
-}
-
-template <typename TChar>
-inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::assign(const TChar* str, size_t len) {
-  data_ = str;
-  length_ = len;
-  return *this;
-}
-
-template <typename TChar>
-inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr(size_t start, size_t len) const {
-  if (len == npos) {
-    len = length_ - start;
-  }
-
-  if (start > length_ || start + len > length_) {
-    return BasicStringPiece<TChar>();
-  }
-  return BasicStringPiece<TChar>(data_ + start, len);
-}
-
-template <typename TChar>
-inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr(
-    BasicStringPiece<TChar>::const_iterator begin,
-    BasicStringPiece<TChar>::const_iterator end) const {
-  return BasicStringPiece<TChar>(begin, end - begin);
-}
-
-template <typename TChar>
-inline const TChar* BasicStringPiece<TChar>::data() const {
-  return data_;
-}
-
-template <typename TChar>
-inline size_t BasicStringPiece<TChar>::length() const {
-  return length_;
-}
-
-template <typename TChar>
-inline size_t BasicStringPiece<TChar>::size() const {
-  return length_;
-}
-
-template <typename TChar>
-inline bool BasicStringPiece<TChar>::empty() const {
-  return length_ == 0;
-}
-
-template <typename TChar>
-inline std::basic_string<TChar> BasicStringPiece<TChar>::to_string() const {
-  return std::basic_string<TChar>(data_, length_);
-}
-
-template <>
-inline bool BasicStringPiece<char>::contains(const BasicStringPiece<char>& rhs) const {
-  if (!data_ || !rhs.data_) {
-    return false;
-  }
-  if (rhs.length_ > length_) {
-    return false;
-  }
-  return strstr(data_, rhs.data_) != nullptr;
-}
-
-template <>
-inline int BasicStringPiece<char>::compare(const BasicStringPiece<char>& rhs) const {
-  const char nullStr = '\0';
-  const char* b1 = data_ != nullptr ? data_ : &nullStr;
-  const char* e1 = b1 + length_;
-  const char* b2 = rhs.data_ != nullptr ? rhs.data_ : &nullStr;
-  const char* e2 = b2 + rhs.length_;
-
-  while (b1 < e1 && b2 < e2) {
-    const int d = static_cast<int>(*b1++) - static_cast<int>(*b2++);
-    if (d) {
-      return d;
-    }
-  }
-  return static_cast<int>(length_ - rhs.length_);
-}
-
-inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char16_t>& str) {
-  const ssize_t result_len = utf16_to_utf8_length(str.data(), str.size());
-  if (result_len < 0) {
-    // Empty string.
-    return out;
-  }
-
-  std::string result;
-  result.resize(static_cast<size_t>(result_len));
-  utf16_to_utf8(str.data(), str.length(), &*result.begin(), static_cast<size_t>(result_len) + 1);
-  return out << result;
-}
-
-template <>
-inline bool BasicStringPiece<char16_t>::contains(const BasicStringPiece<char16_t>& rhs) const {
-  if (!data_ || !rhs.data_) {
-    return false;
-  }
-  if (rhs.length_ > length_) {
-    return false;
-  }
-  return strstr16(data_, rhs.data_) != nullptr;
-}
-
-template <>
-inline int BasicStringPiece<char16_t>::compare(const BasicStringPiece<char16_t>& rhs) const {
-  const char16_t nullStr = u'\0';
-  const char16_t* b1 = data_ != nullptr ? data_ : &nullStr;
-  const char16_t* b2 = rhs.data_ != nullptr ? rhs.data_ : &nullStr;
-  return strzcmp16(b1, length_, b2, rhs.length_);
-}
-
-template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator<(const BasicStringPiece<TChar>& rhs) const {
-  return compare(rhs) < 0;
-}
-
-template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator>(const BasicStringPiece<TChar>& rhs) const {
-  return compare(rhs) > 0;
-}
-
-template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator==(const BasicStringPiece<TChar>& rhs) const {
-  return compare(rhs) == 0;
-}
-
-template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator!=(const BasicStringPiece<TChar>& rhs) const {
-  return compare(rhs) != 0;
-}
-
-template <typename TChar>
-inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::begin() const {
-  return data_;
-}
-
-template <typename TChar>
-inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::end() const {
-  return data_ + length_;
-}
-
-template <typename TChar>
-inline bool operator==(const TChar* lhs, const BasicStringPiece<TChar>& rhs) {
-  return BasicStringPiece<TChar>(lhs) == rhs;
-}
-
-template <typename TChar>
-inline bool operator!=(const TChar* lhs, const BasicStringPiece<TChar>& rhs) {
-  return BasicStringPiece<TChar>(lhs) != rhs;
-}
-
-inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char>& str) {
-  return out.write(str.data(), str.size());
-}
-
-template <typename TChar>
-inline ::std::basic_string<TChar>& operator+=(::std::basic_string<TChar>& lhs,
-                                              const BasicStringPiece<TChar>& rhs) {
-  return lhs.append(rhs.data(), rhs.size());
-}
-
-template <typename TChar>
-inline bool operator==(const ::std::basic_string<TChar>& lhs, const BasicStringPiece<TChar>& rhs) {
-  return BasicStringPiece<TChar>(lhs) == rhs;
-}
-
-template <typename TChar>
-inline bool operator!=(const ::std::basic_string<TChar>& lhs, const BasicStringPiece<TChar>& rhs) {
-  return BasicStringPiece<TChar>(lhs) != rhs;
-}
-
 }  // namespace android
 
-inline ::std::ostream& operator<<(::std::ostream& out, const std::u16string& str) {
+namespace std {
+
+inline ::std::ostream& operator<<(::std::ostream& out, ::std::u16string_view str) {
   ssize_t utf8_len = utf16_to_utf8_length(str.data(), str.size());
   if (utf8_len < 0) {
-    return out << "???";
+    return out;  // empty
   }
 
   std::string utf8;
   utf8.resize(static_cast<size_t>(utf8_len));
-  utf16_to_utf8(str.data(), str.size(), &*utf8.begin(), utf8_len + 1);
+  utf16_to_utf8(str.data(), str.size(), utf8.data(), utf8_len + 1);
   return out << utf8;
 }
 
-namespace std {
-
-template <typename TChar>
-struct hash<android::BasicStringPiece<TChar>> {
-  size_t operator()(const android::BasicStringPiece<TChar>& str) const {
-    uint32_t hashCode = android::JenkinsHashMixBytes(
-        0, reinterpret_cast<const uint8_t*>(str.data()), sizeof(TChar) * str.size());
-    return static_cast<size_t>(hashCode);
-  }
-};
+inline ::std::ostream& operator<<(::std::ostream& out, const ::std::u16string& str) {
+  return out << std::u16string_view(str);
+}
 
 }  // namespace std
 
diff --git a/libs/androidfw/include/androidfw/StringPool.h b/libs/androidfw/include/androidfw/StringPool.h
index 25174d8..0190ab5 100644
--- a/libs/androidfw/include/androidfw/StringPool.h
+++ b/libs/androidfw/include/androidfw/StringPool.h
@@ -167,11 +167,11 @@
 
   // Adds a string to the pool, unless it already exists. Returns a reference to the string in the
   // pool.
-  Ref MakeRef(const android::StringPiece& str);
+  Ref MakeRef(android::StringPiece str);
 
   // Adds a string to the pool, unless it already exists, with a context object that can be used
   // when sorting the string pool. Returns a reference to the string in the pool.
-  Ref MakeRef(const android::StringPiece& str, const Context& context);
+  Ref MakeRef(android::StringPiece str, const Context& context);
 
   // Adds a string from another string pool. Returns a reference to the string in the string pool.
   Ref MakeRef(const Ref& ref);
@@ -215,7 +215,7 @@
 
   static bool Flatten(BigBuffer* out, const StringPool& pool, bool utf8, IDiagnostics* diag);
 
-  Ref MakeRefImpl(const android::StringPiece& str, const Context& context, bool unique);
+  Ref MakeRefImpl(android::StringPiece str, const Context& context, bool unique);
   void ReAssignIndices();
 
   std::vector<std::unique_ptr<Entry>> strings_;
diff --git a/libs/androidfw/include/androidfw/Util.h b/libs/androidfw/include/androidfw/Util.h
index 1bbc7f5..ae7c65f 100644
--- a/libs/androidfw/include/androidfw/Util.h
+++ b/libs/androidfw/include/androidfw/Util.h
@@ -123,16 +123,16 @@
 void ReadUtf16StringFromDevice(const uint16_t* src, size_t len, std::string* out);
 
 // Converts a UTF-8 string to a UTF-16 string.
-std::u16string Utf8ToUtf16(const StringPiece& utf8);
+std::u16string Utf8ToUtf16(StringPiece utf8);
 
 // Converts a UTF-16 string to a UTF-8 string.
-std::string Utf16ToUtf8(const StringPiece16& utf16);
+std::string Utf16ToUtf8(StringPiece16 utf16);
 
 // Converts a UTF8 string into Modified UTF8
-std::string Utf8ToModifiedUtf8(const std::string& utf8);
+std::string Utf8ToModifiedUtf8(std::string_view utf8);
 
 // Converts a Modified UTF8 string into a UTF8 string
-std::string ModifiedUtf8ToUtf8(const std::string& modified_utf8);
+std::string ModifiedUtf8ToUtf8(std::string_view modified_utf8);
 
 inline uint16_t HostToDevice16(uint16_t value) {
   return htods(value);
@@ -150,7 +150,7 @@
   return dtohl(value);
 }
 
-std::vector<std::string> SplitAndLowercase(const android::StringPiece& str, char sep);
+std::vector<std::string> SplitAndLowercase(android::StringPiece str, char sep);
 
 template <typename T>
 inline bool IsFourByteAligned(const incfs::map_ptr<T>& data) {
diff --git a/libs/androidfw/include/androidfw/misc.h b/libs/androidfw/include/androidfw/misc.h
index 5a5a0e2..d40d24e 100644
--- a/libs/androidfw/include/androidfw/misc.h
+++ b/libs/androidfw/include/androidfw/misc.h
@@ -44,6 +44,10 @@
 /* get the file's modification date; returns -1 w/errno set on failure */
 time_t getFileModDate(const char* fileName);
 
+// Check if |path| or |fd| resides on a readonly filesystem.
+bool isReadonlyFilesystem(const char* path);
+bool isReadonlyFilesystem(int fd);
+
 }; // namespace android
 
 #endif // _LIBS_ANDROID_FW_MISC_H
diff --git a/libs/androidfw/misc.cpp b/libs/androidfw/misc.cpp
index 5285420..7af5066 100644
--- a/libs/androidfw/misc.cpp
+++ b/libs/androidfw/misc.cpp
@@ -21,12 +21,17 @@
 //
 #include <androidfw/misc.h>
 
-#include <sys/stat.h>
-#include <cstring>
-#include <errno.h>
-#include <cstdio>
+#include "android-base/logging.h"
 
-using namespace android;
+#ifndef _WIN32
+#include <sys/statvfs.h>
+#include <sys/vfs.h>
+#endif  // _WIN32
+
+#include <cstring>
+#include <cstdio>
+#include <errno.h>
+#include <sys/stat.h>
 
 namespace android {
 
@@ -41,8 +46,7 @@
         if (errno == ENOENT || errno == ENOTDIR)
             return kFileTypeNonexistent;
         else {
-            fprintf(stderr, "getFileType got errno=%d on '%s'\n",
-                errno, fileName);
+            PLOG(ERROR) << "getFileType(): stat(" << fileName << ") failed";
             return kFileTypeUnknown;
         }
     } else {
@@ -82,4 +86,32 @@
     return sb.st_mtime;
 }
 
+#ifdef _WIN32
+// No need to implement these for Windows, the functions only matter on a device.
+bool isReadonlyFilesystem(const char*) {
+    return false;
+}
+bool isReadonlyFilesystem(int) {
+    return false;
+}
+#else   // _WIN32
+bool isReadonlyFilesystem(const char* path) {
+    struct statfs sfs;
+    if (::statfs(path, &sfs)) {
+        PLOG(ERROR) << "isReadonlyFilesystem(): statfs(" << path << ") failed";
+        return false;
+    }
+    return (sfs.f_flags & ST_RDONLY) != 0;
+}
+
+bool isReadonlyFilesystem(int fd) {
+    struct statfs sfs;
+    if (::fstatfs(fd, &sfs)) {
+        PLOG(ERROR) << "isReadonlyFilesystem(): fstatfs(" << fd << ") failed";
+        return false;
+    }
+    return (sfs.f_flags & ST_RDONLY) != 0;
+}
+#endif  // _WIN32
+
 }; // namespace android
diff --git a/libs/androidfw/tests/AttributeResolution_bench.cpp b/libs/androidfw/tests/AttributeResolution_bench.cpp
index ddd8ab8..1c89c61 100644
--- a/libs/androidfw/tests/AttributeResolution_bench.cpp
+++ b/libs/androidfw/tests/AttributeResolution_bench.cpp
@@ -120,8 +120,8 @@
     return;
   }
 
-  std::unique_ptr<Asset> asset = assetmanager.OpenNonAsset(layout_path->to_string(), value->cookie,
-                                                           Asset::ACCESS_BUFFER);
+  std::unique_ptr<Asset> asset =
+      assetmanager.OpenNonAsset(std::string(*layout_path), value->cookie, Asset::ACCESS_BUFFER);
   if (asset == nullptr) {
     state.SkipWithError("failed to load layout");
     return;
diff --git a/libs/androidfw/tests/ByteBucketArray_test.cpp b/libs/androidfw/tests/ByteBucketArray_test.cpp
index 5d464c7..9c36cfb 100644
--- a/libs/androidfw/tests/ByteBucketArray_test.cpp
+++ b/libs/androidfw/tests/ByteBucketArray_test.cpp
@@ -52,4 +52,57 @@
   }
 }
 
+TEST(ByteBucketArrayTest, TestForEach) {
+  ByteBucketArray<int> bba;
+  ASSERT_TRUE(bba.set(0, 1));
+  ASSERT_TRUE(bba.set(10, 2));
+  ASSERT_TRUE(bba.set(26, 3));
+  ASSERT_TRUE(bba.set(129, 4));
+  ASSERT_TRUE(bba.set(234, 5));
+
+  int count = 0;
+  bba.forEachItem([&count](auto i, auto val) {
+    ++count;
+    switch (i) {
+      case 0:
+        EXPECT_EQ(1, val);
+        break;
+      case 10:
+        EXPECT_EQ(2, val);
+        break;
+      case 26:
+        EXPECT_EQ(3, val);
+        break;
+      case 129:
+        EXPECT_EQ(4, val);
+        break;
+      case 234:
+        EXPECT_EQ(5, val);
+        break;
+      default:
+        EXPECT_EQ(0, val);
+        break;
+    }
+  });
+  ASSERT_EQ(4 * 16, count);
+}
+
+TEST(ByteBucketArrayTest, TestTrimBuckets) {
+  ByteBucketArray<int> bba;
+  ASSERT_TRUE(bba.set(0, 1));
+  ASSERT_TRUE(bba.set(255, 2));
+  {
+    bba.trimBuckets([](auto val) { return val < 2; });
+    int count = 0;
+    bba.forEachItem([&count](auto, auto) { ++count; });
+    ASSERT_EQ(1 * 16, count);
+  }
+  {
+    bba.trimBuckets([](auto val) { return val < 3; });
+    int count = 0;
+    bba.forEachItem([&count](auto, auto) { ++count; });
+    ASSERT_EQ(0, count);
+  }
+}
+
 }  // namespace android
diff --git a/libs/androidfw/tests/ConfigDescription_test.cpp b/libs/androidfw/tests/ConfigDescription_test.cpp
index ce7f805..8fed0a4 100644
--- a/libs/androidfw/tests/ConfigDescription_test.cpp
+++ b/libs/androidfw/tests/ConfigDescription_test.cpp
@@ -25,8 +25,8 @@
 
 namespace android {
 
-static ::testing::AssertionResult TestParse(
-    const StringPiece& input, ConfigDescription* config = nullptr) {
+static ::testing::AssertionResult TestParse(StringPiece input,
+                                            ConfigDescription* config = nullptr) {
   if (ConfigDescription::Parse(input, config)) {
     return ::testing::AssertionSuccess() << input << " was successfully parsed";
   }
@@ -138,7 +138,7 @@
   EXPECT_EQ(std::string("vrheadset-v26"), config.toString().string());
 }
 
-static inline ConfigDescription ParseConfigOrDie(const android::StringPiece& str) {
+static inline ConfigDescription ParseConfigOrDie(android::StringPiece str) {
   ConfigDescription config;
   CHECK(ConfigDescription::Parse(str, &config)) << "invalid configuration: " << str;
   return config;
diff --git a/libs/androidfw/tests/StringPiece_test.cpp b/libs/androidfw/tests/StringPiece_test.cpp
index 316a5c1..822e527 100644
--- a/libs/androidfw/tests/StringPiece_test.cpp
+++ b/libs/androidfw/tests/StringPiece_test.cpp
@@ -60,36 +60,4 @@
   EXPECT_TRUE(StringPiece(car) > banana);
 }
 
-TEST(StringPieceTest, ContainsOtherStringPiece) {
-  StringPiece text("I am a leaf on the wind.");
-  StringPiece start_needle("I am");
-  StringPiece end_needle("wind.");
-  StringPiece middle_needle("leaf");
-  StringPiece empty_needle("");
-  StringPiece missing_needle("soar");
-  StringPiece long_needle("This string is longer than the text.");
-
-  EXPECT_TRUE(text.contains(start_needle));
-  EXPECT_TRUE(text.contains(end_needle));
-  EXPECT_TRUE(text.contains(middle_needle));
-  EXPECT_TRUE(text.contains(empty_needle));
-  EXPECT_FALSE(text.contains(missing_needle));
-  EXPECT_FALSE(text.contains(long_needle));
-
-  StringPiece16 text16(u"I am a leaf on the wind.");
-  StringPiece16 start_needle16(u"I am");
-  StringPiece16 end_needle16(u"wind.");
-  StringPiece16 middle_needle16(u"leaf");
-  StringPiece16 empty_needle16(u"");
-  StringPiece16 missing_needle16(u"soar");
-  StringPiece16 long_needle16(u"This string is longer than the text.");
-
-  EXPECT_TRUE(text16.contains(start_needle16));
-  EXPECT_TRUE(text16.contains(end_needle16));
-  EXPECT_TRUE(text16.contains(middle_needle16));
-  EXPECT_TRUE(text16.contains(empty_needle16));
-  EXPECT_FALSE(text16.contains(missing_needle16));
-  EXPECT_FALSE(text16.contains(long_needle16));
-}
-
 }  // namespace android
diff --git a/libs/androidfw/tests/StringPool_test.cpp b/libs/androidfw/tests/StringPool_test.cpp
index 047d457..0e0acae 100644
--- a/libs/androidfw/tests/StringPool_test.cpp
+++ b/libs/androidfw/tests/StringPool_test.cpp
@@ -321,15 +321,15 @@
   ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
   auto str = test.string8At(0);
   ASSERT_TRUE(str.has_value());
-  EXPECT_THAT(str->to_string(), Eq("\xED\xA0\x81\xED\xB0\x80"));
+  EXPECT_THAT(*str, Eq("\xED\xA0\x81\xED\xB0\x80"));
 
   str = test.string8At(1);
   ASSERT_TRUE(str.has_value());
-  EXPECT_THAT(str->to_string(), Eq("foo \xED\xA0\x81\xED\xB0\xB7 bar"));
+  EXPECT_THAT(*str, Eq("foo \xED\xA0\x81\xED\xB0\xB7 bar"));
 
   str = test.string8At(2);
   ASSERT_TRUE(str.has_value());
-  EXPECT_THAT(str->to_string(), Eq("\xED\xA0\x81\xED\xB0\x80\xED\xA0\x81\xED\xB0\xB7"));
+  EXPECT_THAT(*str, Eq("\xED\xA0\x81\xED\xB0\x80\xED\xA0\x81\xED\xB0\xB7"));
 
   // Check that retrieving the strings returns the original UTF-8 character bytes
   EXPECT_THAT(android::util::GetString(test, 0), Eq("\xF0\x90\x90\x80"));
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index c0a4fdf..88cfed9 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -589,6 +589,7 @@
                 "ProfileData.cpp",
                 "ProfileDataContainer.cpp",
                 "Readback.cpp",
+                "Tonemapper.cpp",
                 "TreeInfo.cpp",
                 "WebViewFunctorManager.cpp",
                 "protos/graphicsstats.proto",
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index 02c2e67..8dcd6db 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -16,16 +16,6 @@
 
 #include "Readback.h"
 
-#include <sync/sync.h>
-#include <system/window.h>
-
-#include <gui/TraceUtils.h>
-#include "DeferredLayerUpdater.h"
-#include "Properties.h"
-#include "hwui/Bitmap.h"
-#include "pipeline/skia/LayerDrawable.h"
-#include "renderthread/EglManager.h"
-#include "renderthread/VulkanManager.h"
 #include <SkBitmap.h>
 #include <SkBlendMode.h>
 #include <SkCanvas.h>
@@ -38,6 +28,19 @@
 #include <SkRefCnt.h>
 #include <SkSamplingOptions.h>
 #include <SkSurface.h>
+#include <gui/TraceUtils.h>
+#include <private/android/AHardwareBufferHelpers.h>
+#include <shaders/shaders.h>
+#include <sync/sync.h>
+#include <system/window.h>
+
+#include "DeferredLayerUpdater.h"
+#include "Properties.h"
+#include "Tonemapper.h"
+#include "hwui/Bitmap.h"
+#include "pipeline/skia/LayerDrawable.h"
+#include "renderthread/EglManager.h"
+#include "renderthread/VulkanManager.h"
 #include "utils/Color.h"
 #include "utils/MathUtils.h"
 #include "utils/NdkUtils.h"
@@ -91,8 +94,18 @@
         }
     }
 
-    sk_sp<SkColorSpace> colorSpace = DataSpaceToColorSpace(
-            static_cast<android_dataspace>(ANativeWindow_getBuffersDataSpace(window)));
+    int32_t dataspace = ANativeWindow_getBuffersDataSpace(window);
+
+    // If the application is not updating the Surface themselves, e.g., another
+    // process is producing buffers for the application to display, then
+    // ANativeWindow_getBuffersDataSpace will return an unknown answer, so grab
+    // the dataspace from buffer metadata instead, if it exists.
+    if (dataspace == 0) {
+        dataspace = AHardwareBuffer_getDataSpace(sourceBuffer.get());
+    }
+
+    sk_sp<SkColorSpace> colorSpace =
+            DataSpaceToColorSpace(static_cast<android_dataspace>(dataspace));
     sk_sp<SkImage> image =
             SkImage::MakeFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType, colorSpace);
 
@@ -227,6 +240,10 @@
     const bool hasBufferCrop = cropRect.left < cropRect.right && cropRect.top < cropRect.bottom;
     auto constraint =
             hasBufferCrop ? SkCanvas::kStrict_SrcRectConstraint : SkCanvas::kFast_SrcRectConstraint;
+
+    static constexpr float kMaxLuminanceNits = 4000.f;
+    tonemapPaint(image->imageInfo(), canvas->imageInfo(), kMaxLuminanceNits, paint);
+
     canvas->drawImageRect(image, imageSrcRect, imageDstRect, sampling, &paint, constraint);
     canvas->restore();
 
diff --git a/libs/hwui/SkiaInterpolator.cpp b/libs/hwui/SkiaInterpolator.cpp
index 0695dd1..153c3b6 100644
--- a/libs/hwui/SkiaInterpolator.cpp
+++ b/libs/hwui/SkiaInterpolator.cpp
@@ -17,6 +17,8 @@
 #include "SkiaInterpolator.h"
 
 #include "include/core/SkMath.h"
+#include "include/core/SkScalar.h"
+#include "include/core/SkTypes.h"
 #include "include/private/SkFixed.h"
 #include "include/private/SkMalloc.h"
 #include "include/private/SkTo.h"
diff --git a/libs/hwui/Tonemapper.cpp b/libs/hwui/Tonemapper.cpp
new file mode 100644
index 0000000..a7e76b6
--- /dev/null
+++ b/libs/hwui/Tonemapper.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2022 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.
+ */
+
+#include "Tonemapper.h"
+
+#include <SkRuntimeEffect.h>
+#include <log/log.h>
+#include <shaders/shaders.h>
+
+#include "utils/Color.h"
+
+namespace android::uirenderer {
+
+namespace {
+
+class ColorFilterRuntimeEffectBuilder : public SkRuntimeEffectBuilder {
+public:
+    explicit ColorFilterRuntimeEffectBuilder(sk_sp<SkRuntimeEffect> effect)
+            : SkRuntimeEffectBuilder(std::move(effect)) {}
+
+    sk_sp<SkColorFilter> makeColorFilter() {
+        return this->effect()->makeColorFilter(this->uniforms());
+    }
+};
+
+static sk_sp<SkColorFilter> createLinearEffectColorFilter(const shaders::LinearEffect& linearEffect,
+                                                          float maxDisplayLuminance,
+                                                          float currentDisplayLuminanceNits,
+                                                          float maxLuminance) {
+    auto shaderString = SkString(shaders::buildLinearEffectSkSL(linearEffect));
+    auto [runtimeEffect, error] = SkRuntimeEffect::MakeForColorFilter(std::move(shaderString));
+    if (!runtimeEffect) {
+        LOG_ALWAYS_FATAL("LinearColorFilter construction error: %s", error.c_str());
+    }
+
+    ColorFilterRuntimeEffectBuilder effectBuilder(std::move(runtimeEffect));
+
+    const auto uniforms =
+            shaders::buildLinearEffectUniforms(linearEffect, android::mat4(), maxDisplayLuminance,
+                                               currentDisplayLuminanceNits, maxLuminance);
+
+    for (const auto& uniform : uniforms) {
+        effectBuilder.uniform(uniform.name.c_str()).set(uniform.value.data(), uniform.value.size());
+    }
+
+    return effectBuilder.makeColorFilter();
+}
+
+static bool extractTransfer(ui::Dataspace dataspace) {
+    return dataspace & HAL_DATASPACE_TRANSFER_MASK;
+}
+
+static bool isHdrDataspace(ui::Dataspace dataspace) {
+    const auto transfer = extractTransfer(dataspace);
+
+    return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG;
+}
+
+static ui::Dataspace getDataspace(const SkImageInfo& image) {
+    return static_cast<ui::Dataspace>(
+            ColorSpaceToADataSpace(image.colorSpace(), image.colorType()));
+}
+
+}  // namespace
+
+// Given a source and destination image info, and the max content luminance, generate a tonemaping
+// shader and tag it on the supplied paint.
+void tonemapPaint(const SkImageInfo& source, const SkImageInfo& destination, float maxLuminanceNits,
+                  SkPaint& paint) {
+    const auto sourceDataspace = getDataspace(source);
+    const auto destinationDataspace = getDataspace(destination);
+
+    if (extractTransfer(sourceDataspace) != extractTransfer(destinationDataspace) &&
+        (isHdrDataspace(sourceDataspace) || isHdrDataspace(destinationDataspace))) {
+        const auto effect = shaders::LinearEffect{
+                .inputDataspace = sourceDataspace,
+                .outputDataspace = destinationDataspace,
+                .undoPremultipliedAlpha = source.alphaType() == kPremul_SkAlphaType,
+                .fakeInputDataspace = destinationDataspace,
+                .type = shaders::LinearEffect::SkSLType::ColorFilter};
+        constexpr float kMaxDisplayBrightnessNits = 1000.f;
+        constexpr float kCurrentDisplayBrightnessNits = 500.f;
+        sk_sp<SkColorFilter> colorFilter = createLinearEffectColorFilter(
+                effect, kMaxDisplayBrightnessNits, kCurrentDisplayBrightnessNits, maxLuminanceNits);
+
+        if (paint.getColorFilter()) {
+            paint.setColorFilter(SkColorFilters::Compose(paint.refColorFilter(), colorFilter));
+        } else {
+            paint.setColorFilter(colorFilter);
+        }
+    }
+}
+
+}  // namespace android::uirenderer
diff --git a/libs/hwui/Tonemapper.h b/libs/hwui/Tonemapper.h
new file mode 100644
index 0000000..c0d5325
--- /dev/null
+++ b/libs/hwui/Tonemapper.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2022 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.
+ */
+
+#pragma once
+
+#include <SkCanvas.h>
+
+namespace android::uirenderer {
+
+// Given a source and destination image info, and the max content luminance, generate a tonemaping
+// shader and tag it on the supplied paint.
+void tonemapPaint(const SkImageInfo& source, const SkImageInfo& destination, float maxLuminanceNits,
+                  SkPaint& paint);
+
+}  // namespace android::uirenderer
diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp
index 3ba5409..99f54c1 100644
--- a/libs/hwui/pipeline/skia/LayerDrawable.cpp
+++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp
@@ -25,6 +25,7 @@
 #include "SkColorFilter.h"
 #include "SkRuntimeEffect.h"
 #include "SkSurface.h"
+#include "Tonemapper.h"
 #include "gl/GrGLTypes.h"
 #include "math/mat4.h"
 #include "system/graphics-base-v1.0.h"
@@ -76,37 +77,6 @@
              isIntegerAligned(dstDevRect.y()));
 }
 
-static sk_sp<SkShader> createLinearEffectShader(sk_sp<SkShader> shader,
-                                                const shaders::LinearEffect& linearEffect,
-                                                float maxDisplayLuminance,
-                                                float currentDisplayLuminanceNits,
-                                                float maxLuminance) {
-    auto shaderString = SkString(shaders::buildLinearEffectSkSL(linearEffect));
-    auto [runtimeEffect, error] = SkRuntimeEffect::MakeForShader(std::move(shaderString));
-    if (!runtimeEffect) {
-        LOG_ALWAYS_FATAL("LinearColorFilter construction error: %s", error.c_str());
-    }
-
-    SkRuntimeShaderBuilder effectBuilder(std::move(runtimeEffect));
-
-    effectBuilder.child("child") = std::move(shader);
-
-    const auto uniforms = shaders::buildLinearEffectUniforms(
-            linearEffect, mat4(), maxDisplayLuminance, currentDisplayLuminanceNits, maxLuminance);
-
-    for (const auto& uniform : uniforms) {
-        effectBuilder.uniform(uniform.name.c_str()).set(uniform.value.data(), uniform.value.size());
-    }
-
-    return effectBuilder.makeShader();
-}
-
-static bool isHdrDataspace(ui::Dataspace dataspace) {
-    const auto transfer = dataspace & HAL_DATASPACE_TRANSFER_MASK;
-
-    return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG;
-}
-
 static void adjustCropForYUV(uint32_t format, int bufferWidth, int bufferHeight, SkRect* cropRect) {
     // Chroma channels of YUV420 images are subsampled we may need to shrink the crop region by
     // a whole texel on each side. Since skia still adds its own 0.5 inset, we apply an
@@ -215,31 +185,10 @@
             sampling = SkSamplingOptions(SkFilterMode::kLinear);
         }
 
-        const auto sourceDataspace = static_cast<ui::Dataspace>(
-                ColorSpaceToADataSpace(layerImage->colorSpace(), layerImage->colorType()));
-        const SkImageInfo& imageInfo = canvas->imageInfo();
-        const auto destinationDataspace = static_cast<ui::Dataspace>(
-                ColorSpaceToADataSpace(imageInfo.colorSpace(), imageInfo.colorType()));
-
-        if (isHdrDataspace(sourceDataspace) || isHdrDataspace(destinationDataspace)) {
-            const auto effect = shaders::LinearEffect{
-                    .inputDataspace = sourceDataspace,
-                    .outputDataspace = destinationDataspace,
-                    .undoPremultipliedAlpha = layerImage->alphaType() == kPremul_SkAlphaType,
-                    .fakeInputDataspace = destinationDataspace};
-            auto shader = layerImage->makeShader(sampling,
-                                                 SkMatrix::RectToRect(skiaSrcRect, skiaDestRect));
-            constexpr float kMaxDisplayBrightess = 1000.f;
-            constexpr float kCurrentDisplayBrightness = 500.f;
-            shader = createLinearEffectShader(std::move(shader), effect, kMaxDisplayBrightess,
-                                              kCurrentDisplayBrightness,
-                                              layer->getMaxLuminanceNits());
-            paint.setShader(shader);
-            canvas->drawRect(skiaDestRect, paint);
-        } else {
-            canvas->drawImageRect(layerImage.get(), skiaSrcRect, skiaDestRect, sampling, &paint,
-                                  constraint);
-        }
+        tonemapPaint(layerImage->imageInfo(), canvas->imageInfo(), layer->getMaxLuminanceNits(),
+                     paint);
+        canvas->drawImageRect(layerImage.get(), skiaSrcRect, skiaDestRect, sampling, &paint,
+                              constraint);
 
         canvas->restore();
         // restore the original matrix
diff --git a/location/java/android/location/provider/LocationProviderBase.java b/location/java/android/location/provider/LocationProviderBase.java
index 529eddd..5acec79 100644
--- a/location/java/android/location/provider/LocationProviderBase.java
+++ b/location/java/android/location/provider/LocationProviderBase.java
@@ -101,6 +101,15 @@
     public static final String ACTION_FUSED_PROVIDER =
             "com.android.location.service.FusedLocationProvider";
 
+    /**
+     * The action the wrapping service should have in its intent filter to implement the
+     * {@link android.location.LocationManager#GPS_PROVIDER}.
+     *
+     * @hide
+     */
+    public static final String ACTION_GNSS_PROVIDER =
+            "android.location.provider.action.GNSS_PROVIDER";
+
     final String mTag;
     final @Nullable String mAttributionTag;
     final IBinder mBinder;
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index f3931df..9c5313a 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -7670,8 +7670,10 @@
      * or video calls. This method can be used by voice or video chat applications to select a
      * different audio device than the one selected by default by the platform.
      * <p>The device selection is expressed as an {@link AudioDeviceInfo} among devices returned by
-     * {@link #getAvailableCommunicationDevices()}.
-     * The selection is active as long as the requesting application process lives, until
+     * {@link #getAvailableCommunicationDevices()}. Note that only devices in a sink role
+     * (AKA output devices, see {@link AudioDeviceInfo#isSink()}) can be specified. The matching
+     * source device is selected automatically by the platform.
+     * <p>The selection is active as long as the requesting application process lives, until
      * {@link #clearCommunicationDevice} is called or until the device is disconnected.
      * It is therefore important for applications to clear the request when a call ends or the
      * the requesting activity or service is stopped or destroyed.
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 8594ba5..f1b1d79 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -34,6 +34,7 @@
 
 cc_defaults {
     name: "libandroid_defaults",
+    cpp_std: "gnu++20",
     cflags: [
         "-Wall",
         "-Werror",
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 7863a7d..40eb507 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -71,8 +71,10 @@
     const int64_t mPreferredRateNanos;
     // Target duration for choosing update rate
     int64_t mTargetDurationNanos;
-    // Last update timestamp
-    int64_t mLastUpdateTimestamp;
+    // First target hit timestamp
+    int64_t mFirstTargetMetTimestamp;
+    // Last target hit timestamp
+    int64_t mLastTargetMetTimestamp;
     // Cached samples
     std::vector<int64_t> mActualDurationsNanos;
     std::vector<int64_t> mTimestampsNanos;
@@ -144,7 +146,8 @@
       : mHintSession(std::move(session)),
         mPreferredRateNanos(preferredRateNanos),
         mTargetDurationNanos(targetDurationNanos),
-        mLastUpdateTimestamp(elapsedRealtimeNano()) {}
+        mFirstTargetMetTimestamp(0),
+        mLastTargetMetTimestamp(0) {}
 
 APerformanceHintSession::~APerformanceHintSession() {
     binder::Status ret = mHintSession->close();
@@ -171,7 +174,8 @@
      */
     mActualDurationsNanos.clear();
     mTimestampsNanos.clear();
-    mLastUpdateTimestamp = elapsedRealtimeNano();
+    mFirstTargetMetTimestamp = 0;
+    mLastTargetMetTimestamp = 0;
     return 0;
 }
 
@@ -184,25 +188,38 @@
     mActualDurationsNanos.push_back(actualDurationNanos);
     mTimestampsNanos.push_back(now);
 
-    /**
-     * Cache the hint if the hint is not overtime and the mLastUpdateTimestamp is
-     * still in the mPreferredRateNanos duration.
-     */
-    if (actualDurationNanos < mTargetDurationNanos &&
-        now - mLastUpdateTimestamp <= mPreferredRateNanos) {
-        return 0;
+    if (actualDurationNanos >= mTargetDurationNanos) {
+        // Reset timestamps if we are equal or over the target.
+        mFirstTargetMetTimestamp = 0;
+    } else {
+        // Set mFirstTargetMetTimestamp for first time meeting target.
+        if (!mFirstTargetMetTimestamp || !mLastTargetMetTimestamp ||
+            (now - mLastTargetMetTimestamp > 2 * mPreferredRateNanos)) {
+            mFirstTargetMetTimestamp = now;
+        }
+        /**
+         * Rate limit the change if the update is over mPreferredRateNanos since first
+         * meeting target and less than mPreferredRateNanos since last meeting target.
+         */
+        if (now - mFirstTargetMetTimestamp > mPreferredRateNanos &&
+            now - mLastTargetMetTimestamp <= mPreferredRateNanos) {
+            return 0;
+        }
+        mLastTargetMetTimestamp = now;
     }
 
     binder::Status ret =
             mHintSession->reportActualWorkDuration(mActualDurationsNanos, mTimestampsNanos);
-    mActualDurationsNanos.clear();
-    mTimestampsNanos.clear();
     if (!ret.isOk()) {
         ALOGE("%s: HintSession reportActualWorkDuration failed: %s", __FUNCTION__,
               ret.exceptionMessage().c_str());
+        mFirstTargetMetTimestamp = 0;
+        mLastTargetMetTimestamp = 0;
         return EPIPE;
     }
-    mLastUpdateTimestamp = now;
+    mActualDurationsNanos.clear();
+    mTimestampsNanos.clear();
+
     return 0;
 }
 
diff --git a/native/android/system_fonts.cpp b/native/android/system_fonts.cpp
index 30d0c35..fe3132e 100644
--- a/native/android/system_fonts.cpp
+++ b/native/android/system_fonts.cpp
@@ -66,9 +66,6 @@
         return mFilePath == o.mFilePath && mLocale == o.mLocale && mWeight == o.mWeight &&
                 mItalic == o.mItalic && mCollectionIndex == o.mCollectionIndex && mAxes == o.mAxes;
     }
-
-    AFont() = default;
-    AFont(const AFont&) = default;
 };
 
 struct FontHasher {
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 252ecf4..8c9023c 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -49,6 +49,8 @@
   <string name="save_password_to_title">Save password to</string>
   <!-- This appears as the title of the modal bottom sheet for users to choose other available places the created other credential types can be saved to. [CHAR LIMIT=200] -->
   <string name="save_sign_in_to_title">Save sign-in to</string>
+  <!-- This appears as the title of the modal bottom sheet for users to choose to create a passkey on another device. [CHAR LIMIT=200] -->
+  <string name="create_passkey_in_other_device_title">Create a passkey in another device?</string>
   <!-- This appears as the title of the modal bottom sheet for users to confirm whether they should use the selected provider as default or not. [CHAR LIMIT=200] -->
   <string name="use_provider_for_all_title">Use <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g> for all your sign-ins?</string>
   <!-- TODO: Check the wording here. -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 530f1c4..e3ed3d9 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -44,6 +44,8 @@
 import com.android.credentialmanager.createflow.CreateCredentialUiState
 import com.android.credentialmanager.createflow.CreateScreenState
 import com.android.credentialmanager.createflow.EnabledProviderInfo
+import com.android.credentialmanager.createflow.RemoteInfo
+import com.android.credentialmanager.createflow.RequestDisplayInfo
 import com.android.credentialmanager.getflow.GetCredentialUiState
 import com.android.credentialmanager.getflow.GetScreenState
 import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest.Companion.toBundle
@@ -57,7 +59,7 @@
 ) {
   val requestInfo: RequestInfo
   private val providerEnabledList: List<ProviderData>
-  private val providerDisabledList: List<DisabledProviderData>
+  private val providerDisabledList: List<DisabledProviderData>?
   // TODO: require non-null.
   val resultReceiver: ResultReceiver?
 
@@ -141,26 +143,23 @@
       providerEnabledList as List<CreateCredentialProviderData>, requestDisplayInfo, context)
     val providerDisabledList = CreateFlowUtils.toDisabledProviderList(
       // Handle runtime cast error
-      providerDisabledList as List<DisabledProviderData>, context)
-    var hasDefault = false
-    var defaultProvider: EnabledProviderInfo = providerEnabledList.first()
+      providerDisabledList, context)
+    var defaultProvider: EnabledProviderInfo? = null
+    var remoteEntry: RemoteInfo? = null
     providerEnabledList.forEach{providerInfo -> providerInfo.createOptions =
       providerInfo.createOptions.sortedWith(compareBy { it.lastUsedTimeMillis }).reversed()
-      if (providerInfo.isDefault) {hasDefault = true; defaultProvider = providerInfo} }
+      if (providerInfo.isDefault) {defaultProvider = providerInfo}
+      if (providerInfo.remoteEntry != null) {
+        remoteEntry = providerInfo.remoteEntry!!
+      }
+    }
     return CreateCredentialUiState(
       enabledProviders = providerEnabledList,
       disabledProviders = providerDisabledList,
-      // TODO: Add the screen when defaultProvider has no createOption but
-      //  there's remoteInfo under other providers
-      if (!hasDefault || defaultProvider.createOptions.isEmpty()) {
-        if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL)
-        {CreateScreenState.PASSKEY_INTRO} else {CreateScreenState.PROVIDER_SELECTION}
-      } else {CreateScreenState.CREATION_OPTION_SELECTION},
+      toCreateScreenState(requestDisplayInfo, defaultProvider, remoteEntry),
       requestDisplayInfo,
       false,
-      if (hasDefault) {
-        ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
-      } else null
+      toActiveEntry(defaultProvider, remoteEntry),
     )
   }
 
@@ -210,7 +209,7 @@
     )
   }
 
-  private fun testDisabledProviderList(): List<DisabledProviderData> {
+  private fun testDisabledProviderList(): List<DisabledProviderData>? {
     return listOf(
       DisabledProviderData("com.lastpass.lpandroid"),
       DisabledProviderData("com.google.android.youtube")
@@ -459,12 +458,15 @@
             "                     \"residentKey\": \"required\",\n" +
             "                     \"requireResidentKey\": true\n" +
             "                   }}")
-    val data = request.data
+    val credentialData = request.data
     return RequestInfo.newCreateRequestInfo(
       Binder(),
       CreateCredentialRequest(
         TYPE_PUBLIC_KEY_CREDENTIAL,
-        data
+        credentialData,
+        // TODO: populate with actual data
+        /*candidateQueryData=*/ Bundle(),
+        /*requireSystemProvider=*/ false
       ),
       /*isFirstUsage=*/false,
       "tribank"
@@ -477,7 +479,10 @@
       Binder(),
       CreateCredentialRequest(
         TYPE_PASSWORD_CREDENTIAL,
-        data
+        data,
+        // TODO: populate with actual data
+        /*candidateQueryData=*/ Bundle(),
+        /*requireSystemProvider=*/ false
       ),
       /*isFirstUsage=*/false,
       "tribank"
@@ -490,7 +495,9 @@
       Binder(),
       CreateCredentialRequest(
         "other-sign-ins",
-        data
+        data,
+        /*candidateQueryData=*/ Bundle(),
+        /*requireSystemProvider=*/ false
       ),
       /*isFirstUsage=*/false,
       "tribank"
@@ -502,11 +509,46 @@
       Binder(),
       GetCredentialRequest.Builder()
         .addGetCredentialOption(
-          GetCredentialOption(TYPE_PUBLIC_KEY_CREDENTIAL, Bundle())
+          GetCredentialOption(
+            TYPE_PUBLIC_KEY_CREDENTIAL, Bundle(), /*requireSystemProvider=*/ false)
         )
         .build(),
       /*isFirstUsage=*/false,
       "tribank.us"
     )
   }
+
+  private fun toCreateScreenState(
+    requestDisplayInfo: RequestDisplayInfo,
+    defaultProvider: EnabledProviderInfo?,
+    remoteEntry: RemoteInfo?,
+  ): CreateScreenState {
+    return if (
+      defaultProvider != null && defaultProvider.createOptions.isEmpty() && remoteEntry != null
+    ){
+      CreateScreenState.EXTERNAL_ONLY_SELECTION
+    } else if (defaultProvider == null || defaultProvider.createOptions.isEmpty()) {
+      if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL) {
+        CreateScreenState.PASSKEY_INTRO
+      } else {
+        CreateScreenState.PROVIDER_SELECTION
+      }
+    } else {
+      CreateScreenState.CREATION_OPTION_SELECTION
+    }
+  }
+
+  private fun toActiveEntry(
+    defaultProvider: EnabledProviderInfo?,
+    remoteEntry: RemoteInfo?,
+  ): ActiveEntry? {
+    return if (
+      defaultProvider != null && defaultProvider.createOptions.isNotEmpty()
+    ) {
+      ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
+    } else if (
+      defaultProvider != null && defaultProvider.createOptions.isEmpty() && remoteEntry != null) {
+      ActiveEntry(defaultProvider, remoteEntry)
+    } else null
+  }
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index b96f686..357c55d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -209,12 +209,12 @@
     }
 
     fun toDisabledProviderList(
-      providerDataList: List<DisabledProviderData>,
+      providerDataList: List<DisabledProviderData>?,
       context: Context,
-    ): List<com.android.credentialmanager.createflow.DisabledProviderInfo> {
+    ): List<com.android.credentialmanager.createflow.DisabledProviderInfo>? {
       // TODO: get from the actual service info
       val packageManager = context.packageManager
-      return providerDataList.map {
+      return providerDataList?.map {
         val pkgInfo = packageManager
           .getPackageInfo(it.providerFlattenedComponentName,
             PackageManager.PackageInfoFlags.of(0))
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index fde3279..5552d05 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -60,7 +60,7 @@
     viewModel.onEntrySelected(it, providerActivityLauncher)
   }
   val confirmEntryCallback: () -> Unit = {
-    viewModel.onConfirmCreationSelected(providerActivityLauncher)
+    viewModel.onConfirmEntrySelected(providerActivityLauncher)
   }
   val state = rememberModalBottomSheetState(
     initialValue = ModalBottomSheetValue.Expanded,
@@ -108,9 +108,16 @@
           providerInfo = uiState.activeEntry?.activeProvider!!,
           onDefaultOrNotSelected = viewModel::onDefaultOrNotSelected
         )
+        CreateScreenState.EXTERNAL_ONLY_SELECTION -> ExternalOnlySelectionCard(
+          requestDisplayInfo = uiState.requestDisplayInfo,
+          activeRemoteEntry = uiState.activeEntry?.activeEntryInfo!!,
+          onOptionSelected = selectEntryCallback,
+          onConfirm = confirmEntryCallback,
+          onCancel = viewModel::onCancel,
+        )
       }
     },
-    scrimColor = MaterialTheme.colorScheme.scrim,
+    scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = 0.8f),
     sheetShape = EntryShape.TopRoundedCorner,
   ) {}
   LaunchedEffect(state.currentValue) {
@@ -489,7 +496,7 @@
       ) {
         PrimaryCreateOptionRow(
           requestDisplayInfo = requestDisplayInfo,
-          createOptionInfo = createOptionInfo,
+          entryInfo = createOptionInfo,
           onOptionSelected = onOptionSelected
         )
       }
@@ -562,16 +569,85 @@
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
+fun ExternalOnlySelectionCard(
+  requestDisplayInfo: RequestDisplayInfo,
+  activeRemoteEntry: EntryInfo,
+  onOptionSelected: (EntryInfo) -> Unit,
+  onConfirm: () -> Unit,
+  onCancel: () -> Unit,
+) {
+  Card() {
+    Column() {
+      Icon(
+        painter = painterResource(R.drawable.ic_other_devices),
+        contentDescription = null,
+        tint = Color.Unspecified,
+        modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
+          .padding(all = 24.dp).size(32.dp)
+      )
+      Text(
+        text = stringResource(R.string.create_passkey_in_other_device_title),
+        style = MaterialTheme.typography.titleMedium,
+        modifier = Modifier.padding(horizontal = 24.dp)
+          .align(alignment = Alignment.CenterHorizontally),
+        textAlign = TextAlign.Center,
+      )
+      Divider(
+        thickness = 24.dp,
+        color = Color.Transparent
+      )
+      Card(
+        shape = MaterialTheme.shapes.medium,
+        modifier = Modifier
+          .padding(horizontal = 24.dp)
+          .align(alignment = Alignment.CenterHorizontally),
+      ) {
+        PrimaryCreateOptionRow(
+          requestDisplayInfo = requestDisplayInfo,
+          entryInfo = activeRemoteEntry,
+          onOptionSelected = onOptionSelected
+        )
+      }
+      Divider(
+        thickness = 24.dp,
+        color = Color.Transparent
+      )
+      Row(
+        horizontalArrangement = Arrangement.SpaceBetween,
+        modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+      ) {
+        CancelButton(
+          stringResource(R.string.string_cancel),
+          onClick = onCancel
+        )
+        ConfirmButton(
+          stringResource(R.string.string_continue),
+          onClick = onConfirm
+        )
+      }
+      Divider(
+        thickness = 18.dp,
+        color = Color.Transparent,
+        modifier = Modifier.padding(bottom = 16.dp)
+      )
+    }
+  }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
 fun PrimaryCreateOptionRow(
   requestDisplayInfo: RequestDisplayInfo,
-  createOptionInfo: CreateOptionInfo,
+  entryInfo: EntryInfo,
   onOptionSelected: (EntryInfo) -> Unit
 ) {
   Entry(
-    onClick = {onOptionSelected(createOptionInfo)},
+    onClick = {onOptionSelected(entryInfo)},
     icon = {
       Icon(
-        bitmap = createOptionInfo.profileIcon.toBitmap().asImageBitmap(),
+        bitmap = if (entryInfo is CreateOptionInfo) {
+          entryInfo.profileIcon.toBitmap().asImageBitmap()
+        } else {requestDisplayInfo.typeIcon.toBitmap().asImageBitmap()},
         contentDescription = null,
         tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
         modifier = Modifier.padding(start = 18.dp).size(32.dp)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
index 0f685a1..393cf7d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
@@ -155,7 +155,7 @@
     }
   }
 
-  fun onConfirmCreationSelected(
+  fun onConfirmEntrySelected(
     launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
   ) {
     val selectedEntry = uiState.activeEntry?.activeEntryInfo
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 31d0365..9ac524a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -95,4 +95,5 @@
   CREATION_OPTION_SELECTION,
   MORE_OPTIONS_SELECTION,
   MORE_OPTIONS_ROW_INTRO,
+  EXTERNAL_ONLY_SELECTION,
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 8ccdf4c..720f231 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -61,6 +61,7 @@
 import com.android.credentialmanager.common.ui.Entry
 import com.android.credentialmanager.common.ui.TransparentBackgroundEntry
 import com.android.credentialmanager.jetpack.developer.PublicKeyCredential
+import com.android.credentialmanager.ui.theme.EntryShape
 
 @Composable
 fun GetCredentialScreen(
@@ -94,8 +95,8 @@
         )
       }
     },
-    scrimColor = Color.Transparent,
-    sheetShape = MaterialTheme.shapes.medium,
+    scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = 0.8f),
+    sheetShape = EntryShape.TopRoundedCorner,
   ) {}
   LaunchedEffect(state.currentValue) {
     if (state.currentValue == ModalBottomSheetValue.Hidden) {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt
index 7e7dbde..008e1b6 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt
@@ -38,14 +38,18 @@
             return try {
                 when (from.type) {
                     Credential.TYPE_PASSWORD_CREDENTIAL ->
-                        CreatePasswordRequest.createFrom(from.data)
+                        CreatePasswordRequest.createFrom(from.credentialData)
                     PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL ->
-                        CreatePublicKeyCredentialBaseRequest.createFrom(from.data)
+                        CreatePublicKeyCredentialBaseRequest.createFrom(from.credentialData)
                     else ->
-                        CreateCredentialRequest(from.type, from.data, from.requireSystemProvider())
+                        CreateCredentialRequest(
+                            from.type, from.credentialData, from.requireSystemProvider()
+                        )
                 }
             } catch (e: FrameworkClassParsingException) {
-                CreateCredentialRequest(from.type, from.data, from.requireSystemProvider())
+                CreateCredentialRequest(
+                    from.type, from.credentialData, from.requireSystemProvider()
+                )
             }
         }
     }
diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index 71c52d9..37d6b42 100644
--- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -36,7 +36,7 @@
         </activity>
 
         <provider
-            android:name="com.android.settingslib.spa.framework.SpaSearchProvider"
+            android:name="com.android.settingslib.spa.search.SpaSearchProvider"
             android:authorities="com.android.spa.gallery.search.provider"
             android:enabled="true"
             android:exported="false">
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
index 0c9a043..238204a 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
@@ -36,6 +36,7 @@
 import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.toState
 import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.util.createIntent
 import com.android.settingslib.spa.gallery.R
 import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
 import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_SUMMARY
@@ -91,6 +92,7 @@
                     spaLogger.message(TAG, "create macro for ${EntryEnum.SIMPLE_PREFERENCE}")
                     SimplePreferenceMacro(title = SIMPLE_PREFERENCE_TITLE)
                 }
+                .setStatusDataFn { EntryStatusData(isDisabled = false) }
                 .build()
         )
         entryList.add(
@@ -103,6 +105,7 @@
                         searchKeywords = SIMPLE_PREFERENCE_KEYWORDS,
                     )
                 }
+                .setStatusDataFn { EntryStatusData(isDisabled = true) }
                 .build()
         )
         entryList.add(singleLineSummaryEntry())
@@ -269,7 +272,7 @@
                 )
             }
             .setSliceDataFn { sliceUri, _ ->
-                val intent = owner.createBrowseIntent()?.createBrowsePendingIntent()
+                val intent = owner.createIntent()?.createBrowsePendingIntent()
                     ?: return@setSliceDataFn null
                 return@setSliceDataFn object : EntrySliceData() {
                     init {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
index 238268a..f7cbdae 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
@@ -39,7 +39,10 @@
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.compose.toState
 import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.slice.appendSliceParams
+import com.android.settingslib.spa.framework.util.SESSION_BROWSE
+import com.android.settingslib.spa.framework.util.SESSION_SEARCH
+import com.android.settingslib.spa.framework.util.createIntent
+import com.android.settingslib.spa.slice.fromEntry
 import com.android.settingslib.spa.slice.presenter.SliceDemo
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
@@ -158,14 +161,13 @@
             remember { entryRepository.getAllEntries().filter { it.hasSliceSupport } }
         RegularScaffold(title = "All Slices (${allSliceEntry.size})") {
             for (entry in allSliceEntry) {
-                SliceDemo(sliceUri = entry.createSliceUri(authority))
+                SliceDemo(sliceUri = Uri.Builder().fromEntry(entry, authority).build())
             }
         }
     }
 
     @Composable
     fun OnePage(arguments: Bundle?) {
-        val context = LocalContext.current
         val entryRepository by spaEnvironment.entryRepository
         val id = arguments!!.getString(PARAM_NAME_PAGE_ID, "")
         val pageWithEntry = entryRepository.getPageWithEntry(id)!!
@@ -176,8 +178,8 @@
             Text(text = "Entry size: ${pageWithEntry.entries.size}")
             Preference(model = object : PreferenceModel {
                 override val title = "open page"
-                override val enabled =
-                    page.isBrowsable(context, spaEnvironment.browseActivityClass).toState()
+                override val enabled = (spaEnvironment.browseActivityClass != null &&
+                    page.isBrowsable()).toState()
                 override val onClick = openPage(page)
             })
             EntryList(pageWithEntry.entries)
@@ -186,7 +188,6 @@
 
     @Composable
     fun OneEntry(arguments: Bundle?) {
-        val context = LocalContext.current
         val entryRepository by spaEnvironment.entryRepository
         val id = arguments!!.getString(PARAM_NAME_ENTRY_ID, "")
         val entry = entryRepository.getEntry(id)!!
@@ -194,9 +195,9 @@
         RegularScaffold(title = "Entry - ${entry.debugBrief()}") {
             Preference(model = object : PreferenceModel {
                 override val title = "open entry"
-                override val enabled =
-                    entry.containerPage().isBrowsable(context, spaEnvironment.browseActivityClass)
-                        .toState()
+                override val enabled = (spaEnvironment.browseActivityClass != null &&
+                    entry.containerPage().isBrowsable())
+                    .toState()
                 override val onClick = openEntry(entry)
             })
             Text(text = entryContent)
@@ -219,7 +220,7 @@
     private fun openPage(page: SettingsPage): (() -> Unit)? {
         val context = LocalContext.current
         val intent =
-            page.createBrowseIntent(context, spaEnvironment.browseActivityClass) ?: return null
+            page.createIntent(SESSION_BROWSE) ?: return null
         val route = page.buildRoute()
         return {
             spaEnvironment.logger.message(
@@ -232,8 +233,7 @@
     @Composable
     private fun openEntry(entry: SettingsEntry): (() -> Unit)? {
         val context = LocalContext.current
-        val intent = entry.containerPage()
-            .createBrowseIntent(context, spaEnvironment.browseActivityClass, entry.id)
+        val intent = entry.createIntent(SESSION_SEARCH)
             ?: return null
         val route = entry.containerPage().buildRoute()
         return {
@@ -245,18 +245,6 @@
     }
 }
 
-private fun SettingsEntry.createSliceUri(
-    authority: String?,
-    runtimeArguments: Bundle? = null
-): Uri {
-    if (authority == null) return Uri.EMPTY
-    return Uri.Builder().scheme("content").authority(authority).appendSliceParams(
-        route = this.containerPage().buildRoute(),
-        entryId = this.id,
-        runtimeArguments = runtimeArguments,
-    ).build()
-}
-
 /**
  * A blank activity without any page.
  */
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
index 3df7727..59ec985 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
@@ -32,6 +32,12 @@
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
 import com.android.settingslib.spa.framework.common.addUri
 import com.android.settingslib.spa.framework.common.getColumns
+import com.android.settingslib.spa.framework.util.KEY_DESTINATION
+import com.android.settingslib.spa.framework.util.KEY_HIGHLIGHT_ENTRY
+import com.android.settingslib.spa.framework.util.KEY_SESSION_SOURCE_NAME
+import com.android.settingslib.spa.framework.util.SESSION_BROWSE
+import com.android.settingslib.spa.framework.util.SESSION_SEARCH
+import com.android.settingslib.spa.framework.util.createIntent
 
 private const val TAG = "DebugProvider"
 
@@ -116,9 +122,11 @@
         val entryRepository by spaEnvironment.entryRepository
         val cursor = MatrixCursor(QueryEnum.PAGE_DEBUG_QUERY.getColumns())
         for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
-            val command = pageWithEntry.page.createBrowseAdbCommand(
-                context,
-                spaEnvironment.browseActivityClass
+            val page = pageWithEntry.page
+            if (!page.isBrowsable()) continue
+            val command = createBrowseAdbCommand(
+                destination = page.buildRoute(),
+                sessionName = SESSION_BROWSE
             )
             if (command != null) {
                 cursor.newRow().add(ColumnEnum.PAGE_START_ADB.id, command)
@@ -131,8 +139,13 @@
         val entryRepository by spaEnvironment.entryRepository
         val cursor = MatrixCursor(QueryEnum.ENTRY_DEBUG_QUERY.getColumns())
         for (entry in entryRepository.getAllEntries()) {
-            val command = entry.containerPage()
-                .createBrowseAdbCommand(context, spaEnvironment.browseActivityClass, entry.id)
+            val page = entry.containerPage()
+            if (!page.isBrowsable()) continue
+            val command = createBrowseAdbCommand(
+                destination = page.buildRoute(),
+                entryId = entry.id,
+                sessionName = SESSION_SEARCH
+            )
             if (command != null) {
                 cursor.newRow().add(ColumnEnum.ENTRY_START_ADB.id, command)
             }
@@ -145,8 +158,7 @@
         val cursor = MatrixCursor(QueryEnum.PAGE_INFO_QUERY.getColumns())
         for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
             val page = pageWithEntry.page
-            val intent =
-                page.createBrowseIntent(context, spaEnvironment.browseActivityClass) ?: Intent()
+            val intent = page.createIntent(SESSION_BROWSE) ?: Intent()
             cursor.newRow()
                 .add(ColumnEnum.PAGE_ID.id, page.id)
                 .add(ColumnEnum.PAGE_NAME.id, page.displayName)
@@ -162,17 +174,36 @@
         val entryRepository by spaEnvironment.entryRepository
         val cursor = MatrixCursor(QueryEnum.ENTRY_INFO_QUERY.getColumns())
         for (entry in entryRepository.getAllEntries()) {
-            val intent = entry.containerPage()
-                .createBrowseIntent(context, spaEnvironment.browseActivityClass, entry.id)
-                ?: Intent()
+            val intent = entry.createIntent(SESSION_SEARCH) ?: Intent()
             cursor.newRow()
                 .add(ColumnEnum.ENTRY_ID.id, entry.id)
                 .add(ColumnEnum.ENTRY_NAME.id, entry.displayName)
                 .add(ColumnEnum.ENTRY_ROUTE.id, entry.containerPage().buildRoute())
                 .add(ColumnEnum.ENTRY_INTENT_URI.id, intent.toUri(URI_INTENT_SCHEME))
-                .add(ColumnEnum.ENTRY_HIERARCHY_PATH.id,
-                    entryRepository.getEntryPathWithDisplayName(entry.id))
+                .add(
+                    ColumnEnum.ENTRY_HIERARCHY_PATH.id,
+                    entryRepository.getEntryPathWithDisplayName(entry.id)
+                )
         }
         return cursor
     }
 }
+
+private fun createBrowseAdbCommand(
+    destination: String? = null,
+    entryId: String? = null,
+    sessionName: String? = null,
+): String? {
+    val context = SpaEnvironmentFactory.instance.appContext
+    val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
+    val packageName = context.packageName
+    val activityName = browseActivityClass.name.replace(packageName, "")
+    val destinationParam =
+        if (destination != null) " -e $KEY_DESTINATION $destination" else ""
+    val highlightParam =
+        if (entryId != null) " -e $KEY_HIGHLIGHT_ENTRY $entryId" else ""
+    val sessionParam =
+        if (sessionName != null) " -e $KEY_SESSION_SOURCE_NAME $sessionName" else ""
+    return "adb shell am start -n $packageName/$activityName" +
+        "$destinationParam$highlightParam$sessionParam"
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index 6ed7481..aa10cc8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -16,21 +16,17 @@
 
 package com.android.settingslib.spa.framework
 
+import android.content.Intent
 import android.os.Bundle
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
 import androidx.annotation.VisibleForTesting
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.core.view.WindowCompat
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleEventObserver
 import androidx.navigation.NavGraph.Companion.findStartDestination
 import androidx.navigation.compose.NavHost
 import androidx.navigation.compose.composable
@@ -39,12 +35,16 @@
 import com.android.settingslib.spa.framework.common.LogCategory
 import com.android.settingslib.spa.framework.common.SettingsPage
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.LocalNavController
 import com.android.settingslib.spa.framework.compose.NavControllerWrapperImpl
 import com.android.settingslib.spa.framework.compose.localNavController
 import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.util.PageEvent
+import com.android.settingslib.spa.framework.util.getDestination
+import com.android.settingslib.spa.framework.util.getEntryId
+import com.android.settingslib.spa.framework.util.getSessionName
 import com.android.settingslib.spa.framework.util.navRoute
 
 private const val TAG = "BrowseActivity"
@@ -77,57 +77,20 @@
         setContent {
             SettingsTheme {
                 val sppRepository by spaEnvironment.pageProviderRepository
-                BrowseContent(
-                    allProviders = sppRepository.getAllProviders(),
-                    initialDestination = intent?.getStringExtra(KEY_DESTINATION)
-                        ?: sppRepository.getDefaultStartPage(),
-                    initialEntryId = intent?.getStringExtra(KEY_HIGHLIGHT_ENTRY)
-                )
+                BrowseContent(sppRepository, intent)
             }
         }
     }
-
-    companion object {
-        const val KEY_DESTINATION = "spaActivityDestination"
-        const val KEY_HIGHLIGHT_ENTRY = "highlightEntry"
-    }
 }
 
 @VisibleForTesting
 @Composable
-fun BrowseContent(
-    allProviders: Collection<SettingsPageProvider>,
-    initialDestination: String,
-    initialEntryId: String?
-) {
+fun BrowseContent(sppRepository: SettingsPageProviderRepository, initialIntent: Intent? = null) {
     val navController = rememberNavController()
     CompositionLocalProvider(navController.localNavController()) {
         val controller = LocalNavController.current as NavControllerWrapperImpl
-        controller.NavContent(allProviders)
-        controller.InitialDestination(initialDestination, initialEntryId)
-    }
-}
-
-@Composable
-private fun SettingsPageProvider.PageEvents(arguments: Bundle? = null) {
-    val page = remember(arguments) { createSettingsPage(arguments) }
-    val lifecycleOwner = LocalLifecycleOwner.current
-    DisposableEffect(lifecycleOwner) {
-        val observer = LifecycleEventObserver { _, event ->
-            if (event == Lifecycle.Event.ON_START) {
-                page.enterPage()
-            } else if (event == Lifecycle.Event.ON_STOP) {
-                page.leavePage()
-            }
-        }
-
-        // Add the observer to the lifecycle
-        lifecycleOwner.lifecycle.addObserver(observer)
-
-        // When the effect leaves the Composition, remove the observer
-        onDispose {
-            lifecycleOwner.lifecycle.removeObserver(observer)
-        }
+        controller.NavContent(sppRepository.getAllProviders())
+        controller.InitialDestination(initialIntent, sppRepository.getDefaultStartPage())
     }
 }
 
@@ -144,7 +107,7 @@
                 route = spp.name + spp.parameter.navRoute(),
                 arguments = spp.parameter,
             ) { navBackStackEntry ->
-                spp.PageEvents(navBackStackEntry.arguments)
+                spp.PageEvent(navBackStackEntry.arguments)
                 spp.Page(navBackStackEntry.arguments)
             }
         }
@@ -153,17 +116,22 @@
 
 @Composable
 private fun NavControllerWrapperImpl.InitialDestination(
-    destination: String,
-    highlightEntryId: String?
+    initialIntent: Intent?,
+    defaultDestination: String
 ) {
     val destinationNavigated = rememberSaveable { mutableStateOf(false) }
     if (destinationNavigated.value) return
     destinationNavigated.value = true
 
-    if (destination.isEmpty()) return
+    val initialDestination = initialIntent?.getDestination() ?: defaultDestination
+    if (initialDestination.isEmpty()) return
+    val initialEntryId = initialIntent?.getEntryId()
+    val sessionSourceName = initialIntent?.getSessionName()
+
     LaunchedEffect(Unit) {
-        highlightId = highlightEntryId
-        navController.navigate(destination) {
+        highlightId = initialEntryId
+        sessionName = sessionSourceName
+        navController.navigate(initialDestination) {
             popUpTo(navController.graph.findStartDestination().id) {
                 inclusive = true
             }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt
index 121c07f..61b46be 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt
@@ -17,6 +17,7 @@
 package com.android.settingslib.spa.framework.common
 
 import android.content.UriMatcher
+import androidx.annotation.VisibleForTesting
 
 /**
  * Enum to define all column names in provider.
@@ -125,14 +126,17 @@
     ),
 }
 
-internal fun QueryEnum.getColumns(): Array<String> {
+@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+fun QueryEnum.getColumns(): Array<String> {
     return columnNames.map { it.id }.toTypedArray()
 }
 
-internal fun QueryEnum.getIndex(name: ColumnEnum): Int {
+@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+fun QueryEnum.getIndex(name: ColumnEnum): Int {
     return columnNames.indexOf(name)
 }
 
-internal fun QueryEnum.addUri(uriMatcher: UriMatcher, authority: String) {
+@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+fun QueryEnum.addUri(uriMatcher: UriMatcher, authority: String) {
     uriMatcher.addURI(authority, queryPath, queryMatchCode)
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
index 9ee7f9e..702c075 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
@@ -219,11 +219,6 @@
         return this
     }
 
-    fun setIsAllowSearch(isAllowSearch: Boolean): SettingsEntryBuilder {
-        this.isAllowSearch = isAllowSearch
-        return this
-    }
-
     fun setIsSearchDataDynamic(isDynamic: Boolean): SettingsEntryBuilder {
         this.isSearchDataDynamic = isDynamic
         return this
@@ -251,6 +246,13 @@
 
     fun setSearchDataFn(fn: SearchDataGetter): SettingsEntryBuilder {
         this.searchDataFn = fn
+        this.isAllowSearch = true
+        return this
+    }
+
+    fun clearSearchDataFn(): SettingsEntryBuilder {
+        this.searchDataFn = { null }
+        this.isAllowSearch = false
         return this
     }
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index 82e05a5..7a39b73 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -16,13 +16,8 @@
 
 package com.android.settingslib.spa.framework.common
 
-import android.app.Activity
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
 import android.os.Bundle
 import androidx.navigation.NamedNavArgument
-import com.android.settingslib.spa.framework.BrowseActivity
 import com.android.settingslib.spa.framework.util.isRuntimeParam
 import com.android.settingslib.spa.framework.util.navLink
 import com.android.settingslib.spa.framework.util.normalize
@@ -95,63 +90,8 @@
         return false
     }
 
-    fun enterPage() {
-        SpaEnvironmentFactory.instance.logger.event(
-            id,
-            LogEvent.PAGE_ENTER,
-            category = LogCategory.FRAMEWORK,
-            details = displayName,
-        )
-    }
-
-    fun leavePage() {
-        SpaEnvironmentFactory.instance.logger.event(
-            id,
-            LogEvent.PAGE_LEAVE,
-            category = LogCategory.FRAMEWORK,
-            details = displayName,
-        )
-    }
-
-    fun createBrowseIntent(entryId: String? = null): Intent? {
-        val context = SpaEnvironmentFactory.instance.appContext
-        val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass
-        return createBrowseIntent(context, browseActivityClass, entryId)
-    }
-
-    fun createBrowseIntent(
-        context: Context?,
-        browseActivityClass: Class<out Activity>?,
-        entryId: String? = null
-    ): Intent? {
-        if (!isBrowsable(context, browseActivityClass)) return null
-        return Intent().setComponent(ComponentName(context!!, browseActivityClass!!))
-            .apply {
-                putExtra(BrowseActivity.KEY_DESTINATION, buildRoute())
-                if (entryId != null) {
-                    putExtra(BrowseActivity.KEY_HIGHLIGHT_ENTRY, entryId)
-                }
-            }
-    }
-
-    fun createBrowseAdbCommand(
-        context: Context?,
-        browseActivityClass: Class<out Activity>?,
-        entryId: String? = null
-    ): String? {
-        if (!isBrowsable(context, browseActivityClass)) return null
-        val packageName = context!!.packageName
-        val activityName = browseActivityClass!!.name.replace(packageName, "")
-        val destinationParam = " -e ${BrowseActivity.KEY_DESTINATION} ${buildRoute()}"
-        val highlightParam =
-            if (entryId != null) " -e ${BrowseActivity.KEY_HIGHLIGHT_ENTRY} $entryId" else ""
-        return "adb shell am start -n $packageName/$activityName$destinationParam$highlightParam"
-    }
-
-    fun isBrowsable(context: Context?, browseActivityClass: Class<out Activity>?): Boolean {
-        return context != null &&
-            browseActivityClass != null &&
-            !isCreateBy(NULL_PAGE_NAME) &&
+    fun isBrowsable(): Boolean {
+        return !isCreateBy(NULL_PAGE_NAME) &&
             !hasRuntimeParam()
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
index 00a0362..6ecb7fa 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.spa.framework.common
 
+import android.os.Bundle
 import android.util.Log
 
 // Defines the category of the log, for quick filter
@@ -38,10 +39,13 @@
 
     // Entry related events.
     ENTRY_CLICK,
-    ENTRY_SWITCH_ON,
-    ENTRY_SWITCH_OFF,
+    ENTRY_SWITCH,
 }
 
+internal const val LOG_DATA_DISPLAY_NAME = "name"
+internal const val LOG_DATA_SESSION_NAME = "session"
+internal const val LOG_DATA_SWITCH_STATUS = "switch"
+
 /**
  * The interface of logger in Spa
  */
@@ -54,7 +58,7 @@
         id: String,
         event: LogEvent,
         category: LogCategory = LogCategory.DEFAULT,
-        details: String? = null
+        extraData: Bundle = Bundle.EMPTY
     ) {
     }
 }
@@ -64,8 +68,8 @@
         Log.d("SpaMsg-$category", "[$tag] $msg")
     }
 
-    override fun event(id: String, event: LogEvent, category: LogCategory, details: String?) {
-        val extraMsg = if (details == null) "" else " ($details)"
-        Log.d("SpaEvent-$category", "[$id] $event$extraMsg")
+    override fun event(id: String, event: LogEvent, category: LogCategory, extraData: Bundle) {
+        val extraMsg = extraData.toString().removeRange(0, 6)
+        Log.d("SpaEvent-$category", "[$id] $event $extraMsg")
     }
 }
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
index 382c498..eb2bffe 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
@@ -29,7 +29,10 @@
     fun navigateBack()
 
     val highlightEntryId: String?
-      get() = null
+        get() = null
+
+    val sessionSourceName: String?
+        get() = null
 }
 
 @Composable
@@ -63,6 +66,7 @@
     private val onBackPressedDispatcher: OnBackPressedDispatcher?,
 ) : NavControllerWrapper {
     var highlightId: String? = null
+    var sessionName: String? = null
 
     override fun navigate(route: String) {
         navController.navigate(route)
@@ -73,5 +77,8 @@
     }
 
     override val highlightEntryId: String?
-      get() = highlightId
+        get() = highlightId
+
+    override val sessionSourceName: String?
+        get() = sessionName
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/WidgetLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt
similarity index 74%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/WidgetLogger.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt
index 8d0a35c..1c88187 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/WidgetLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt
@@ -16,17 +16,22 @@
 
 package com.android.settingslib.spa.framework.util
 
+import android.os.Bundle
 import androidx.compose.runtime.Composable
+import androidx.core.os.bundleOf
+import com.android.settingslib.spa.framework.common.LOG_DATA_SWITCH_STATUS
 import com.android.settingslib.spa.framework.common.LocalEntryDataProvider
 import com.android.settingslib.spa.framework.common.LogCategory
 import com.android.settingslib.spa.framework.common.LogEvent
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
 
 @Composable
-fun logEntryEvent(): (event: LogEvent) -> Unit {
-    val entryId = LocalEntryDataProvider.current.entryId ?: return {}
-    return {
-        SpaEnvironmentFactory.instance.logger.event(entryId, it, category = LogCategory.VIEW)
+fun logEntryEvent(): (event: LogEvent, extraData: Bundle) -> Unit {
+    val entryId = LocalEntryDataProvider.current.entryId ?: return { _, _ -> }
+    return { event, extraData ->
+        SpaEnvironmentFactory.instance.logger.event(
+            entryId, event, category = LogCategory.VIEW, extraData = extraData
+        )
     }
 }
 
@@ -35,7 +40,7 @@
     if (onClick == null) return null
     val logEvent = logEntryEvent()
     return {
-        logEvent(LogEvent.ENTRY_CLICK)
+        logEvent(LogEvent.ENTRY_CLICK, Bundle.EMPTY)
         onClick()
     }
 }
@@ -45,8 +50,7 @@
     if (onSwitch == null) return null
     val logEvent = logEntryEvent()
     return {
-        val event = if (it) LogEvent.ENTRY_SWITCH_ON else LogEvent.ENTRY_SWITCH_OFF
-        logEvent(event)
+        logEvent(LogEvent.ENTRY_SWITCH, bundleOf(LOG_DATA_SWITCH_STATUS to it))
         onSwitch(it)
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt
index d801840..97e3ac2 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt
@@ -27,6 +27,13 @@
 import kotlinx.coroutines.flow.map
 
 /**
+ * Returns a [Flow] whose values are a list which containing the results of applying the given
+ * [transform] function to each element in the original flow's list.
+ */
+inline fun <T, R> Flow<List<T>>.mapItem(crossinline transform: (T) -> R): Flow<List<R>> =
+    map { list -> list.map(transform) }
+
+/**
  * Returns a [Flow] whose values are a list which containing the results of asynchronously applying
  * the given [transform] function to each element in the original flow's list.
  */
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
new file mode 100644
index 0000000..a881254
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.framework.util
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.core.os.bundleOf
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import com.android.settingslib.spa.framework.common.LOG_DATA_DISPLAY_NAME
+import com.android.settingslib.spa.framework.common.LOG_DATA_SESSION_NAME
+import com.android.settingslib.spa.framework.common.LogCategory
+import com.android.settingslib.spa.framework.common.LogEvent
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.LocalNavController
+
+@Composable
+internal fun SettingsPageProvider.PageEvent(arguments: Bundle? = null) {
+    val page = remember(arguments) { createSettingsPage(arguments) }
+    val lifecycleOwner = LocalLifecycleOwner.current
+    val navController = LocalNavController.current
+    DisposableEffect(lifecycleOwner) {
+        val observer = LifecycleEventObserver { _, event ->
+            val logPageEvent: (event: LogEvent) -> Unit = {
+                SpaEnvironmentFactory.instance.logger.event(
+                    id = page.id,
+                    event = it,
+                    category = LogCategory.FRAMEWORK,
+                    extraData = bundleOf(
+                        LOG_DATA_DISPLAY_NAME to page.displayName,
+                        LOG_DATA_SESSION_NAME to navController.sessionSourceName,
+                    )
+                )
+            }
+            if (event == Lifecycle.Event.ON_START) {
+                logPageEvent(LogEvent.PAGE_ENTER)
+            } else if (event == Lifecycle.Event.ON_STOP) {
+                logPageEvent(LogEvent.PAGE_LEAVE)
+            }
+        }
+
+        // Add the observer to the lifecycle
+        lifecycleOwner.lifecycle.addObserver(observer)
+
+        // When the effect leaves the Composition, remove the observer
+        onDispose {
+            lifecycleOwner.lifecycle.removeObserver(observer)
+        }
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt
new file mode 100644
index 0000000..2c3c2e0
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.framework.util
+
+import android.content.ComponentName
+import android.content.Intent
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+
+const val SESSION_BROWSE = "browse"
+const val SESSION_SEARCH = "search"
+const val SESSION_SLICE = "slice"
+
+const val KEY_DESTINATION = "spaActivityDestination"
+const val KEY_HIGHLIGHT_ENTRY = "highlightEntry"
+const val KEY_SESSION_SOURCE_NAME = "sessionSource"
+
+val SPA_INTENT_RESERVED_KEYS = listOf(
+    KEY_DESTINATION,
+    KEY_HIGHLIGHT_ENTRY,
+    KEY_SESSION_SOURCE_NAME
+)
+
+private fun createBaseIntent(): Intent? {
+    val context = SpaEnvironmentFactory.instance.appContext
+    val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
+    return Intent().setComponent(ComponentName(context, browseActivityClass))
+}
+
+fun SettingsPage.createIntent(sessionName: String? = null): Intent? {
+    if (!isBrowsable()) return null
+    return createBaseIntent()?.appendSpaParams(
+        destination = buildRoute(),
+        sessionName = sessionName
+    )
+}
+
+fun SettingsEntry.createIntent(sessionName: String? = null): Intent? {
+    val sp = containerPage()
+    if (!sp.isBrowsable()) return null
+    return createBaseIntent()?.appendSpaParams(
+        destination = sp.buildRoute(),
+        entryId = id,
+        sessionName = sessionName
+    )
+}
+
+fun Intent.appendSpaParams(
+    destination: String? = null,
+    entryId: String? = null,
+    sessionName: String? = null
+): Intent {
+    return apply {
+        if (destination != null) putExtra(KEY_DESTINATION, destination)
+        if (entryId != null) putExtra(KEY_HIGHLIGHT_ENTRY, entryId)
+        if (sessionName != null) putExtra(KEY_SESSION_SOURCE_NAME, sessionName)
+    }
+}
+
+fun Intent.getDestination(): String? {
+    return getStringExtra(KEY_DESTINATION)
+}
+
+fun Intent.getEntryId(): String? {
+    return getStringExtra(KEY_HIGHLIGHT_ENTRY)
+}
+
+fun Intent.getSessionName(): String? {
+    return getStringExtra(KEY_SESSION_SOURCE_NAME)
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
similarity index 91%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
index 3689e4e..02aed1c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.framework
+package com.android.settingslib.spa.search
 
 import android.content.ContentProvider
 import android.content.ContentValues
@@ -26,12 +26,15 @@
 import android.database.MatrixCursor
 import android.net.Uri
 import android.util.Log
+import androidx.annotation.VisibleForTesting
 import com.android.settingslib.spa.framework.common.ColumnEnum
 import com.android.settingslib.spa.framework.common.QueryEnum
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
 import com.android.settingslib.spa.framework.common.addUri
 import com.android.settingslib.spa.framework.common.getColumns
+import com.android.settingslib.spa.framework.util.SESSION_SEARCH
+import com.android.settingslib.spa.framework.util.createIntent
 
 private const val TAG = "SpaSearchProvider"
 
@@ -115,7 +118,8 @@
         }
     }
 
-    private fun querySearchImmutableStatusData(): Cursor {
+    @VisibleForTesting
+    fun querySearchImmutableStatusData(): Cursor {
         val entryRepository by spaEnvironment.entryRepository
         val cursor = MatrixCursor(QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY.getColumns())
         for (entry in entryRepository.getAllEntries()) {
@@ -125,7 +129,8 @@
         return cursor
     }
 
-    private fun querySearchMutableStatusData(): Cursor {
+    @VisibleForTesting
+    fun querySearchMutableStatusData(): Cursor {
         val entryRepository by spaEnvironment.entryRepository
         val cursor = MatrixCursor(QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY.getColumns())
         for (entry in entryRepository.getAllEntries()) {
@@ -135,7 +140,8 @@
         return cursor
     }
 
-    private fun querySearchStaticData(): Cursor {
+    @VisibleForTesting
+    fun querySearchStaticData(): Cursor {
         val entryRepository by spaEnvironment.entryRepository
         val cursor = MatrixCursor(QueryEnum.SEARCH_STATIC_DATA_QUERY.getColumns())
         for (entry in entryRepository.getAllEntries()) {
@@ -145,7 +151,8 @@
         return cursor
     }
 
-    private fun querySearchDynamicData(): Cursor {
+    @VisibleForTesting
+    fun querySearchDynamicData(): Cursor {
         val entryRepository by spaEnvironment.entryRepository
         val cursor = MatrixCursor(QueryEnum.SEARCH_DYNAMIC_DATA_QUERY.getColumns())
         for (entry in entryRepository.getAllEntries()) {
@@ -157,20 +164,19 @@
 
     private fun fetchSearchData(entry: SettingsEntry, cursor: MatrixCursor) {
         val entryRepository by spaEnvironment.entryRepository
-        val browseActivityClass = spaEnvironment.browseActivityClass
 
         // Fetch search data. We can add runtime arguments later if necessary
         val searchData = entry.getSearchData() ?: return
-        val intent = entry.containerPage()
-            .createBrowseIntent(context, browseActivityClass, entry.id)
-            ?: Intent()
+        val intent = entry.createIntent(SESSION_SEARCH) ?: Intent()
         cursor.newRow()
             .add(ColumnEnum.ENTRY_ID.id, entry.id)
             .add(ColumnEnum.ENTRY_INTENT_URI.id, intent.toUri(Intent.URI_INTENT_SCHEME))
             .add(ColumnEnum.SEARCH_TITLE.id, searchData.title)
             .add(ColumnEnum.SEARCH_KEYWORD.id, searchData.keyword)
-            .add(ColumnEnum.SEARCH_PATH.id,
-                entryRepository.getEntryPathWithTitle(entry.id, searchData.title))
+            .add(
+                ColumnEnum.SEARCH_PATH.id,
+                entryRepository.getEntryPathWithTitle(entry.id, searchData.title)
+            )
     }
 
     private fun fetchStatusData(entry: SettingsEntry, cursor: MatrixCursor) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
index 14855a8..7a4750d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
@@ -20,6 +20,7 @@
 import android.util.Log
 import com.android.settingslib.spa.framework.common.EntrySliceData
 import com.android.settingslib.spa.framework.common.SettingsEntryRepository
+import com.android.settingslib.spa.framework.util.getEntryId
 
 private const val TAG = "SliceDataRepository"
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
index ff143ed..f362890 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
@@ -24,9 +24,15 @@
 import android.content.Intent
 import android.net.Uri
 import android.os.Bundle
-import com.android.settingslib.spa.framework.BrowseActivity.Companion.KEY_DESTINATION
-import com.android.settingslib.spa.framework.BrowseActivity.Companion.KEY_HIGHLIGHT_ENTRY
+import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.util.KEY_DESTINATION
+import com.android.settingslib.spa.framework.util.KEY_HIGHLIGHT_ENTRY
+import com.android.settingslib.spa.framework.util.SESSION_SLICE
+import com.android.settingslib.spa.framework.util.SPA_INTENT_RESERVED_KEYS
+import com.android.settingslib.spa.framework.util.appendSpaParams
+import com.android.settingslib.spa.framework.util.getDestination
+import com.android.settingslib.spa.framework.util.getEntryId
 
 // Defines SliceUri, which contains special query parameters:
 //  -- KEY_DESTINATION: The route that this slice is navigated to.
@@ -35,11 +41,6 @@
 // Use {entryId, runtimeParams} as the unique Id of this Slice.
 typealias SliceUri = Uri
 
-val RESERVED_KEYS = listOf(
-    KEY_DESTINATION,
-    KEY_HIGHLIGHT_ENTRY
-)
-
 fun SliceUri.getEntryId(): String? {
     return getQueryParameter(KEY_HIGHLIGHT_ENTRY)
 }
@@ -51,7 +52,7 @@
 fun SliceUri.getRuntimeArguments(): Bundle {
     val params = Bundle()
     for (queryName in queryParameterNames) {
-        if (RESERVED_KEYS.contains(queryName)) continue
+        if (SPA_INTENT_RESERVED_KEYS.contains(queryName)) continue
         params.putString(queryName, getQueryParameter(queryName))
     }
     return params
@@ -63,12 +64,12 @@
     return "${entryId}_$params"
 }
 
-fun Uri.Builder.appendSliceParams(
-    route: String? = null,
+fun Uri.Builder.appendSpaParams(
+    destination: String? = null,
     entryId: String? = null,
     runtimeArguments: Bundle? = null
 ): Uri.Builder {
-    if (route != null) appendQueryParameter(KEY_DESTINATION, route)
+    if (destination != null) appendQueryParameter(KEY_DESTINATION, destination)
     if (entryId != null) appendQueryParameter(KEY_HIGHLIGHT_ENTRY, entryId)
     if (runtimeArguments != null) {
         for (key in runtimeArguments.keySet()) {
@@ -78,6 +79,20 @@
     return this
 }
 
+fun Uri.Builder.fromEntry(
+    entry: SettingsEntry,
+    authority: String?,
+    runtimeArguments: Bundle? = null
+): Uri.Builder {
+    if (authority == null) return this
+    val sp = entry.containerPage()
+    return scheme("content").authority(authority).appendSpaParams(
+        destination = sp.buildRoute(),
+        entryId = entry.id,
+        runtimeArguments = runtimeArguments
+    )
+}
+
 fun SliceUri.createBroadcastPendingIntent(): PendingIntent? {
     val context = SpaEnvironmentFactory.instance.appContext
     val sliceBroadcastClass =
@@ -97,8 +112,8 @@
 fun Intent.createBrowsePendingIntent(): PendingIntent? {
     val context = SpaEnvironmentFactory.instance.appContext
     val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
-    val destination = getStringExtra(KEY_DESTINATION) ?: return null
-    val entryId = getStringExtra(KEY_HIGHLIGHT_ENTRY)
+    val destination = getDestination() ?: return null
+    val entryId = getEntryId()
     return createBrowsePendingIntent(context, browseActivityClass, destination, entryId)
 }
 
@@ -109,15 +124,12 @@
     entryId: String?
 ): PendingIntent {
     val intent = Intent().setComponent(ComponentName(context, browseActivityClass))
+        .appendSpaParams(destination, entryId, SESSION_SLICE)
         .apply {
             // Set both extra and data (which is a Uri) in Slice Intent:
             // 1) extra is used in SPA navigation framework
             // 2) data is used in Slice framework
-            putExtra(KEY_DESTINATION, destination)
-            if (entryId != null) {
-                putExtra(KEY_HIGHLIGHT_ENTRY, entryId)
-            }
-            data = Uri.Builder().appendSliceParams(destination, entryId).build()
+            data = Uri.Builder().appendSpaParams(destination, entryId).build()
             flags = Intent.FLAG_ACTIVITY_NEW_TASK
         }
 
@@ -130,7 +142,7 @@
     entryId: String
 ): PendingIntent {
     val intent = Intent().setComponent(ComponentName(context, sliceBroadcastClass))
-        .apply { data = Uri.Builder().appendSliceParams(entryId = entryId).build() }
+        .apply { data = Uri.Builder().appendSpaParams(entryId = entryId).build() }
     return PendingIntent.getBroadcast(
         context, 0 /* requestCode */, intent,
         PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
index 77c564b..b6099e9 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
@@ -24,7 +24,6 @@
 import androidx.compose.ui.graphics.vector.ImageVector
 import com.android.settingslib.spa.framework.common.EntryMacro
 import com.android.settingslib.spa.framework.common.EntrySearchData
-import com.android.settingslib.spa.framework.common.EntryStatusData
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spa.framework.util.EntryHighlight
@@ -56,10 +55,6 @@
             keyword = searchKeywords
         )
     }
-
-    override fun getStatusData(): EntryStatusData {
-        return EntryStatusData(isDisabled = false)
-    }
 }
 
 /**
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
index bde3bba..bd5884d 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
@@ -30,6 +30,7 @@
 import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
 import com.android.settingslib.spa.tests.testutils.SpaLoggerForTest
+import com.android.settingslib.spa.tests.testutils.SppHome
 import com.android.settingslib.spa.testutils.waitUntil
 import com.google.common.truth.Truth
 import org.junit.Rule
@@ -45,7 +46,8 @@
 
     private val context: Context = ApplicationProvider.getApplicationContext()
     private val spaLogger = SpaLoggerForTest()
-    private val spaEnvironment = SpaEnvironmentForTest(context, logger = spaLogger)
+    private val spaEnvironment =
+        SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage()), logger = spaLogger)
 
     @Test
     fun testBrowsePage() {
@@ -58,13 +60,7 @@
         val sppLayer1 = sppRepository.getProviderOrNull("SppLayer1")!!
         val pageLayer1 = sppLayer1.createSettingsPage()
 
-        composeTestRule.setContent {
-            BrowseContent(
-                allProviders = listOf(sppHome, sppLayer1),
-                initialDestination = pageHome.buildRoute(),
-                initialEntryId = null
-            )
-        }
+        composeTestRule.setContent { BrowseContent(sppRepository) }
 
         composeTestRule.onNodeWithText(sppHome.getTitle(null)).assertIsDisplayed()
         spaLogger.verifyPageEvent(pageHome.id, 1, 0)
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
index f8339b6..c0b7464 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
@@ -23,6 +23,8 @@
 import com.android.settingslib.spa.tests.testutils.SppHome
 import com.android.settingslib.spa.tests.testutils.SppLayer1
 import com.android.settingslib.spa.tests.testutils.SppLayer2
+import com.android.settingslib.spa.tests.testutils.getUniqueEntryId
+import com.android.settingslib.spa.tests.testutils.getUniquePageId
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -30,7 +32,8 @@
 @RunWith(AndroidJUnit4::class)
 class SettingsEntryRepositoryTest {
     private val context: Context = ApplicationProvider.getApplicationContext()
-    private val spaEnvironment = SpaEnvironmentForTest(context)
+    private val spaEnvironment =
+        SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage()))
     private val entryRepository by spaEnvironment.entryRepository
 
     @Test
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
index 2017d53..f98963c 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
@@ -20,6 +20,8 @@
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.core.os.bundleOf
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.tests.testutils.getUniqueEntryId
+import com.android.settingslib.spa.tests.testutils.getUniquePageId
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
 import org.junit.Test
@@ -64,6 +66,7 @@
         assertThat(entry.isAllowSearch).isFalse()
         assertThat(entry.isSearchDataDynamic).isFalse()
         assertThat(entry.hasMutableStatus).isFalse()
+        assertThat(entry.hasSliceSupport).isFalse()
     }
 
     @Test
@@ -121,12 +124,13 @@
     @Test
     fun testSetAttributes() {
         val owner = SettingsPage.create("mySpp")
-        val entry = SettingsEntryBuilder.create(owner, "myEntry")
+        val entryBuilder = SettingsEntryBuilder.create(owner, "myEntry")
             .setDisplayName("myEntryDisplay")
-            .setIsAllowSearch(true)
             .setIsSearchDataDynamic(false)
             .setHasMutableStatus(true)
-            .build()
+            .setSearchDataFn { null }
+            .setSliceDataFn { _, _ -> null }
+        val entry = entryBuilder.build()
         assertThat(entry.id).isEqualTo(getUniqueEntryId("myEntry", owner))
         assertThat(entry.displayName).isEqualTo("myEntryDisplay")
         assertThat(entry.fromPage).isNull()
@@ -134,6 +138,10 @@
         assertThat(entry.isAllowSearch).isTrue()
         assertThat(entry.isSearchDataDynamic).isFalse()
         assertThat(entry.hasMutableStatus).isTrue()
+        assertThat(entry.hasSliceSupport).isTrue()
+
+        val entry2 = entryBuilder.clearSearchDataFn().build()
+        assertThat(entry2.isAllowSearch).isFalse()
     }
 
     @Test
@@ -150,6 +158,10 @@
 
         val rtArguments = bundleOf("rtParam" to "v2")
         composeTestRule.setContent { entry.UiLayout(rtArguments) }
+        assertThat(entry.isAllowSearch).isTrue()
+        assertThat(entry.isSearchDataDynamic).isFalse()
+        assertThat(entry.hasMutableStatus).isFalse()
+        assertThat(entry.hasSliceSupport).isFalse()
         val searchData = entry.getSearchData(rtArguments)
         val statusData = entry.getStatusData(rtArguments)
         assertThat(searchData?.title).isEqualTo("myTitle")
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
index 743b5e3..1f5de2d 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
@@ -22,9 +22,8 @@
 import androidx.navigation.navArgument
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.tests.testutils.BlankActivity
 import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
-import com.android.settingslib.spa.tests.testutils.SpaLoggerForTest
+import com.android.settingslib.spa.tests.testutils.getUniquePageId
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -32,8 +31,7 @@
 @RunWith(AndroidJUnit4::class)
 class SettingsPageTest {
     private val context: Context = ApplicationProvider.getApplicationContext()
-    private val spaLogger = SpaLoggerForTest()
-    private val spaEnvironment = SpaEnvironmentForTest(context, logger = spaLogger)
+    private val spaEnvironment = SpaEnvironmentForTest(context)
 
     @Test
     fun testNullPage() {
@@ -45,9 +43,7 @@
         assertThat(page.isCreateBy("NULL")).isTrue()
         assertThat(page.isCreateBy("Spp")).isFalse()
         assertThat(page.hasRuntimeParam()).isFalse()
-        assertThat(page.isBrowsable(context, BlankActivity::class.java)).isFalse()
-        assertThat(page.createBrowseIntent(context, BlankActivity::class.java)).isNull()
-        assertThat(page.createBrowseAdbCommand(context, BlankActivity::class.java)).isNull()
+        assertThat(page.isBrowsable()).isFalse()
     }
 
     @Test
@@ -60,11 +56,7 @@
         assertThat(page.isCreateBy("NULL")).isFalse()
         assertThat(page.isCreateBy("mySpp")).isTrue()
         assertThat(page.hasRuntimeParam()).isFalse()
-        assertThat(page.isBrowsable(context, BlankActivity::class.java)).isTrue()
-        assertThat(page.createBrowseIntent(context, BlankActivity::class.java)).isNotNull()
-        assertThat(page.createBrowseAdbCommand(context, BlankActivity::class.java)).contains(
-            "-e spaActivityDestination mySpp"
-        )
+        assertThat(page.isBrowsable()).isTrue()
     }
 
     @Test
@@ -74,20 +66,20 @@
             "int_param" to 10,
         )
         val page = spaEnvironment.createPage("SppWithParam", arguments)
-        assertThat(page.id).isEqualTo(getUniquePageId("SppWithParam", listOf(
-            navArgument("string_param") { type = NavType.StringType },
-            navArgument("int_param") { type = NavType.IntType },
-        ), arguments))
+        assertThat(page.id).isEqualTo(
+            getUniquePageId(
+                "SppWithParam", listOf(
+                    navArgument("string_param") { type = NavType.StringType },
+                    navArgument("int_param") { type = NavType.IntType },
+                ), arguments
+            )
+        )
         assertThat(page.sppName).isEqualTo("SppWithParam")
         assertThat(page.displayName).isEqualTo("SppWithParam")
         assertThat(page.buildRoute()).isEqualTo("SppWithParam/myStr/10")
         assertThat(page.isCreateBy("SppWithParam")).isTrue()
         assertThat(page.hasRuntimeParam()).isFalse()
-        assertThat(page.isBrowsable(context, BlankActivity::class.java)).isTrue()
-        assertThat(page.createBrowseIntent(context, BlankActivity::class.java)).isNotNull()
-        assertThat(page.createBrowseAdbCommand(context, BlankActivity::class.java)).contains(
-            "-e spaActivityDestination SppWithParam/myStr/10"
-        )
+        assertThat(page.isBrowsable()).isTrue()
     }
 
     @Test
@@ -98,33 +90,20 @@
             "rt_param" to "rtStr",
         )
         val page = spaEnvironment.createPage("SppWithRtParam", arguments)
-        assertThat(page.id).isEqualTo(getUniquePageId("SppWithRtParam", listOf(
-            navArgument("string_param") { type = NavType.StringType },
-            navArgument("int_param") { type = NavType.IntType },
-            navArgument("rt_param") { type = NavType.StringType },
-        ), arguments))
+        assertThat(page.id).isEqualTo(
+            getUniquePageId(
+                "SppWithRtParam", listOf(
+                    navArgument("string_param") { type = NavType.StringType },
+                    navArgument("int_param") { type = NavType.IntType },
+                    navArgument("rt_param") { type = NavType.StringType },
+                ), arguments
+            )
+        )
         assertThat(page.sppName).isEqualTo("SppWithRtParam")
         assertThat(page.displayName).isEqualTo("SppWithRtParam")
         assertThat(page.buildRoute()).isEqualTo("SppWithRtParam/myStr/10/rtStr")
         assertThat(page.isCreateBy("SppWithRtParam")).isTrue()
         assertThat(page.hasRuntimeParam()).isTrue()
-        assertThat(page.isBrowsable(context, BlankActivity::class.java)).isFalse()
-        assertThat(page.createBrowseIntent(context, BlankActivity::class.java)).isNull()
-        assertThat(page.createBrowseAdbCommand(context, BlankActivity::class.java)).isNull()
-    }
-
-    @Test
-    fun testPageEvent() {
-        spaLogger.reset()
-        SpaEnvironmentFactory.reset(spaEnvironment)
-        val page = spaEnvironment.createPage("SppHome")
-        page.enterPage()
-        page.leavePage()
-        page.enterPage()
-        assertThat(page.createBrowseIntent()).isNotNull()
-        assertThat(spaLogger.getEventCount(page.id, LogEvent.PAGE_ENTER, LogCategory.FRAMEWORK))
-            .isEqualTo(2)
-        assertThat(spaLogger.getEventCount(page.id, LogEvent.PAGE_LEAVE, LogCategory.FRAMEWORK))
-            .isEqualTo(1)
+        assertThat(page.isBrowsable()).isFalse()
     }
 }
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SpaIntentTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SpaIntentTest.kt
new file mode 100644
index 0000000..1854728
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SpaIntentTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.framework.util
+
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SpaIntentTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+    private val spaEnvironment = SpaEnvironmentForTest(context)
+
+    @Before
+    fun setEnvironment() {
+        SpaEnvironmentFactory.reset(spaEnvironment)
+    }
+
+    @Test
+    fun testCreateIntent() {
+        val nullPage = SettingsPage.createNull()
+        Truth.assertThat(nullPage.createIntent()).isNull()
+        Truth.assertThat(SettingsEntryBuilder.createInject(nullPage).build().createIntent())
+            .isNull()
+
+        val page = spaEnvironment.createPage("SppHome")
+        val pageIntent = page.createIntent()
+        Truth.assertThat(pageIntent).isNotNull()
+        Truth.assertThat(pageIntent!!.getDestination()).isEqualTo(page.buildRoute())
+        Truth.assertThat(pageIntent.getEntryId()).isNull()
+        Truth.assertThat(pageIntent.getSessionName()).isNull()
+
+        val entry = SettingsEntryBuilder.createInject(page).build()
+        val entryIntent = entry.createIntent(SESSION_SEARCH)
+        Truth.assertThat(entryIntent).isNotNull()
+        Truth.assertThat(entryIntent!!.getDestination()).isEqualTo(page.buildRoute())
+        Truth.assertThat(entryIntent.getEntryId()).isEqualTo(entry.id)
+        Truth.assertThat(entryIntent.getSessionName()).isEqualTo(SESSION_SEARCH)
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt
new file mode 100644
index 0000000..cdb0f3a
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.search
+
+import android.content.Context
+import android.database.Cursor
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.common.ColumnEnum
+import com.android.settingslib.spa.framework.common.QueryEnum
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.common.getIndex
+import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
+import com.android.settingslib.spa.tests.testutils.SppForSearch
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SpaSearchProviderTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+    private val spaEnvironment =
+        SpaEnvironmentForTest(context, listOf(SppForSearch.createSettingsPage()))
+    private val searchProvider = SpaSearchProvider()
+
+    @Test
+    fun testQuerySearchStatusData() {
+        SpaEnvironmentFactory.reset(spaEnvironment)
+        val pageOwner = spaEnvironment.createPage("SppForSearch")
+
+        val immutableStatus = searchProvider.querySearchImmutableStatusData()
+        Truth.assertThat(immutableStatus.count).isEqualTo(1)
+        immutableStatus.moveToFirst()
+        immutableStatus.checkValue(
+            QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
+            ColumnEnum.ENTRY_ID,
+            pageOwner.getEntryId("SearchDynamicWithImmutableStatus")
+        )
+
+        val mutableStatus = searchProvider.querySearchMutableStatusData()
+        Truth.assertThat(mutableStatus.count).isEqualTo(2)
+        mutableStatus.moveToFirst()
+        mutableStatus.checkValue(
+            QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
+            ColumnEnum.ENTRY_ID,
+            pageOwner.getEntryId("SearchStaticWithMutableStatus")
+        )
+
+        mutableStatus.moveToNext()
+        mutableStatus.checkValue(
+            QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
+            ColumnEnum.ENTRY_ID,
+            pageOwner.getEntryId("SearchDynamicWithMutableStatus")
+        )
+    }
+
+    @Test
+    fun testQuerySearchIndexData() {
+        SpaEnvironmentFactory.reset(spaEnvironment)
+        val staticData = searchProvider.querySearchStaticData()
+        Truth.assertThat(staticData.count).isEqualTo(2)
+
+        val dynamicData = searchProvider.querySearchDynamicData()
+        Truth.assertThat(dynamicData.count).isEqualTo(2)
+    }
+}
+
+private fun Cursor.checkValue(query: QueryEnum, column: ColumnEnum, value: String) {
+    Truth.assertThat(getString(query.getIndex(column))).isEqualTo(value)
+}
+
+private fun SettingsPage.getEntryId(name: String): String {
+    return SettingsEntryBuilder.create(this, name).build().id
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
index 6ebd64f..1bdba29 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
@@ -18,15 +18,16 @@
 
 import android.content.Context
 import android.net.Uri
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
 import androidx.lifecycle.Observer
 import androidx.slice.Slice
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settingslib.spa.framework.common.createSettingsPage
-import com.android.settingslib.spa.framework.common.getUniqueEntryId
-import com.android.settingslib.spa.testutils.InstantTaskExecutorRule
 import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
+import com.android.settingslib.spa.tests.testutils.SppHome
 import com.android.settingslib.spa.tests.testutils.SppLayer2
+import com.android.settingslib.spa.tests.testutils.getUniqueEntryId
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
 import org.junit.Test
@@ -37,7 +38,8 @@
     @get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
 
     private val context: Context = ApplicationProvider.getApplicationContext()
-    private val spaEnvironment = SpaEnvironmentForTest(context)
+    private val spaEnvironment =
+        SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage()))
     private val sliceDataRepository by spaEnvironment.sliceDataRepository
 
     @Test
@@ -48,7 +50,7 @@
         // Slice supported
         val page = SppLayer2.createSettingsPage()
         val entryId = getUniqueEntryId("Layer2Entry1", page)
-        val sliceUri = Uri.Builder().appendSliceParams(page.buildRoute(), entryId).build()
+        val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build()
         assertThat(sliceUri.getDestination()).isEqualTo("SppLayer2")
         assertThat(sliceUri.getSliceId()).isEqualTo("${entryId}_Bundle[{}]")
         val sliceData = sliceDataRepository.getOrBuildSliceData(sliceUri)
@@ -57,7 +59,7 @@
 
         // Slice unsupported
         val entryId2 = getUniqueEntryId("Layer2Entry2", page)
-        val sliceUri2 = Uri.Builder().appendSliceParams(page.buildRoute(), entryId2).build()
+        val sliceUri2 = Uri.Builder().appendSpaParams(page.buildRoute(), entryId2).build()
         assertThat(sliceUri2.getDestination()).isEqualTo("SppLayer2")
         assertThat(sliceUri2.getSliceId()).isEqualTo("${entryId2}_Bundle[{}]")
         assertThat(sliceDataRepository.getOrBuildSliceData(sliceUri2)).isNull()
@@ -67,7 +69,7 @@
     fun getActiveSliceDataTest() {
         val page = SppLayer2.createSettingsPage()
         val entryId = getUniqueEntryId("Layer2Entry1", page)
-        val sliceUri = Uri.Builder().appendSliceParams(page.buildRoute(), entryId).build()
+        val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build()
 
         // build slice data first
         val sliceData = sliceDataRepository.getOrBuildSliceData(sliceUri)
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt
index 16a87f6..d1c4e51 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt
@@ -43,14 +43,14 @@
         // valid slice uri
         val dest = "myRoute"
         val entryId = "myEntry"
-        val sliceUriWithoutParams = Uri.Builder().appendSliceParams(dest, entryId).build()
+        val sliceUriWithoutParams = Uri.Builder().appendSpaParams(dest, entryId).build()
         assertThat(sliceUriWithoutParams.getEntryId()).isEqualTo(entryId)
         assertThat(sliceUriWithoutParams.getDestination()).isEqualTo(dest)
         assertThat(sliceUriWithoutParams.getRuntimeArguments().size()).isEqualTo(0)
         assertThat(sliceUriWithoutParams.getSliceId()).isEqualTo("${entryId}_Bundle[{}]")
 
         val sliceUriWithParams =
-            Uri.Builder().appendSliceParams(dest, entryId, bundleOf("p1" to "v1")).build()
+            Uri.Builder().appendSpaParams(dest, entryId, bundleOf("p1" to "v1")).build()
         assertThat(sliceUriWithParams.getEntryId()).isEqualTo(entryId)
         assertThat(sliceUriWithParams.getDestination()).isEqualTo(dest)
         assertThat(sliceUriWithParams.getRuntimeArguments().size()).isEqualTo(1)
@@ -67,7 +67,7 @@
         // Valid Slice Uri
         val dest = "myRoute"
         val entryId = "myEntry"
-        val sliceUriWithoutParams = Uri.Builder().appendSliceParams(dest, entryId).build()
+        val sliceUriWithoutParams = Uri.Builder().appendSpaParams(dest, entryId).build()
         val pendingIntent = sliceUriWithoutParams.createBroadcastPendingIntent()
         assertThat(pendingIntent).isNotNull()
         assertThat(pendingIntent!!.isBroadcast).isTrue()
@@ -87,7 +87,7 @@
         // Valid Slice Uri
         val dest = "myRoute"
         val entryId = "myEntry"
-        val sliceUri = Uri.Builder().appendSliceParams(dest, entryId).build()
+        val sliceUri = Uri.Builder().appendSpaParams(dest, entryId).build()
         val pendingIntent = sliceUri.createBrowsePendingIntent()
         assertThat(pendingIntent).isNotNull()
         assertThat(pendingIntent!!.isActivity).isTrue()
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
index ab269f2..f38bd08 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
@@ -24,7 +24,9 @@
 import androidx.navigation.NavType
 import androidx.navigation.navArgument
 import com.android.settingslib.spa.framework.BrowseActivity
+import com.android.settingslib.spa.framework.common.EntrySearchData
 import com.android.settingslib.spa.framework.common.EntrySliceData
+import com.android.settingslib.spa.framework.common.EntryStatusData
 import com.android.settingslib.spa.framework.common.LogCategory
 import com.android.settingslib.spa.framework.common.LogEvent
 import com.android.settingslib.spa.framework.common.SettingsEntry
@@ -49,7 +51,7 @@
         messageCount[key] = (messageCount[key] ?: 0) + 1
     }
 
-    override fun event(id: String, event: LogEvent, category: LogCategory, details: String?) {
+    override fun event(id: String, event: LogEvent, category: LogCategory, extraData: Bundle) {
         val key = EventCountKey(id, event, category)
         eventCount[key] = (eventCount[key] ?: 0) + 1
     }
@@ -141,8 +143,43 @@
     }
 }
 
+object SppForSearch : SettingsPageProvider {
+    override val name = "SppForSearch"
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val owner = this.createSettingsPage()
+        return listOf(
+            SettingsEntryBuilder.create(owner, "SearchStaticWithNoStatus")
+                .setSearchDataFn { EntrySearchData(title = "SearchStaticWithNoStatus") }
+                .build(),
+            SettingsEntryBuilder.create(owner, "SearchStaticWithMutableStatus")
+                .setHasMutableStatus(true)
+                .setSearchDataFn { EntrySearchData(title = "SearchStaticWithMutableStatus") }
+                .setStatusDataFn { EntryStatusData(isSwitchOff = true) }
+                .build(),
+            SettingsEntryBuilder.create(owner, "SearchDynamicWithMutableStatus")
+                .setIsSearchDataDynamic(true)
+                .setHasMutableStatus(true)
+                .setSearchDataFn { EntrySearchData(title = "SearchDynamicWithMutableStatus") }
+                .setStatusDataFn { EntryStatusData(isDisabled = true) }
+                .build(),
+            SettingsEntryBuilder.create(owner, "SearchDynamicWithImmutableStatus")
+                .setIsSearchDataDynamic(true)
+                .setSearchDataFn {
+                    EntrySearchData(
+                        title = "SearchDynamicWithImmutableStatus",
+                        keyword = listOf("kw1", "kw2")
+                    )
+                }
+                .setStatusDataFn { EntryStatusData(isDisabled = true) }
+                .build(),
+        )
+    }
+}
+
 class SpaEnvironmentForTest(
     context: Context,
+    rootPages: List<SettingsPage> = emptyList(),
     override val browseActivityClass: Class<out Activity>? = BlankActivity::class.java,
     override val sliceBroadcastReceiverClass: Class<out BroadcastReceiver>? =
         BlankSliceBroadcastReceiver::class.java,
@@ -153,6 +190,7 @@
         SettingsPageProviderRepository(
             listOf(
                 SppHome, SppLayer1, SppLayer2,
+                SppForSearch,
                 object : SettingsPageProvider {
                     override val name = "SppWithParam"
                     override val parameter = listOf(
@@ -169,7 +207,7 @@
                     )
                 },
             ),
-            listOf(SettingsPage.create("SppHome"))
+            rootPages
         )
     }
 
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/UniqueIdHelper.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt
similarity index 89%
rename from packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/UniqueIdHelper.kt
rename to packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt
index 93f9afe..7e51fea 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/UniqueIdHelper.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt
@@ -14,10 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.framework.common
+package com.android.settingslib.spa.tests.testutils
 
 import android.os.Bundle
 import androidx.navigation.NamedNavArgument
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.toHashId
 import com.android.settingslib.spa.framework.util.normalize
 
 fun getUniquePageId(
diff --git a/packages/SettingsLib/Spa/testutils/Android.bp b/packages/SettingsLib/Spa/testutils/Android.bp
index 48df569..de87dde 100644
--- a/packages/SettingsLib/Spa/testutils/Android.bp
+++ b/packages/SettingsLib/Spa/testutils/Android.bp
@@ -24,7 +24,7 @@
     srcs: ["src/**/*.kt"],
 
     static_libs: [
-        "androidx.arch.core_core-runtime",
+        "androidx.arch.core_core-testing",
         "androidx.compose.ui_ui-test-junit4",
         "androidx.compose.ui_ui-test-manifest",
         "mockito",
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle b/packages/SettingsLib/Spa/testutils/build.gradle
index be8df43..81e54c1 100644
--- a/packages/SettingsLib/Spa/testutils/build.gradle
+++ b/packages/SettingsLib/Spa/testutils/build.gradle
@@ -47,7 +47,7 @@
 }
 
 dependencies {
-    api "androidx.arch.core:core-runtime:2.1.0"
+    api "androidx.arch.core:core-testing:2.1.0"
     api "androidx.compose.ui:ui-test-junit4:$jetpack_compose_version"
     api "com.google.truth:truth:1.1.3"
     api "org.mockito:mockito-core:2.21.0"
diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/InstantTaskExecutorRule.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/InstantTaskExecutorRule.kt
deleted file mode 100644
index 43c18d4..0000000
--- a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/InstantTaskExecutorRule.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- *  Copyright (C) 2022 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.settingslib.spa.testutils
-
-import androidx.arch.core.executor.ArchTaskExecutor
-import androidx.arch.core.executor.TaskExecutor
-import org.junit.rules.TestWatcher
-import org.junit.runner.Description
-
-/**
- * Test rule that makes ArchTaskExecutor main thread assertions pass. There is one such assert
- * in LifecycleRegistry.
-
- * This is a copy of androidx/arch/core/executor/testing/InstantTaskExecutorRule which should be
- * replaced once the dependency issue is solved.
- */
-class InstantTaskExecutorRule : TestWatcher() {
-    override fun starting(description: Description) {
-        super.starting(description)
-        ArchTaskExecutor.getInstance().setDelegate(
-            object : TaskExecutor() {
-                override fun executeOnDiskIO(runnable: Runnable) {
-                    runnable.run()
-                }
-
-                override fun postToMainThread(runnable: Runnable) {
-                    runnable.run()
-                }
-
-                override fun isMainThread(): Boolean {
-                    return true
-                }
-            }
-        )
-    }
-
-    override fun finished(description: Description) {
-        super.finished(description)
-        ArchTaskExecutor.getInstance().setDelegate(null)
-    }
-}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
index a7122d0..6999908 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
@@ -33,7 +33,8 @@
      *
      * @return the [AppRecord] list which will be displayed.
      */
-    fun filter(userIdFlow: Flow<Int>, option: Int, recordListFlow: Flow<List<T>>): Flow<List<T>>
+    fun filter(userIdFlow: Flow<Int>, option: Int, recordListFlow: Flow<List<T>>): Flow<List<T>> =
+        recordListFlow
 
     /**
      * This function is called when the App List's loading is finished and displayed to the user.
@@ -67,5 +68,5 @@
      * @return null if no summary should be displayed.
      */
     @Composable
-    fun getSummary(option: Int, record: T): State<String>?
+    fun getSummary(option: Int, record: T): State<String>? = null
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index 681eb1c..15766e1 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -55,19 +55,22 @@
     val searchQuery: State<String>,
 )
 
+internal data class AppListInput<T : AppRecord>(
+    val config: AppListConfig,
+    val listModel: AppListModel<T>,
+    val state: AppListState,
+    val header: @Composable () -> Unit,
+    val appItem: @Composable AppListItemModel<T>.() -> Unit,
+    val bottomPadding: Dp,
+)
+
 /**
  * The template to render an App List.
  *
  * This UI element will take the remaining space on the screen to show the App List.
  */
 @Composable
-internal fun <T : AppRecord> AppList(
-    config: AppListConfig,
-    listModel: AppListModel<T>,
-    state: AppListState,
-    header: @Composable () -> Unit,
-    appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
-    bottomPadding: Dp,
+internal fun <T : AppRecord> AppListInput<T>.AppList(
     appListDataSupplier: @Composable () -> State<AppListData<T>?> = {
         loadAppListData(config, listModel, state)
     },
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt
index ac3f8ff..28bf832 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt
@@ -35,16 +35,13 @@
 )
 
 @Composable
-fun <T : AppRecord> AppListItem(
-    itemModel: AppListItemModel<T>,
-    onClick: () -> Unit,
-) {
+fun <T : AppRecord> AppListItemModel<T>.AppListItem(onClick: () -> Unit) {
     Preference(remember {
         object : PreferenceModel {
-            override val title = itemModel.label
-            override val summary = itemModel.summary
+            override val title = label
+            override val summary = this@AppListItem.summary
             override val icon = @Composable {
-                AppIcon(app = itemModel.record.app, size = SettingsDimension.appIconItemSize)
+                AppIcon(app = record.app, size = SettingsDimension.appIconItemSize)
             }
             override val onClick = onClick
         }
@@ -58,7 +55,6 @@
         val record = object : AppRecord {
             override val app = LocalContext.current.applicationInfo
         }
-        val itemModel = AppListItemModel<AppRecord>(record, "Chrome", "Allowed".toState())
-        AppListItem(itemModel) {}
+        AppListItemModel<AppRecord>(record, "Chrome", "Allowed".toState()).AppListItem {}
     }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index f371ce9..d452c74 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -47,7 +47,23 @@
     primaryUserOnly: Boolean = false,
     moreOptions: @Composable MoreOptionsScope.() -> Unit = {},
     header: @Composable () -> Unit = {},
-    appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
+    appItem: @Composable AppListItemModel<T>.() -> Unit,
+) {
+    AppListPageImpl(
+        title, listModel, showInstantApps, primaryUserOnly, moreOptions, header, appItem,
+    ) { it.AppList() }
+}
+
+@Composable
+internal fun <T : AppRecord> AppListPageImpl(
+    title: String,
+    listModel: AppListModel<T>,
+    showInstantApps: Boolean = false,
+    primaryUserOnly: Boolean = false,
+    moreOptions: @Composable MoreOptionsScope.() -> Unit = {},
+    header: @Composable () -> Unit = {},
+    appItem: @Composable AppListItemModel<T>.() -> Unit,
+    appList: @Composable (input: AppListInput<T>) -> Unit,
 ) {
     val showSystem = rememberSaveable { mutableStateOf(false) }
     SearchScaffold(
@@ -64,7 +80,7 @@
                 val options = remember { listModel.getSpinnerOptions() }
                 val selectedOption = rememberSaveable { mutableStateOf(0) }
                 Spinner(options, selectedOption.value) { selectedOption.value = it }
-                AppList(
+                val appListInput = AppListInput(
                     config = AppListConfig(
                         userId = userInfo.id,
                         showInstantApps = showInstantApps,
@@ -79,6 +95,7 @@
                     appItem = appItem,
                     bottomPadding = bottomPadding,
                 )
+                appList(appListInput)
             }
         }
     }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt
index 5290bec..452971b 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt
@@ -9,8 +9,7 @@
 import com.android.settingslib.spaprivileged.model.app.AppRecord
 
 @Composable
-fun <T : AppRecord> AppListSwitchItem(
-    itemModel: AppListItemModel<T>,
+fun <T : AppRecord> AppListItemModel<T>.AppListSwitchItem(
     onClick: () -> Unit,
     checked: State<Boolean?>,
     changeable: State<Boolean>,
@@ -19,14 +18,14 @@
     TwoTargetSwitchPreference(
         model = remember {
             object : SwitchPreferenceModel {
-                override val title = itemModel.label
-                override val summary = itemModel.summary
+                override val title = label
+                override val summary = this@AppListSwitchItem.summary
                 override val checked = checked
                 override val changeable = changeable
                 override val onCheckedChange = onCheckedChange
             }
         },
-        icon = { AppIcon(itemModel.record.app, SettingsDimension.appIconItemSize) },
+        icon = { AppIcon(record.app, SettingsDimension.appIconItemSize) },
         onClick = onClick,
     )
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index de5a4a2..8287693 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -66,7 +66,7 @@
         val owner = SettingsPage.create(name, parameter = parameter, arguments = arguments)
         val entryList = mutableListOf<SettingsEntry>()
         entryList.add(
-            SettingsEntryBuilder.create(ENTRY_NAME, owner).setIsAllowSearch(false).build()
+            SettingsEntryBuilder.create(ENTRY_NAME, owner).build()
         )
         return entryList
     }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index 6db2733..00eb607 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -70,7 +70,6 @@
             entryList.add(
                 SettingsEntryBuilder.createLinkFrom("${ENTRY_NAME}_$category", appListPage)
                     .setLink(toPage = appInfoPage)
-                    .setIsAllowSearch(false)
                     .build()
             )
         }
@@ -92,12 +91,11 @@
         AppListPage(
             title = stringResource(listModel.pageTitleResId),
             listModel = internalListModel,
-        ) { itemModel ->
+        ) {
             AppListItem(
-                itemModel = itemModel,
                 onClick = TogglePermissionAppInfoPageProvider.navigator(
                     permissionType = permissionType,
-                    app = itemModel.record.app,
+                    app = record.app,
                 ),
             )
         }
@@ -120,7 +118,7 @@
                 parameter = PAGE_PARAMETER,
                 arguments = bundleOf(PERMISSION to permissionType)
             )
-            return SettingsEntryBuilder.createInject(owner = appListPage).setIsAllowSearch(false)
+            return SettingsEntryBuilder.createInject(owner = appListPage)
                 .setUiLayoutFn {
                     val listModel = rememberContext(listModelSupplier)
                     Preference(
diff --git a/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml b/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml
new file mode 100644
index 0000000..fb1e09a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<resources>
+    <!-- Test Permission title. [DO NOT TRANSLATE] -->
+    <string name="test_permission_title" translatable="false">Test Permission</string>
+
+    <!-- Test Permission switch title. [DO NOT TRANSLATE] -->
+    <string name="test_permission_switch_title" translatable="false">Allow Test Permission</string>
+
+    <!-- Test Permission footer. [DO NOT TRANSLATE] -->
+    <string name="test_permission_footer" translatable="false">Test Permission is for demo.</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
index bc6925b..c4f2df2 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -40,9 +40,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class AppListRepositoryTest {
-
-    @JvmField
-    @Rule
+    @get:Rule
     val mockito: MockitoRule = MockitoJUnit.rule()
 
     @Mock
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
index b570815..b9c875b 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
@@ -21,7 +21,7 @@
 import androidx.compose.runtime.Composable
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settingslib.spa.framework.compose.stateOf
-import com.android.settingslib.spa.framework.util.asyncMapItem
+import com.android.settingslib.spa.framework.util.mapItem
 import com.android.settingslib.spa.testutils.waitUntil
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -39,8 +39,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class AppListViewModelTest {
-    @JvmField
-    @Rule
+    @get:Rule
     val mockito: MockitoRule = MockitoJUnit.rule()
 
     @Mock
@@ -117,16 +116,7 @@
     var onFirstLoadedCalled = false
 
     override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
-        appListFlow.asyncMapItem { TestAppRecord(it) }
-
-    @Composable
-    override fun getSummary(option: Int, record: TestAppRecord) = null
-
-    override fun filter(
-        userIdFlow: Flow<Int>,
-        option: Int,
-        recordListFlow: Flow<List<TestAppRecord>>,
-    ) = recordListFlow
+        appListFlow.mapItem(::TestAppRecord)
 
     override suspend fun onFirstLoaded(recordList: List<TestAppRecord>) {
         onFirstLoadedCalled = true
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt
index 4207490..4002655 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt
@@ -40,8 +40,7 @@
 
 @RunWith(AndroidJUnit4::class)
 class PackageManagerExtTest {
-    @JvmField
-    @Rule
+    @get:Rule
     val mockito: MockitoRule = MockitoJUnit.rule()
 
     @Mock
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
new file mode 100644
index 0000000..c3c96c6
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spaprivileged.template.app
+
+import android.content.Context
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.tests.testutils.TestAppListModel
+import com.android.settingslib.spaprivileged.tests.testutils.TestAppRecord
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AppListPageTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private var context: Context = ApplicationProvider.getApplicationContext()
+
+    @Test
+    fun title_isDisplayed() {
+        setContent()
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+    }
+
+    @Test
+    fun appListState_hasCorrectInitialState() {
+        val inputState by setContent()
+
+        val state = inputState!!.state
+        assertThat(state.showSystem.value).isFalse()
+        assertThat(state.option.value).isEqualTo(0)
+        assertThat(state.searchQuery.value).isEqualTo("")
+    }
+
+    @Test
+    fun canShowSystem() {
+        val inputState by setContent()
+
+        composeTestRule.onNodeWithContentDescription(
+            context.getString(R.string.abc_action_menu_overflow_description)
+        ).performClick()
+        composeTestRule.onNodeWithText(context.getString(R.string.menu_show_system)).performClick()
+
+        val state = inputState!!.state
+        assertThat(state.showSystem.value).isTrue()
+    }
+
+    @Test
+    fun afterShowSystem_displayHideSystem() {
+        setContent()
+        composeTestRule.onNodeWithContentDescription(
+            context.getString(R.string.abc_action_menu_overflow_description)
+        ).performClick()
+        composeTestRule.onNodeWithText(context.getString(R.string.menu_show_system)).performClick()
+
+        composeTestRule.onNodeWithContentDescription(
+            context.getString(R.string.abc_action_menu_overflow_description)
+        ).performClick()
+
+        composeTestRule.onNodeWithText(context.getString(R.string.menu_hide_system))
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun whenHasOptions_firstOptionDisplayed() {
+        val inputState by setContent(options = listOf(OPTION_0, OPTION_1))
+
+        composeTestRule.onNodeWithText(OPTION_0).assertIsDisplayed()
+        composeTestRule.onNodeWithText(OPTION_1).assertDoesNotExist()
+        val state = inputState!!.state
+        assertThat(state.option.value).isEqualTo(0)
+    }
+
+    @Test
+    fun whenHasOptions_couldSwitchOption() {
+        val inputState by setContent(options = listOf(OPTION_0, OPTION_1))
+
+        composeTestRule.onNodeWithText(OPTION_0).performClick()
+        composeTestRule.onNodeWithText(OPTION_1).performClick()
+
+        composeTestRule.onNodeWithText(OPTION_1).assertIsDisplayed()
+        composeTestRule.onNodeWithText(OPTION_0).assertDoesNotExist()
+        val state = inputState!!.state
+        assertThat(state.option.value).isEqualTo(1)
+    }
+
+    private fun setContent(
+        options: List<String> = emptyList(),
+        header: @Composable () -> Unit = {},
+    ): State<AppListInput<TestAppRecord>?> {
+        val appListState = mutableStateOf<AppListInput<TestAppRecord>?>(null)
+        composeTestRule.setContent {
+            AppListPageImpl(
+                title = TITLE,
+                listModel = TestAppListModel(options),
+                header = header,
+                appItem = { AppListItem {} },
+                appList = { appListState.value = it },
+            )
+        }
+        return appListState
+    }
+
+    private companion object {
+        const val TITLE = "Title"
+        const val OPTION_0 = "Option 1"
+        const val OPTION_1 = "Option 2"
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
index 9f20c78..df80dd4 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
@@ -29,14 +29,12 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spa.framework.compose.toState
-import com.android.settingslib.spa.framework.util.asyncMapItem
 import com.android.settingslib.spaprivileged.R
 import com.android.settingslib.spaprivileged.model.app.AppEntry
 import com.android.settingslib.spaprivileged.model.app.AppListConfig
 import com.android.settingslib.spaprivileged.model.app.AppListData
-import com.android.settingslib.spaprivileged.model.app.AppListModel
-import com.android.settingslib.spaprivileged.model.app.AppRecord
-import kotlinx.coroutines.flow.Flow
+import com.android.settingslib.spaprivileged.tests.testutils.TestAppListModel
+import com.android.settingslib.spaprivileged.tests.testutils.TestAppRecord
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -92,21 +90,19 @@
         enableGrouping: Boolean = false,
     ) {
         composeTestRule.setContent {
-            AppList(
+            val appListInput = AppListInput(
                 config = AppListConfig(userId = USER_ID, showInstantApps = false),
-                listModel = TestAppListModel(enableGrouping),
+                listModel = TestAppListModel(enableGrouping = enableGrouping),
                 state = AppListState(
                     showSystem = false.toState(),
                     option = 0.toState(),
                     searchQuery = "".toState(),
                 ),
                 header = header,
-                appItem = { AppListItem(it) {} },
+                appItem = { AppListItem {} },
                 bottomPadding = 0.dp,
-                appListDataSupplier = {
-                    stateOf(AppListData(appEntries, option = 0))
-                }
             )
+            appListInput.AppList { stateOf(AppListData(appEntries, option = 0)) }
         }
     }
 
@@ -137,25 +133,3 @@
         )
     }
 }
-
-private data class TestAppRecord(
-    override val app: ApplicationInfo,
-    val group: String? = null,
-) : AppRecord
-
-private class TestAppListModel(val enableGrouping: Boolean) : AppListModel<TestAppRecord> {
-    override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
-        appListFlow.asyncMapItem { TestAppRecord(it) }
-
-    @Composable
-    override fun getSummary(option: Int, record: TestAppRecord) = null
-
-    override fun filter(
-        userIdFlow: Flow<Int>,
-        option: Int,
-        recordListFlow: Flow<List<TestAppRecord>>,
-    ) = recordListFlow
-
-    override fun getGroupTitle(option: Int, record: TestAppRecord) =
-        if (enableGrouping) record.group else null
-}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
index b3638c2..8e98d8c 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
@@ -41,8 +41,7 @@
 
 @RunWith(AndroidJUnit4::class)
 class AppStorageSizeTest {
-    @JvmField
-    @Rule
+    @get:Rule
     val mockito: MockitoRule = MockitoJUnit.rule()
 
     @get:Rule
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
new file mode 100644
index 0000000..4bc612a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spaprivileged.template.app
+
+import android.content.Context
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListModel
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class TogglePermissionAppListPageTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private var context: Context = ApplicationProvider.getApplicationContext()
+
+    @Test
+    fun appListInjectEntry_titleDisplayed() {
+        val entry = TogglePermissionAppListPageProvider.buildInjectEntry(PERMISSION_TYPE) {
+            TestTogglePermissionAppListModel()
+        }.build()
+
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                entry.UiLayout()
+            }
+        }
+
+        composeTestRule.onNodeWithText(context.getString(R.string.test_permission_title))
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun appListRoute() {
+        val route = TogglePermissionAppListPageProvider.getRoute(PERMISSION_TYPE)
+
+        assertThat(route).isEqualTo("TogglePermissionAppList/test.PERMISSION")
+    }
+
+    private companion object {
+        const val PERMISSION_TYPE = "test.PERMISSION"
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt
new file mode 100644
index 0000000..af3189f
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spaprivileged.template.app
+
+import android.content.Context
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListModel
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class TogglePermissionAppListTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private var context: Context = ApplicationProvider.getApplicationContext()
+
+    @Test
+    fun appListInjectEntry_titleDisplayed() {
+        val entry = TestTogglePermissionAppListProvider.buildAppListInjectEntry().build()
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                entry.UiLayout()
+            }
+        }
+
+        composeTestRule.onNodeWithText(context.getString(R.string.test_permission_title))
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun appListRoute() {
+        val route = TestTogglePermissionAppListProvider.getAppListRoute()
+
+        assertThat(route).isEqualTo("TogglePermissionAppList/test.PERMISSION")
+    }
+
+    @Test
+    fun togglePermissionAppListTemplate_createPageProviders() {
+        val togglePermissionAppListTemplate =
+            TogglePermissionAppListTemplate(listOf(TestTogglePermissionAppListProvider))
+
+        val createPageProviders = togglePermissionAppListTemplate.createPageProviders()
+
+        assertThat(createPageProviders).hasSize(2)
+        assertThat(createPageProviders.any { it is TogglePermissionAppListPageProvider }).isTrue()
+        assertThat(createPageProviders.any { it is TogglePermissionAppInfoPageProvider }).isTrue()
+    }
+}
+
+private object TestTogglePermissionAppListProvider : TogglePermissionAppListProvider {
+    override val permissionType = "test.PERMISSION"
+    override fun createModel(context: Context) = TestTogglePermissionAppListModel()
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt
new file mode 100644
index 0000000..ada4016
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spaprivileged.tests.testutils
+
+import android.content.pm.ApplicationInfo
+import com.android.settingslib.spa.framework.util.mapItem
+import com.android.settingslib.spaprivileged.model.app.AppListModel
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import kotlinx.coroutines.flow.Flow
+
+data class TestAppRecord(
+    override val app: ApplicationInfo,
+    val group: String? = null,
+) : AppRecord
+
+class TestAppListModel(
+    private val options: List<String> = emptyList(),
+    private val enableGrouping: Boolean = false,
+) : AppListModel<TestAppRecord> {
+    override fun getSpinnerOptions() = options
+
+    override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
+        appListFlow.mapItem(::TestAppRecord)
+
+    override fun getGroupTitle(option: Int, record: TestAppRecord) =
+        if (enableGrouping) record.group else null
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt
new file mode 100644
index 0000000..91a9c6b
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spaprivileged.tests.testutils
+
+import android.content.pm.ApplicationInfo
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListModel
+import kotlinx.coroutines.flow.Flow
+
+class TestTogglePermissionAppListModel : TogglePermissionAppListModel<TestAppRecord> {
+    override val pageTitleResId = R.string.test_permission_title
+    override val switchTitleResId = R.string.test_permission_switch_title
+    override val footerResId = R.string.test_permission_footer
+
+    override fun transformItem(app: ApplicationInfo) = TestAppRecord(app = app)
+
+    override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<TestAppRecord>>) =
+        recordListFlow
+
+    @Composable
+    override fun isAllowed(record: TestAppRecord) = stateOf(null)
+
+    override fun isChangeable(record: TestAppRecord) = false
+
+    override fun setAllowed(record: TestAppRecord, newAllowed: Boolean) {}
+}
diff --git a/packages/SettingsLib/TwoTargetPreference/Android.bp b/packages/SettingsLib/TwoTargetPreference/Android.bp
index 3baef4b..e9c6aed 100644
--- a/packages/SettingsLib/TwoTargetPreference/Android.bp
+++ b/packages/SettingsLib/TwoTargetPreference/Android.bp
@@ -23,5 +23,6 @@
     apex_available: [
         "//apex_available:platform",
         "com.android.permission",
+        "com.android.healthconnect",
     ],
 }
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 7c3948a..87354c7 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -168,25 +168,14 @@
 }
 
 android_library {
-    name: "SystemUI-tests",
+    name: "SystemUI-tests-base",
     manifest: "tests/AndroidManifest-base.xml",
-    additional_manifests: ["tests/AndroidManifest.xml"],
-
     resource_dirs: [
         "tests/res",
         "res-product",
         "res-keyguard",
         "res",
     ],
-    srcs: [
-        "tests/src/**/*.kt",
-        "tests/src/**/*.java",
-        "src/**/*.kt",
-        "src/**/*.java",
-        "src/**/I*.aidl",
-        ":ReleaseJavaFiles",
-        ":SystemUI-tests-utils",
-    ],
     static_libs: [
         "WifiTrackerLib",
         "SystemUIAnimationLib",
@@ -225,9 +214,6 @@
         "metrics-helper-lib",
         "hamcrest-library",
         "androidx.test.rules",
-        "androidx.test.uiautomator_uiautomator",
-        "mockito-target-extended-minus-junit4",
-        "androidx.test.ext.junit",
         "testables",
         "truth-prebuilt",
         "monet",
@@ -237,6 +223,27 @@
         "LowLightDreamLib",
         "motion_tool_lib",
     ],
+}
+
+android_library {
+    name: "SystemUI-tests",
+    manifest: "tests/AndroidManifest-base.xml",
+    additional_manifests: ["tests/AndroidManifest.xml"],
+    srcs: [
+        "tests/src/**/*.kt",
+        "tests/src/**/*.java",
+        "src/**/*.kt",
+        "src/**/*.java",
+        "src/**/I*.aidl",
+        ":ReleaseJavaFiles",
+        ":SystemUI-tests-utils",
+    ],
+    static_libs: [
+        "SystemUI-tests-base",
+        "androidx.test.uiautomator_uiautomator",
+        "mockito-target-extended-minus-junit4",
+        "androidx.test.ext.junit",
+    ],
     libs: [
         "android.test.runner",
         "android.test.base",
@@ -253,6 +260,45 @@
     },
 }
 
+android_app {
+    name: "SystemUIRobo-stub",
+    defaults: [
+        "platform_app_defaults",
+        "SystemUI_app_defaults",
+    ],
+    manifest: "tests/AndroidManifest-base.xml",
+    static_libs: [
+        "SystemUI-tests-base",
+    ],
+    aaptflags: [
+        "--extra-packages",
+        "com.android.systemui",
+    ],
+    dont_merge_manifests: true,
+    platform_apis: true,
+    system_ext_specific: true,
+    certificate: "platform",
+    privileged: true,
+    resource_dirs: [],
+}
+
+android_robolectric_test {
+    name: "SystemUiRoboTests",
+    srcs: [
+        "tests/robolectric/src/**/*.kt",
+        "tests/robolectric/src/**/*.java",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+        "android.test.mock",
+        "truth-prebuilt",
+    ],
+    kotlincflags: ["-Xjvm-default=enable"],
+    instrumentation_for: "SystemUIRobo-stub",
+    java_resource_dirs: ["tests/robolectric/config"],
+}
+
 // Opt-out config for optimizing the SystemUI target using R8.
 // Disabled via `export SYSTEMUI_OPTIMIZE_JAVA=false`, or explicitly in Make via
 // `SYSTEMUI_OPTIMIZE_JAVA := false`.
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
index f9c6841..43bfa74 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
@@ -320,9 +320,7 @@
                                 counterWallpaper.cleanUp(finishTransaction)
                                 // Release surface references now. This is apparently to free GPU
                                 // memory while doing quick operations (eg. during CTS).
-                                for (i in info.changes.indices.reversed()) {
-                                    info.changes[i].leash.release()
-                                }
+                                info.releaseAllSurfaces()
                                 for (i in leashMap.size - 1 downTo 0) {
                                     leashMap.valueAt(i).release()
                                 }
@@ -331,6 +329,7 @@
                                         null /* wct */,
                                         finishTransaction
                                     )
+                                    finishTransaction.close()
                                 } catch (e: RemoteException) {
                                     Log.e(
                                         "ActivityOptionsCompat",
@@ -364,6 +363,9 @@
                 ) {
                     // TODO: hook up merge to recents onTaskAppeared if applicable. Until then,
                     //       ignore any incoming merges.
+                    // Clean up stuff though cuz GC takes too long for benchmark tests.
+                    t.close()
+                    info.releaseAllSurfaces()
                 }
             }
         }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt
new file mode 100644
index 0000000..f490c54
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2022 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.systemui.shared.quickaffordance.data.content
+
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+
+class FakeKeyguardQuickAffordanceProviderClient(
+    slots: List<KeyguardQuickAffordanceProviderClient.Slot> =
+        listOf(
+            KeyguardQuickAffordanceProviderClient.Slot(
+                id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+                capacity = 1,
+            ),
+            KeyguardQuickAffordanceProviderClient.Slot(
+                id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+                capacity = 1,
+            ),
+        ),
+    affordances: List<KeyguardQuickAffordanceProviderClient.Affordance> =
+        listOf(
+            KeyguardQuickAffordanceProviderClient.Affordance(
+                id = AFFORDANCE_1,
+                name = AFFORDANCE_1,
+                iconResourceId = 0,
+            ),
+            KeyguardQuickAffordanceProviderClient.Affordance(
+                id = AFFORDANCE_2,
+                name = AFFORDANCE_2,
+                iconResourceId = 0,
+            ),
+            KeyguardQuickAffordanceProviderClient.Affordance(
+                id = AFFORDANCE_3,
+                name = AFFORDANCE_3,
+                iconResourceId = 0,
+            ),
+        ),
+    flags: List<KeyguardQuickAffordanceProviderClient.Flag> =
+        listOf(
+            KeyguardQuickAffordanceProviderClient.Flag(
+                name = KeyguardQuickAffordanceProviderContract.FlagsTable.FLAG_NAME_FEATURE_ENABLED,
+                value = true,
+            )
+        ),
+) : KeyguardQuickAffordanceProviderClient {
+
+    private val slots = MutableStateFlow(slots)
+    private val affordances = MutableStateFlow(affordances)
+    private val flags = MutableStateFlow(flags)
+
+    private val selections = MutableStateFlow<Map<String, List<String>>>(emptyMap())
+
+    override suspend fun insertSelection(slotId: String, affordanceId: String) {
+        val slotCapacity =
+            querySlots().find { it.id == slotId }?.capacity
+                ?: error("Slot with ID \"$slotId\" not found!")
+        val affordances = selections.value.getOrDefault(slotId, mutableListOf()).toMutableList()
+        while (affordances.size + 1 > slotCapacity) {
+            affordances.removeAt(0)
+        }
+        affordances.remove(affordanceId)
+        affordances.add(affordanceId)
+        selections.value = selections.value.toMutableMap().apply { this[slotId] = affordances }
+    }
+
+    override suspend fun querySlots(): List<KeyguardQuickAffordanceProviderClient.Slot> {
+        return slots.value
+    }
+
+    override suspend fun queryFlags(): List<KeyguardQuickAffordanceProviderClient.Flag> {
+        return flags.value
+    }
+
+    override fun observeSlots(): Flow<List<KeyguardQuickAffordanceProviderClient.Slot>> {
+        return slots.asStateFlow()
+    }
+
+    override fun observeFlags(): Flow<List<KeyguardQuickAffordanceProviderClient.Flag>> {
+        return flags.asStateFlow()
+    }
+
+    override suspend fun queryAffordances():
+        List<KeyguardQuickAffordanceProviderClient.Affordance> {
+        return affordances.value
+    }
+
+    override fun observeAffordances():
+        Flow<List<KeyguardQuickAffordanceProviderClient.Affordance>> {
+        return affordances.asStateFlow()
+    }
+
+    override suspend fun querySelections(): List<KeyguardQuickAffordanceProviderClient.Selection> {
+        return toSelectionList(selections.value, affordances.value)
+    }
+
+    override fun observeSelections(): Flow<List<KeyguardQuickAffordanceProviderClient.Selection>> {
+        return combine(selections, affordances) { selections, affordances ->
+            toSelectionList(selections, affordances)
+        }
+    }
+
+    override suspend fun deleteSelection(slotId: String, affordanceId: String) {
+        val affordances = selections.value.getOrDefault(slotId, mutableListOf()).toMutableList()
+        affordances.remove(affordanceId)
+
+        selections.value = selections.value.toMutableMap().apply { this[slotId] = affordances }
+    }
+
+    override suspend fun deleteAllSelections(slotId: String) {
+        selections.value = selections.value.toMutableMap().apply { this[slotId] = emptyList() }
+    }
+
+    override suspend fun getAffordanceIcon(iconResourceId: Int, tintColor: Int): Drawable {
+        return BitmapDrawable()
+    }
+
+    fun setFlag(
+        name: String,
+        value: Boolean,
+    ) {
+        flags.value =
+            flags.value.toMutableList().apply {
+                removeIf { it.name == name }
+                add(KeyguardQuickAffordanceProviderClient.Flag(name = name, value = value))
+            }
+    }
+
+    fun setSlotCapacity(slotId: String, capacity: Int) {
+        slots.value =
+            slots.value.toMutableList().apply {
+                val index = indexOfFirst { it.id == slotId }
+                check(index != -1) { "Slot with ID \"$slotId\" doesn't exist!" }
+                set(
+                    index,
+                    KeyguardQuickAffordanceProviderClient.Slot(id = slotId, capacity = capacity)
+                )
+            }
+    }
+
+    fun addAffordance(affordance: KeyguardQuickAffordanceProviderClient.Affordance): Int {
+        affordances.value = affordances.value + listOf(affordance)
+        return affordances.value.size - 1
+    }
+
+    private fun toSelectionList(
+        selections: Map<String, List<String>>,
+        affordances: List<KeyguardQuickAffordanceProviderClient.Affordance>,
+    ): List<KeyguardQuickAffordanceProviderClient.Selection> {
+        return selections
+            .map { (slotId, affordanceIds) ->
+                affordanceIds.map { affordanceId ->
+                    val affordanceName =
+                        affordances.find { it.id == affordanceId }?.name
+                            ?: error("No affordance with ID of \"$affordanceId\"!")
+                    KeyguardQuickAffordanceProviderClient.Selection(
+                        slotId = slotId,
+                        affordanceId = affordanceId,
+                        affordanceName = affordanceName,
+                    )
+                }
+            }
+            .flatten()
+    }
+
+    companion object {
+        const val AFFORDANCE_1 = "affordance_1"
+        const val AFFORDANCE_2 = "affordance_2"
+        const val AFFORDANCE_3 = "affordance_3"
+    }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderClient.kt
new file mode 100644
index 0000000..3213b2e
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderClient.kt
@@ -0,0 +1,479 @@
+/*
+ * Copyright (C) 2022 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.systemui.shared.quickaffordance.data.content
+
+import android.annotation.SuppressLint
+import android.content.ContentValues
+import android.content.Context
+import android.database.ContentObserver
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import androidx.annotation.DrawableRes
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.withContext
+
+/** Client for using a content provider implementing the [Contract]. */
+interface KeyguardQuickAffordanceProviderClient {
+
+    /**
+     * Selects an affordance with the given ID for a slot on the lock screen with the given ID.
+     *
+     * Note that the maximum number of selected affordances on this slot is automatically enforced.
+     * Selecting a slot that is already full (e.g. already has a number of selected affordances at
+     * its maximum capacity) will automatically remove the oldest selected affordance before adding
+     * the one passed in this call. Additionally, selecting an affordance that's already one of the
+     * selected affordances on the slot will move the selected affordance to the newest location in
+     * the slot.
+     */
+    suspend fun insertSelection(
+        slotId: String,
+        affordanceId: String,
+    )
+
+    /** Returns all available slots supported by the device. */
+    suspend fun querySlots(): List<Slot>
+
+    /** Returns the list of flags. */
+    suspend fun queryFlags(): List<Flag>
+
+    /**
+     * Returns [Flow] for observing the collection of slots.
+     *
+     * @see [querySlots]
+     */
+    fun observeSlots(): Flow<List<Slot>>
+
+    /**
+     * Returns [Flow] for observing the collection of flags.
+     *
+     * @see [queryFlags]
+     */
+    fun observeFlags(): Flow<List<Flag>>
+
+    /**
+     * Returns all available affordances supported by the device, regardless of current slot
+     * placement.
+     */
+    suspend fun queryAffordances(): List<Affordance>
+
+    /**
+     * Returns [Flow] for observing the collection of affordances.
+     *
+     * @see [queryAffordances]
+     */
+    fun observeAffordances(): Flow<List<Affordance>>
+
+    /** Returns the current slot-affordance selections. */
+    suspend fun querySelections(): List<Selection>
+
+    /**
+     * Returns [Flow] for observing the collection of selections.
+     *
+     * @see [querySelections]
+     */
+    fun observeSelections(): Flow<List<Selection>>
+
+    /** Unselects an affordance with the given ID from the slot with the given ID. */
+    suspend fun deleteSelection(
+        slotId: String,
+        affordanceId: String,
+    )
+
+    /** Unselects all affordances from the slot with the given ID. */
+    suspend fun deleteAllSelections(
+        slotId: String,
+    )
+
+    /** Returns a [Drawable] with the given ID, loaded from the system UI package. */
+    suspend fun getAffordanceIcon(
+        @DrawableRes iconResourceId: Int,
+        tintColor: Int = Color.WHITE,
+    ): Drawable
+
+    /** Models a slot. A position that quick affordances can be positioned in. */
+    data class Slot(
+        /** Unique ID of the slot. */
+        val id: String,
+        /**
+         * The maximum number of quick affordances that are allowed to be positioned in this slot.
+         */
+        val capacity: Int,
+    )
+
+    /**
+     * Models a quick affordance. An action that can be selected by the user to appear in one or
+     * more slots on the lock screen.
+     */
+    data class Affordance(
+        /** Unique ID of the quick affordance. */
+        val id: String,
+        /** User-facing label for this affordance. */
+        val name: String,
+        /**
+         * Resource ID for the user-facing icon for this affordance. This resource is hosted by the
+         * System UI process so it must be used with
+         * `PackageManager.getResourcesForApplication(String)`.
+         */
+        val iconResourceId: Int,
+        /**
+         * Whether the affordance is enabled. Disabled affordances should be shown on the picker but
+         * should be rendered as "disabled". When tapped, the enablement properties should be used
+         * to populate UI that would explain to the user what to do in order to re-enable this
+         * affordance.
+         */
+        val isEnabled: Boolean = true,
+        /**
+         * If the affordance is disabled, this is a set of instruction messages to be shown to the
+         * user when the disabled affordance is selected. The instructions should help the user
+         * figure out what to do in order to re-neable this affordance.
+         */
+        val enablementInstructions: List<String>? = null,
+        /**
+         * If the affordance is disabled, this is a label for a button shown together with the set
+         * of instruction messages when the disabled affordance is selected. The button should help
+         * send the user to a flow that would help them achieve the instructions and re-enable this
+         * affordance.
+         *
+         * If `null`, the button should not be shown.
+         */
+        val enablementActionText: String? = null,
+        /**
+         * If the affordance is disabled, this is a "component name" of the format
+         * `packageName/action` to be used as an `Intent` for `startActivity` when the action button
+         * (shown together with the set of instruction messages when the disabled affordance is
+         * selected) is clicked by the user. The button should help send the user to a flow that
+         * would help them achieve the instructions and re-enable this affordance.
+         *
+         * If `null`, the button should not be shown.
+         */
+        val enablementActionComponentName: String? = null,
+    )
+
+    /** Models a selection of a quick affordance on a slot. */
+    data class Selection(
+        /** The unique ID of the slot. */
+        val slotId: String,
+        /** The unique ID of the quick affordance. */
+        val affordanceId: String,
+        /** The user-visible label for the quick affordance. */
+        val affordanceName: String,
+    )
+
+    /** Models a System UI flag. */
+    data class Flag(
+        /** The name of the flag. */
+        val name: String,
+        /** The value of the flag. */
+        val value: Boolean,
+    )
+}
+
+class KeyguardQuickAffordanceProviderClientImpl(
+    private val context: Context,
+    private val backgroundDispatcher: CoroutineDispatcher,
+) : KeyguardQuickAffordanceProviderClient {
+
+    override suspend fun insertSelection(
+        slotId: String,
+        affordanceId: String,
+    ) {
+        withContext(backgroundDispatcher) {
+            context.contentResolver.insert(
+                Contract.SelectionTable.URI,
+                ContentValues().apply {
+                    put(Contract.SelectionTable.Columns.SLOT_ID, slotId)
+                    put(Contract.SelectionTable.Columns.AFFORDANCE_ID, affordanceId)
+                }
+            )
+        }
+    }
+
+    override suspend fun querySlots(): List<KeyguardQuickAffordanceProviderClient.Slot> {
+        return withContext(backgroundDispatcher) {
+            context.contentResolver
+                .query(
+                    Contract.SlotTable.URI,
+                    null,
+                    null,
+                    null,
+                    null,
+                )
+                ?.use { cursor ->
+                    buildList {
+                        val idColumnIndex = cursor.getColumnIndex(Contract.SlotTable.Columns.ID)
+                        val capacityColumnIndex =
+                            cursor.getColumnIndex(Contract.SlotTable.Columns.CAPACITY)
+                        if (idColumnIndex == -1 || capacityColumnIndex == -1) {
+                            return@buildList
+                        }
+
+                        while (cursor.moveToNext()) {
+                            add(
+                                KeyguardQuickAffordanceProviderClient.Slot(
+                                    id = cursor.getString(idColumnIndex),
+                                    capacity = cursor.getInt(capacityColumnIndex),
+                                )
+                            )
+                        }
+                    }
+                }
+        }
+            ?: emptyList()
+    }
+
+    override suspend fun queryFlags(): List<KeyguardQuickAffordanceProviderClient.Flag> {
+        return withContext(backgroundDispatcher) {
+            context.contentResolver
+                .query(
+                    Contract.FlagsTable.URI,
+                    null,
+                    null,
+                    null,
+                    null,
+                )
+                ?.use { cursor ->
+                    buildList {
+                        val nameColumnIndex =
+                            cursor.getColumnIndex(Contract.FlagsTable.Columns.NAME)
+                        val valueColumnIndex =
+                            cursor.getColumnIndex(Contract.FlagsTable.Columns.VALUE)
+                        if (nameColumnIndex == -1 || valueColumnIndex == -1) {
+                            return@buildList
+                        }
+
+                        while (cursor.moveToNext()) {
+                            add(
+                                KeyguardQuickAffordanceProviderClient.Flag(
+                                    name = cursor.getString(nameColumnIndex),
+                                    value = cursor.getInt(valueColumnIndex) == 1,
+                                )
+                            )
+                        }
+                    }
+                }
+        }
+            ?: emptyList()
+    }
+
+    override fun observeSlots(): Flow<List<KeyguardQuickAffordanceProviderClient.Slot>> {
+        return observeUri(Contract.SlotTable.URI).map { querySlots() }
+    }
+
+    override fun observeFlags(): Flow<List<KeyguardQuickAffordanceProviderClient.Flag>> {
+        return observeUri(Contract.FlagsTable.URI).map { queryFlags() }
+    }
+
+    override suspend fun queryAffordances():
+        List<KeyguardQuickAffordanceProviderClient.Affordance> {
+        return withContext(backgroundDispatcher) {
+            context.contentResolver
+                .query(
+                    Contract.AffordanceTable.URI,
+                    null,
+                    null,
+                    null,
+                    null,
+                )
+                ?.use { cursor ->
+                    buildList {
+                        val idColumnIndex =
+                            cursor.getColumnIndex(Contract.AffordanceTable.Columns.ID)
+                        val nameColumnIndex =
+                            cursor.getColumnIndex(Contract.AffordanceTable.Columns.NAME)
+                        val iconColumnIndex =
+                            cursor.getColumnIndex(Contract.AffordanceTable.Columns.ICON)
+                        val isEnabledColumnIndex =
+                            cursor.getColumnIndex(Contract.AffordanceTable.Columns.IS_ENABLED)
+                        val enablementInstructionsColumnIndex =
+                            cursor.getColumnIndex(
+                                Contract.AffordanceTable.Columns.ENABLEMENT_INSTRUCTIONS
+                            )
+                        val enablementActionTextColumnIndex =
+                            cursor.getColumnIndex(
+                                Contract.AffordanceTable.Columns.ENABLEMENT_ACTION_TEXT
+                            )
+                        val enablementComponentNameColumnIndex =
+                            cursor.getColumnIndex(
+                                Contract.AffordanceTable.Columns.ENABLEMENT_COMPONENT_NAME
+                            )
+                        if (
+                            idColumnIndex == -1 ||
+                                nameColumnIndex == -1 ||
+                                iconColumnIndex == -1 ||
+                                isEnabledColumnIndex == -1 ||
+                                enablementInstructionsColumnIndex == -1 ||
+                                enablementActionTextColumnIndex == -1 ||
+                                enablementComponentNameColumnIndex == -1
+                        ) {
+                            return@buildList
+                        }
+
+                        while (cursor.moveToNext()) {
+                            add(
+                                KeyguardQuickAffordanceProviderClient.Affordance(
+                                    id = cursor.getString(idColumnIndex),
+                                    name = cursor.getString(nameColumnIndex),
+                                    iconResourceId = cursor.getInt(iconColumnIndex),
+                                    isEnabled = cursor.getInt(isEnabledColumnIndex) == 1,
+                                    enablementInstructions =
+                                        cursor
+                                            .getString(enablementInstructionsColumnIndex)
+                                            ?.split(
+                                                Contract.AffordanceTable
+                                                    .ENABLEMENT_INSTRUCTIONS_DELIMITER
+                                            ),
+                                    enablementActionText =
+                                        cursor.getString(enablementActionTextColumnIndex),
+                                    enablementActionComponentName =
+                                        cursor.getString(enablementComponentNameColumnIndex),
+                                )
+                            )
+                        }
+                    }
+                }
+        }
+            ?: emptyList()
+    }
+
+    override fun observeAffordances():
+        Flow<List<KeyguardQuickAffordanceProviderClient.Affordance>> {
+        return observeUri(Contract.AffordanceTable.URI).map { queryAffordances() }
+    }
+
+    override suspend fun querySelections(): List<KeyguardQuickAffordanceProviderClient.Selection> {
+        return withContext(backgroundDispatcher) {
+            context.contentResolver
+                .query(
+                    Contract.SelectionTable.URI,
+                    null,
+                    null,
+                    null,
+                    null,
+                )
+                ?.use { cursor ->
+                    buildList {
+                        val slotIdColumnIndex =
+                            cursor.getColumnIndex(Contract.SelectionTable.Columns.SLOT_ID)
+                        val affordanceIdColumnIndex =
+                            cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_ID)
+                        val affordanceNameColumnIndex =
+                            cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_NAME)
+                        if (
+                            slotIdColumnIndex == -1 ||
+                                affordanceIdColumnIndex == -1 ||
+                                affordanceNameColumnIndex == -1
+                        ) {
+                            return@buildList
+                        }
+
+                        while (cursor.moveToNext()) {
+                            add(
+                                KeyguardQuickAffordanceProviderClient.Selection(
+                                    slotId = cursor.getString(slotIdColumnIndex),
+                                    affordanceId = cursor.getString(affordanceIdColumnIndex),
+                                    affordanceName = cursor.getString(affordanceNameColumnIndex),
+                                )
+                            )
+                        }
+                    }
+                }
+        }
+            ?: emptyList()
+    }
+
+    override fun observeSelections(): Flow<List<KeyguardQuickAffordanceProviderClient.Selection>> {
+        return observeUri(Contract.SelectionTable.URI).map { querySelections() }
+    }
+
+    override suspend fun deleteSelection(
+        slotId: String,
+        affordanceId: String,
+    ) {
+        withContext(backgroundDispatcher) {
+            context.contentResolver.delete(
+                Contract.SelectionTable.URI,
+                "${Contract.SelectionTable.Columns.SLOT_ID} = ? AND" +
+                    " ${Contract.SelectionTable.Columns.AFFORDANCE_ID} = ?",
+                arrayOf(
+                    slotId,
+                    affordanceId,
+                ),
+            )
+        }
+    }
+
+    override suspend fun deleteAllSelections(
+        slotId: String,
+    ) {
+        withContext(backgroundDispatcher) {
+            context.contentResolver.delete(
+                Contract.SelectionTable.URI,
+                Contract.SelectionTable.Columns.SLOT_ID,
+                arrayOf(
+                    slotId,
+                ),
+            )
+        }
+    }
+
+    @SuppressLint("UseCompatLoadingForDrawables")
+    override suspend fun getAffordanceIcon(
+        @DrawableRes iconResourceId: Int,
+        tintColor: Int,
+    ): Drawable {
+        return withContext(backgroundDispatcher) {
+            context.packageManager
+                .getResourcesForApplication(SYSTEM_UI_PACKAGE_NAME)
+                .getDrawable(iconResourceId, context.theme)
+                .apply { setTint(tintColor) }
+        }
+    }
+
+    private fun observeUri(
+        uri: Uri,
+    ): Flow<Unit> {
+        return callbackFlow {
+                val observer =
+                    object : ContentObserver(null) {
+                        override fun onChange(selfChange: Boolean) {
+                            trySend(Unit)
+                        }
+                    }
+
+                context.contentResolver.registerContentObserver(
+                    uri,
+                    /* notifyForDescendants= */ true,
+                    observer,
+                )
+
+                awaitClose { context.contentResolver.unregisterContentObserver(observer) }
+            }
+            .onStart { emit(Unit) }
+    }
+
+    companion object {
+        private const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui"
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt
similarity index 98%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt
rename to packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt
index 98d8d3e..17be74b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt
@@ -15,7 +15,7 @@
  *
  */
 
-package com.android.systemui.shared.keyguard.data.content
+package com.android.systemui.shared.quickaffordance.data.content
 
 import android.content.ContentResolver
 import android.net.Uri
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordanceSlots.kt
similarity index 100%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt
rename to packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordanceSlots.kt
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
index 93c8073..1b0dacc 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
@@ -166,15 +166,14 @@
                     counterLauncher.cleanUp(finishTransaction);
                     counterWallpaper.cleanUp(finishTransaction);
                     // Release surface references now. This is apparently to free GPU memory
-                    // while doing quick operations (eg. during CTS).
-                    for (int i = info.getChanges().size() - 1; i >= 0; --i) {
-                        info.getChanges().get(i).getLeash().release();
-                    }
+                    // before GC would.
+                    info.releaseAllSurfaces();
                     // Don't release here since launcher might still be using them. Instead
                     // let launcher release them (eg. via RemoteAnimationTargets)
                     leashMap.clear();
                     try {
                         finishCallback.onTransitionFinished(null /* wct */, finishTransaction);
+                        finishTransaction.close();
                     } catch (RemoteException e) {
                         Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
                                 + " finished callback", e);
@@ -203,10 +202,13 @@
                 synchronized (mFinishRunnables) {
                     finishRunnable = mFinishRunnables.remove(mergeTarget);
                 }
+                // Since we're not actually animating, release native memory now
+                t.close();
+                info.releaseAllSurfaces();
                 if (finishRunnable == null) return;
                 onAnimationCancelled(false /* isKeyguardOccluded */);
                 finishRunnable.run();
             }
         };
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index d4d3d25..b7e2494 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -126,15 +126,18 @@
             public void mergeAnimation(IBinder transition, TransitionInfo info,
                     SurfaceControl.Transaction t, IBinder mergeTarget,
                     IRemoteTransitionFinishedCallback finishedCallback) {
-                if (!mergeTarget.equals(mToken)) return;
-                if (!mRecentsSession.merge(info, t, recents)) return;
-                try {
-                    finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Error merging transition.", e);
+                if (mergeTarget.equals(mToken) && mRecentsSession.merge(info, t, recents)) {
+                    try {
+                        finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Error merging transition.", e);
+                    }
+                    // commit taskAppeared after merge transition finished.
+                    mRecentsSession.commitTasksAppearedIfNeeded(recents);
+                } else {
+                    t.close();
+                    info.releaseAllSurfaces();
                 }
-                // commit taskAppeared after merge transition finished.
-                mRecentsSession.commitTasksAppearedIfNeeded(recents);
             }
         };
         return new RemoteTransition(remote, appThread);
@@ -248,6 +251,8 @@
                 }
                 // In this case, we are "returning" to an already running app, so just consume
                 // the merge and do nothing.
+                info.releaseAllSurfaces();
+                t.close();
                 return true;
             }
             final int layer = mInfo.getChanges().size() * 3;
@@ -264,6 +269,8 @@
                 t.setLayer(targets[i].leash, layer);
             }
             t.apply();
+            // not using the incoming anim-only surfaces
+            info.releaseAnimSurfaces();
             mAppearedTargets = targets;
             return true;
         }
@@ -380,9 +387,7 @@
             }
             // Only release the non-local created surface references. The animator is responsible
             // for releasing the leashes created by local.
-            for (int i = 0; i < mInfo.getChanges().size(); ++i) {
-                mInfo.getChanges().get(i).getLeash().release();
-            }
+            mInfo.releaseAllSurfaces();
             // Reset all members.
             mWrapped = null;
             mFinishCB = null;
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
index c5190e8..ea808eb 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
@@ -135,7 +135,7 @@
             mPowerManager.userActivity(SystemClock.uptimeMillis(), true);
         }
         mActivityTaskManager.stopSystemLockTaskMode();
-        mShadeController.collapsePanel(false);
+        mShadeController.collapseShade(false);
         if (mTelecomManager != null && mTelecomManager.isInCall()) {
             mTelecomManager.showInCallScreen(false);
             if (mEmergencyButtonCallback != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index d60cc75..50449b0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -52,6 +52,7 @@
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -183,15 +184,18 @@
     private final AccessibilityManager mA11yManager;
     private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
     private final NotificationShadeWindowController mNotificationShadeController;
+    private final ShadeController mShadeController;
     private final StatusBarWindowCallback mNotificationShadeCallback;
     private boolean mDismissNotificationShadeActionRegistered;
 
     @Inject
     public SystemActions(Context context,
             NotificationShadeWindowController notificationShadeController,
+            ShadeController shadeController,
             Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
             Optional<Recents> recentsOptional) {
         mContext = context;
+        mShadeController = shadeController;
         mRecentsOptional = recentsOptional;
         mReceiver = new SystemActionsBroadcastReceiver();
         mLocale = mContext.getResources().getConfiguration().getLocales().get(0);
@@ -529,9 +533,7 @@
     }
 
     private void handleAccessibilityDismissNotificationShade() {
-        mCentralSurfacesOptionalLazy.get().ifPresent(
-                centralSurfaces -> centralSurfaces.animateCollapsePanels(
-                        CommandQueue.FLAG_EXCLUDE_NONE, false /* force */));
+        mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
     }
 
     private void handleDpadUp() {
diff --git a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
index 5616a00..621b99d 100644
--- a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
@@ -29,13 +29,15 @@
 import android.util.Log
 import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapper
 import com.android.systemui.controls.controller.ControlsFavoritePersistenceWrapper
+import com.android.systemui.keyguard.domain.backup.KeyguardQuickAffordanceBackupHelper
 import com.android.systemui.people.widget.PeopleBackupHelper
 
 /**
  * Helper for backing up elements in SystemUI
  *
- * This helper is invoked by BackupManager whenever a backup or restore is required in SystemUI.
- * The helper can be used to back up any element that is stored in [Context.getFilesDir].
+ * This helper is invoked by BackupManager whenever a backup or restore is required in SystemUI. The
+ * helper can be used to back up any element that is stored in [Context.getFilesDir] or
+ * [Context.getSharedPreferences].
  *
  * After restoring is done, a [ACTION_RESTORE_FINISHED] intent will be send to SystemUI user 0,
  * indicating that restoring is finished for a given user.
@@ -47,9 +49,11 @@
         internal const val CONTROLS = ControlsFavoritePersistenceWrapper.FILE_NAME
         private const val NO_OVERWRITE_FILES_BACKUP_KEY = "systemui.files_no_overwrite"
         private const val PEOPLE_TILES_BACKUP_KEY = "systemui.people.shared_preferences"
+        private const val KEYGUARD_QUICK_AFFORDANCES_BACKUP_KEY =
+            "systemui.keyguard.quickaffordance.shared_preferences"
         val controlsDataLock = Any()
         const val ACTION_RESTORE_FINISHED = "com.android.systemui.backup.RESTORE_FINISHED"
-        private const val PERMISSION_SELF = "com.android.systemui.permission.SELF"
+        const val PERMISSION_SELF = "com.android.systemui.permission.SELF"
     }
 
     override fun onCreate(userHandle: UserHandle, operationType: Int) {
@@ -67,17 +71,27 @@
         }
 
         val keys = PeopleBackupHelper.getFilesToBackup()
-        addHelper(PEOPLE_TILES_BACKUP_KEY, PeopleBackupHelper(
-                this, userHandle, keys.toTypedArray()))
+        addHelper(
+            PEOPLE_TILES_BACKUP_KEY,
+            PeopleBackupHelper(this, userHandle, keys.toTypedArray())
+        )
+        addHelper(
+            KEYGUARD_QUICK_AFFORDANCES_BACKUP_KEY,
+            KeyguardQuickAffordanceBackupHelper(
+                context = this,
+                userId = userHandle.identifier,
+            ),
+        )
     }
 
     override fun onRestoreFinished() {
         super.onRestoreFinished()
-        val intent = Intent(ACTION_RESTORE_FINISHED).apply {
-            `package` = packageName
-            putExtra(Intent.EXTRA_USER_ID, userId)
-            flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY
-        }
+        val intent =
+            Intent(ACTION_RESTORE_FINISHED).apply {
+                `package` = packageName
+                putExtra(Intent.EXTRA_USER_ID, userId)
+                flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY
+            }
         sendBroadcastAsUser(intent, UserHandle.SYSTEM, PERMISSION_SELF)
     }
 
@@ -90,7 +104,9 @@
      * @property lock a lock to hold while backing up and restoring the files.
      * @property context the context of the [BackupAgent]
      * @property fileNamesAndPostProcess a map from the filenames to back up and the post processing
+     * ```
      *                                   actions to take
+     * ```
      */
     private class NoOverwriteFileBackupHelper(
         val lock: Any,
@@ -115,23 +131,23 @@
             data: BackupDataOutput?,
             newState: ParcelFileDescriptor?
         ) {
-            synchronized(lock) {
-                super.performBackup(oldState, data, newState)
-            }
+            synchronized(lock) { super.performBackup(oldState, data, newState) }
         }
     }
 }
+
 private fun getPPControlsFile(context: Context): () -> Unit {
     return {
         val filesDir = context.filesDir
         val file = Environment.buildPath(filesDir, BackupHelper.CONTROLS)
         if (file.exists()) {
-            val dest = Environment.buildPath(filesDir,
-                AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME)
+            val dest =
+                Environment.buildPath(filesDir, AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME)
             file.copyTo(dest)
             val jobScheduler = context.getSystemService(JobScheduler::class.java)
             jobScheduler?.schedule(
-                AuxiliaryPersistenceWrapper.DeletionJobService.getJobForContext(context))
+                AuxiliaryPersistenceWrapper.DeletionJobService.getJobForContext(context)
+            )
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index 537cbc5..a0a892d 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -64,8 +64,9 @@
  * from SystemUI. That way the number of calls to [BroadcastReceiver.onReceive] can be reduced for
  * a given broadcast.
  *
- * Use only for IntentFilters with actions and optionally categories. It does not support,
- * permissions, schemes, data types, data authorities or priority different than 0.
+ * Use only for IntentFilters with actions and optionally categories. It does not support schemes,
+ * data types, data authorities or priority different than 0.
+ *
  * Cannot be used for getting sticky broadcasts (either as return of registering or as re-delivery).
  * Broadcast handling may be asynchronous *without* calling goAsync(), as it's running within sysui
  * and doesn't need to worry about being killed.
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index aa6c619..2d558ad 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -346,6 +346,12 @@
     // TODO(b/256873975): Tracking Bug
     @JvmField @Keep val WM_BUBBLE_BAR = unreleasedFlag(1111, "wm_bubble_bar")
 
+    // TODO(b/260271148): Tracking bug
+    @Keep
+    @JvmField
+    val WM_DESKTOP_WINDOWING_2 =
+        sysPropBooleanFlag(1112, "persist.wm.debug.desktop_mode_2", default = false)
+
     // 1200 - predictive back
     @Keep
     @JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
index 29febb6..4ae37c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
@@ -29,7 +29,7 @@
 import com.android.systemui.SystemUIAppComponentFactoryBase
 import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback
 import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
-import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
 import javax.inject.Inject
 import kotlinx.coroutines.runBlocking
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 0214313..e631816 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -220,6 +220,7 @@
                                 synchronized (mFinishCallbacks) {
                                     if (mFinishCallbacks.remove(transition) == null) return;
                                 }
+                                info.releaseAllSurfaces();
                                 Slog.d(TAG, "Finish IRemoteAnimationRunner.");
                                 finishCallback.onTransitionFinished(null /* wct */, null /* t */);
                             }
@@ -235,6 +236,8 @@
                     synchronized (mFinishCallbacks) {
                         origFinishCB = mFinishCallbacks.remove(transition);
                     }
+                    info.releaseAllSurfaces();
+                    t.close();
                     if (origFinishCB == null) {
                         // already finished (or not started yet), so do nothing.
                         return;
@@ -423,12 +426,15 @@
             t.apply();
             mBinder.setOccluded(true /* isOccluded */, true /* animate */);
             finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+            info.releaseAllSurfaces();
         }
 
         @Override
         public void mergeAnimation(IBinder transition, TransitionInfo info,
                 SurfaceControl.Transaction t, IBinder mergeTarget,
                 IRemoteTransitionFinishedCallback finishCallback) {
+            t.close();
+            info.releaseAllSurfaces();
         }
     };
 
@@ -440,12 +446,15 @@
             t.apply();
             mBinder.setOccluded(false /* isOccluded */, true /* animate */);
             finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+            info.releaseAllSurfaces();
         }
 
         @Override
         public void mergeAnimation(IBinder transition, TransitionInfo info,
                 SurfaceControl.Transaction t, IBinder mergeTarget,
                 IRemoteTransitionFinishedCallback finishCallback) {
+            t.close();
+            info.releaseAllSurfaces();
         }
     };
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 5ed3ba7..948239a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -870,7 +870,7 @@
                 @Override
                 public void onLaunchAnimationEnd(boolean launchIsFullScreen) {
                     if (launchIsFullScreen) {
-                        mCentralSurfaces.instantCollapseNotificationPanel();
+                        mShadeController.get().instantCollapseShade();
                     }
 
                     mOccludeAnimationPlaying = false;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
index 3c09aab..dbc376e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
@@ -26,14 +26,17 @@
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import dagger.Lazy
+import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.flowOf
-import javax.inject.Inject
 
 @SysUISingleton
-class CameraQuickAffordanceConfig @Inject constructor(
-        @Application private val context: Context,
-        private val cameraGestureHelper: CameraGestureHelper,
+class CameraQuickAffordanceConfig
+@Inject
+constructor(
+    @Application private val context: Context,
+    private val cameraGestureHelper: Lazy<CameraGestureHelper>,
 ) : KeyguardQuickAffordanceConfig {
 
     override val key: String
@@ -46,17 +49,23 @@
         get() = com.android.internal.R.drawable.perm_group_camera
 
     override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState>
-        get() = flowOf(
-            KeyguardQuickAffordanceConfig.LockScreenState.Visible(
-                    icon = Icon.Resource(
+        get() =
+            flowOf(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+                    icon =
+                        Icon.Resource(
                             com.android.internal.R.drawable.perm_group_camera,
                             ContentDescription.Resource(R.string.accessibility_camera_button)
-                    )
+                        )
+                )
             )
-        )
 
-    override fun onTriggered(expandable: Expandable?): KeyguardQuickAffordanceConfig.OnTriggeredResult {
-        cameraGestureHelper.launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
+    override fun onTriggered(
+        expandable: Expandable?
+    ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+        cameraGestureHelper
+            .get()
+            .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
         return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
index 4477310..98b1a73 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
-import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
 import kotlinx.coroutines.flow.Flow
 
 /** Defines interface that can act as data source for a single quick affordance model. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
index b29cf45..4f37e5f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
@@ -18,9 +18,11 @@
 package com.android.systemui.keyguard.data.quickaffordance
 
 import android.content.Context
+import android.content.IntentFilter
 import android.content.SharedPreferences
-import androidx.annotation.VisibleForTesting
 import com.android.systemui.R
+import com.android.systemui.backup.BackupHelper
+import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
@@ -28,14 +30,18 @@
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.onStart
 
 /**
  * Manages and provides access to the current "selections" of keyguard quick affordances, answering
  * the question "which affordances should the keyguard show?".
  */
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class KeyguardQuickAffordanceSelectionManager
 @Inject
@@ -43,15 +49,10 @@
     @Application context: Context,
     private val userFileManager: UserFileManager,
     private val userTracker: UserTracker,
+    broadcastDispatcher: BroadcastDispatcher,
 ) {
 
-    private val sharedPrefs: SharedPreferences
-        get() =
-            userFileManager.getSharedPreferences(
-                FILE_NAME,
-                Context.MODE_PRIVATE,
-                userTracker.userId,
-            )
+    private var sharedPrefs: SharedPreferences = instantiateSharedPrefs()
 
     private val userId: Flow<Int> = conflatedCallbackFlow {
         val callback =
@@ -78,21 +79,54 @@
             }
     }
 
+    /**
+     * Emits an event each time a Backup & Restore restoration job is completed. Does not emit an
+     * initial value.
+     */
+    private val backupRestorationEvents: Flow<Unit> =
+        broadcastDispatcher.broadcastFlow(
+            filter = IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED),
+            flags = Context.RECEIVER_NOT_EXPORTED,
+            permission = BackupHelper.PERMISSION_SELF,
+        )
+
     /** IDs of affordances to show, indexed by slot ID, and sorted in descending priority order. */
     val selections: Flow<Map<String, List<String>>> =
-        userId.flatMapLatest {
-            conflatedCallbackFlow {
-                val listener =
-                    SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
-                        trySend(getSelections())
-                    }
-
-                sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
-                send(getSelections())
-
-                awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) }
+        combine(
+                userId,
+                backupRestorationEvents.onStart {
+                    // We emit an initial event to make sure that the combine emits at least once,
+                    // even
+                    // if we never get a Backup & Restore restoration event (which is the most
+                    // common
+                    // case anyway as restoration really only happens on initial device setup).
+                    emit(Unit)
+                }
+            ) { _, _ ->
             }
-        }
+            .flatMapLatest {
+                conflatedCallbackFlow {
+                    // We want to instantiate a new SharedPreferences instance each time either the
+                    // user
+                    // ID changes or we have a backup & restore restoration event. The reason is
+                    // that
+                    // our sharedPrefs instance needs to be replaced with a new one as it depends on
+                    // the
+                    // user ID and when the B&R job completes, the backing file is replaced but the
+                    // existing instance still has a stale in-memory cache.
+                    sharedPrefs = instantiateSharedPrefs()
+
+                    val listener =
+                        SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
+                            trySend(getSelections())
+                        }
+
+                    sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
+                    send(getSelections())
+
+                    awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) }
+                }
+            }
 
     /**
      * Returns a snapshot of the IDs of affordances to show, indexed by slot ID, and sorted in
@@ -144,9 +178,17 @@
         sharedPrefs.edit().putString(key, value).apply()
     }
 
+    private fun instantiateSharedPrefs(): SharedPreferences {
+        return userFileManager.getSharedPreferences(
+            FILE_NAME,
+            Context.MODE_PRIVATE,
+            userTracker.userId,
+        )
+    }
+
     companion object {
         private const val TAG = "KeyguardQuickAffordanceSelectionManager"
-        @VisibleForTesting const val FILE_NAME = "quick_affordance_selections"
+        const val FILE_NAME = "quick_affordance_selections"
         private const val KEY_PREFIX_SLOT = "slot_"
         private const val SLOT_AFFORDANCES_DELIMITER = ":"
         private const val AFFORDANCE_DELIMITER = ","
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt
new file mode 100644
index 0000000..0e865ce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 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.systemui.keyguard.domain.backup
+
+import android.app.backup.SharedPreferencesBackupHelper
+import android.content.Context
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.settings.UserFileManagerImpl
+
+/** Handles backup & restore for keyguard quick affordances. */
+class KeyguardQuickAffordanceBackupHelper(
+    context: Context,
+    userId: Int,
+) :
+    SharedPreferencesBackupHelper(
+        context,
+        if (UserFileManagerImpl.isPrimaryUser(userId)) {
+            KeyguardQuickAffordanceSelectionManager.FILE_NAME
+        } else {
+            UserFileManagerImpl.secondaryUserFile(
+                    context = context,
+                    fileName = KeyguardQuickAffordanceSelectionManager.FILE_NAME,
+                    directoryName = UserFileManagerImpl.SHARED_PREFS,
+                    userId = userId,
+                )
+                .also { UserFileManagerImpl.ensureParentDirExists(it) }
+                .toString()
+        }
+    )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 2d94d76..ee7154f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -34,8 +34,8 @@
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.settings.UserTracker
-import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import dagger.Lazy
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index b252be1..f7a9bc7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -1053,18 +1053,9 @@
                     rootOverlay!!.add(mediaFrame)
                 } else {
                     val targetHost = getHost(newLocation)!!.hostView
-                    // When adding back to the host, let's make sure to reset the bounds.
-                    // Usually adding the view will trigger a layout that does this automatically,
-                    // but we sometimes suppress this.
+                    // This will either do a full layout pass and remeasure, or it will bypass
+                    // that and directly set the mediaFrame's bounds within the premeasured host.
                     targetHost.addView(mediaFrame)
-                    val left = targetHost.paddingLeft
-                    val top = targetHost.paddingTop
-                    mediaFrame.setLeftTopRightBottom(
-                        left,
-                        top,
-                        left + currentBounds.width(),
-                        top + currentBounds.height()
-                    )
 
                     if (mediaFrame.childCount > 0) {
                         val child = mediaFrame.getChildAt(0)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index 4bf3031..4feb984 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -420,7 +420,9 @@
      */
     fun getMeasurementsForState(hostState: MediaHostState): MeasurementOutput? =
         traceSection("MediaViewController#getMeasurementsForState") {
-            val viewState = obtainViewState(hostState) ?: return null
+            // measurements should never factor in the squish fraction
+            val viewState =
+                obtainViewState(hostState.copy().also { it.squishFraction = 1.0f }) ?: return null
             measurement.measuredWidth = viewState.width
             measurement.measuredHeight = viewState.height
             return measurement
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
index 314252b..4c9c99c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.qs.QSUserSwitcherEvent
 import com.android.systemui.qs.tiles.UserDetailView
 import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.user.ui.dialog.DialogShowerImpl
 import javax.inject.Inject
 import javax.inject.Provider
 
@@ -130,19 +131,6 @@
         }
     }
 
-    private class DialogShowerImpl(
-        private val animateFrom: Dialog,
-        private val dialogLaunchAnimator: DialogLaunchAnimator
-    ) : DialogInterface by animateFrom, DialogShower {
-        override fun showDialog(dialog: Dialog, cuj: DialogCuj) {
-            dialogLaunchAnimator.showFromDialog(
-                dialog,
-                animateFrom = animateFrom,
-                cuj
-            )
-        }
-    }
-
     interface DialogShower : DialogInterface {
         fun showDialog(dialog: Dialog, cuj: DialogCuj)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 10d31ea..57b256e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -84,6 +84,8 @@
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.Toast;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
 import android.window.WindowContext;
 
 import androidx.concurrent.futures.CallbackToFutureAdapter;
@@ -279,6 +281,13 @@
     private final ActionIntentExecutor mActionExecutor;
     private final UserManager mUserManager;
 
+    private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
+        if (DEBUG_INPUT) {
+            Log.d(TAG, "Predictive Back callback dispatched");
+        }
+        respondToBack();
+    };
+
     private ScreenshotView mScreenshotView;
     private Bitmap mScreenBitmap;
     private SaveImageInBackgroundTask mSaveInBgTask;
@@ -465,6 +474,10 @@
         }
     }
 
+    private void respondToBack() {
+        dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
+    }
+
     /**
      * Update resources on configuration change. Reinflate for theme/color changes.
      */
@@ -476,6 +489,26 @@
         // Inflate the screenshot layout
         mScreenshotView = (ScreenshotView)
                 LayoutInflater.from(mContext).inflate(R.layout.screenshot, null);
+        mScreenshotView.addOnAttachStateChangeListener(
+                new View.OnAttachStateChangeListener() {
+                    @Override
+                    public void onViewAttachedToWindow(@NonNull View v) {
+                        if (DEBUG_INPUT) {
+                            Log.d(TAG, "Registering Predictive Back callback");
+                        }
+                        mScreenshotView.findOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+                                OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback);
+                    }
+
+                    @Override
+                    public void onViewDetachedFromWindow(@NonNull View v) {
+                        if (DEBUG_INPUT) {
+                            Log.d(TAG, "Unregistering Predictive Back callback");
+                        }
+                        mScreenshotView.findOnBackInvokedDispatcher()
+                                .unregisterOnBackInvokedCallback(mOnBackInvokedCallback);
+                    }
+                });
         mScreenshotView.init(mUiEventLogger, new ScreenshotView.ScreenshotViewCallback() {
             @Override
             public void onUserInteraction() {
@@ -503,7 +536,7 @@
                 if (DEBUG_INPUT) {
                     Log.d(TAG, "onKeyEvent: KeyEvent.KEYCODE_BACK");
                 }
-                dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
+                respondToBack();
                 return true;
             }
             return false;
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
index d450afa..bfba6df 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
@@ -35,12 +35,14 @@
 import javax.inject.Inject
 
 /**
- * Implementation for retrieving file paths for file storage of system and secondary users.
- * Files lie in {File Directory}/UserFileManager/{User Id} for secondary user.
- * For system user, we use the conventional {File Directory}
+ * Implementation for retrieving file paths for file storage of system and secondary users. Files
+ * lie in {File Directory}/UserFileManager/{User Id} for secondary user. For system user, we use the
+ * conventional {File Directory}
  */
 @SysUISingleton
-class UserFileManagerImpl @Inject constructor(
+class UserFileManagerImpl
+@Inject
+constructor(
     // Context of system process and system user.
     private val context: Context,
     val userManager: UserManager,
@@ -49,80 +51,114 @@
 ) : UserFileManager, CoreStartable {
     companion object {
         private const val FILES = "files"
-        @VisibleForTesting internal const val SHARED_PREFS = "shared_prefs"
+        const val SHARED_PREFS = "shared_prefs"
         @VisibleForTesting internal const val ID = "UserFileManager"
-    }
 
-   private val broadcastReceiver = object : BroadcastReceiver() {
+        /** Returns `true` if the given user ID is that for the primary/system user. */
+        fun isPrimaryUser(userId: Int): Boolean {
+            return UserHandle(userId).isSystem
+        }
+
         /**
-         * Listen to Intent.ACTION_USER_REMOVED to clear user data.
+         * Returns a [File] pointing to the correct path for a secondary user ID.
+         *
+         * Note that there is no check for the type of user. This should only be called for
+         * secondary users, never for the system user. For that, make sure to call [isPrimaryUser].
+         *
+         * Note also that there is no guarantee that the parent directory structure for the file
+         * exists on disk. For that, call [ensureParentDirExists].
+         *
+         * @param context The context
+         * @param fileName The name of the file
+         * @param directoryName The name of the directory that would contain the file
+         * @param userId The ID of the user to build a file path for
          */
-        override fun onReceive(context: Context, intent: Intent) {
-            if (intent.action == Intent.ACTION_USER_REMOVED) {
-                clearDeletedUserData()
+        fun secondaryUserFile(
+            context: Context,
+            fileName: String,
+            directoryName: String,
+            userId: Int,
+        ): File {
+            return Environment.buildPath(
+                context.filesDir,
+                ID,
+                userId.toString(),
+                directoryName,
+                fileName,
+            )
+        }
+
+        /**
+         * Checks to see if parent dir of the file exists. If it does not, we create the parent dirs
+         * recursively.
+         */
+        fun ensureParentDirExists(file: File) {
+            val parent = file.parentFile
+            if (!parent.exists()) {
+                if (!parent.mkdirs()) {
+                    Log.e(ID, "Could not create parent directory for file: ${file.absolutePath}")
+                }
             }
         }
     }
 
-    /**
-     * Poll for user-specific directories to delete upon start up.
-     */
+    private val broadcastReceiver =
+        object : BroadcastReceiver() {
+            /** Listen to Intent.ACTION_USER_REMOVED to clear user data. */
+            override fun onReceive(context: Context, intent: Intent) {
+                if (intent.action == Intent.ACTION_USER_REMOVED) {
+                    clearDeletedUserData()
+                }
+            }
+        }
+
+    /** Poll for user-specific directories to delete upon start up. */
     override fun start() {
         clearDeletedUserData()
-        val filter = IntentFilter().apply {
-            addAction(Intent.ACTION_USER_REMOVED)
-        }
+        val filter = IntentFilter().apply { addAction(Intent.ACTION_USER_REMOVED) }
         broadcastDispatcher.registerReceiver(broadcastReceiver, filter, backgroundExecutor)
     }
 
-    /**
-     * Return the file based on current user.
-     */
+    /** Return the file based on current user. */
     override fun getFile(fileName: String, userId: Int): File {
-        return if (UserHandle(userId).isSystem) {
-            Environment.buildPath(
-                context.filesDir,
-                fileName
-            )
+        return if (isPrimaryUser(userId)) {
+            Environment.buildPath(context.filesDir, fileName)
         } else {
-            val secondaryFile = Environment.buildPath(
-                context.filesDir,
-                ID,
-                userId.toString(),
-                FILES,
-                fileName
-            )
+            val secondaryFile =
+                secondaryUserFile(
+                    context = context,
+                    userId = userId,
+                    directoryName = FILES,
+                    fileName = fileName,
+                )
             ensureParentDirExists(secondaryFile)
             secondaryFile
         }
     }
 
-    /**
-     * Get shared preferences from user.
-     */
+    /** Get shared preferences from user. */
     override fun getSharedPreferences(
         fileName: String,
         @Context.PreferencesMode mode: Int,
         userId: Int
     ): SharedPreferences {
-        if (UserHandle(userId).isSystem) {
+        if (isPrimaryUser(userId)) {
             return context.getSharedPreferences(fileName, mode)
         }
-        val secondaryUserDir = Environment.buildPath(
-            context.filesDir,
-            ID,
-            userId.toString(),
-            SHARED_PREFS,
-            fileName
-        )
+
+        val secondaryUserDir =
+            secondaryUserFile(
+                context = context,
+                fileName = fileName,
+                directoryName = SHARED_PREFS,
+                userId = userId,
+            )
 
         ensureParentDirExists(secondaryUserDir)
         return context.getSharedPreferences(secondaryUserDir, mode)
     }
 
-    /**
-     * Remove dirs for deleted users.
-     */
+    /** Remove dirs for deleted users. */
     @VisibleForTesting
     internal fun clearDeletedUserData() {
         backgroundExecutor.execute {
@@ -133,10 +169,11 @@
 
             dirsToDelete.forEach { dir ->
                 try {
-                    val dirToDelete = Environment.buildPath(
-                        file,
-                        dir,
-                    )
+                    val dirToDelete =
+                        Environment.buildPath(
+                            file,
+                            dir,
+                        )
                     dirToDelete.deleteRecursively()
                 } catch (e: Exception) {
                     Log.e(ID, "Deletion failed.", e)
@@ -144,18 +181,4 @@
             }
         }
     }
-
-    /**
-     * Checks to see if parent dir of the file exists. If it does not, we create the parent dirs
-     * recursively.
-     */
-    @VisibleForTesting
-    internal fun ensureParentDirExists(file: File) {
-        val parent = file.parentFile
-        if (!parent.exists()) {
-            if (!parent.mkdirs()) {
-                Log.e(ID, "Could not create parent directory for file: ${file.absolutePath}")
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index aa610bd..de9dcf9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.shade;
 
+import android.view.MotionEvent;
+
+import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -29,31 +32,32 @@
  */
 public interface ShadeController {
 
-    /**
-     * Make our window larger and the panel expanded
-     */
-    void instantExpandNotificationsPanel();
+    /** Make our window larger and the shade expanded */
+    void instantExpandShade();
 
-    /** See {@link #animateCollapsePanels(int, boolean)}. */
-    void animateCollapsePanels();
+    /** Collapse the shade instantly with no animation. */
+    void instantCollapseShade();
 
-    /** See {@link #animateCollapsePanels(int, boolean)}. */
-    void animateCollapsePanels(int flags);
+    /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+    void animateCollapseShade();
+
+    /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+    void animateCollapseShade(int flags);
+
+    /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+    void animateCollapseShadeForced();
+
+    /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+    void animateCollapseShadeDelayed();
 
     /**
      * Collapse the shade animated, showing the bouncer when on {@link StatusBarState#KEYGUARD} or
-     * dismissing {@link CentralSurfaces} when on {@link StatusBarState#SHADE}.
+     * dismissing status bar when on {@link StatusBarState#SHADE}.
      */
-    void animateCollapsePanels(int flags, boolean force);
-
-    /** See {@link #animateCollapsePanels(int, boolean)}. */
-    void animateCollapsePanels(int flags, boolean force, boolean delayed);
-
-    /** See {@link #animateCollapsePanels(int, boolean)}. */
     void animateCollapsePanels(int flags, boolean force, boolean delayed, float speedUpFactor);
 
     /**
-     * If the notifications panel is not fully expanded, collapse it animated.
+     * If the shade is not fully expanded, collapse it animated.
      *
      * @return Seems to always return false
      */
@@ -77,9 +81,7 @@
      */
     void addPostCollapseAction(Runnable action);
 
-    /**
-     * Run all of the runnables added by {@link #addPostCollapseAction}.
-     */
+    /** Run all of the runnables added by {@link #addPostCollapseAction}. */
     void runPostCollapseRunnables();
 
     /**
@@ -87,13 +89,48 @@
      *
      * @return true if the shade was open, else false
      */
-    boolean collapsePanel();
+    boolean collapseShade();
 
     /**
-     * If animate is true, does the same as {@link #collapsePanel()}. Otherwise, instantly collapse
-     * the panel. Post collapse runnables will be executed
+     * If animate is true, does the same as {@link #collapseShade()}. Otherwise, instantly collapse
+     * the shade. Post collapse runnables will be executed
      *
      * @param animate true to animate the collapse, false for instantaneous collapse
      */
-    void collapsePanel(boolean animate);
+    void collapseShade(boolean animate);
+
+    /** Makes shade expanded but not visible. */
+    void makeExpandedInvisible();
+
+    /** Makes shade expanded and visible. */
+    void makeExpandedVisible(boolean force);
+
+    /** Returns whether the shade is expanded and visible. */
+    boolean isExpandedVisible();
+
+    /** Handle status bar touch event. */
+    void onStatusBarTouch(MotionEvent event);
+
+    /** Sets the listener for when the visibility of the shade changes. */
+    void setVisibilityListener(ShadeVisibilityListener listener);
+
+    /** */
+    void setNotificationPresenter(NotificationPresenter presenter);
+
+    /** */
+    void setNotificationShadeWindowViewController(
+            NotificationShadeWindowViewController notificationShadeWindowViewController);
+
+    /** */
+    void setNotificationPanelViewController(
+            NotificationPanelViewController notificationPanelViewController);
+
+    /** Listens for shade visibility changes. */
+    interface ShadeVisibilityListener {
+        /** Called when the visibility of the shade changes. */
+        void visibilityChanged(boolean visible);
+
+        /** Called when shade expanded and visible state changed. */
+        void expandedVisibleChanged(boolean expandedVisible);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index d783293..807e2e6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -16,9 +16,12 @@
 
 package com.android.systemui.shade;
 
+import android.content.ComponentCallbacks2;
 import android.util.Log;
+import android.view.MotionEvent;
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
 
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.dagger.SysUISingleton;
@@ -27,11 +30,12 @@
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowController;
 
 import java.util.ArrayList;
-import java.util.Optional;
 
 import javax.inject.Inject;
 
@@ -39,68 +43,81 @@
 
 /** An implementation of {@link ShadeController}. */
 @SysUISingleton
-public class ShadeControllerImpl implements ShadeController {
+public final class ShadeControllerImpl implements ShadeController {
 
     private static final String TAG = "ShadeControllerImpl";
     private static final boolean SPEW = false;
 
-    private final CommandQueue mCommandQueue;
-    private final StatusBarStateController mStatusBarStateController;
-    protected final NotificationShadeWindowController mNotificationShadeWindowController;
-    private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private final int mDisplayId;
-    protected final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
+
+    private final CommandQueue mCommandQueue;
+    private final KeyguardStateController mKeyguardStateController;
+    private final NotificationShadeWindowController mNotificationShadeWindowController;
+    private final StatusBarStateController mStatusBarStateController;
+    private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    private final StatusBarWindowController mStatusBarWindowController;
+
     private final Lazy<AssistManager> mAssistManagerLazy;
+    private final Lazy<NotificationGutsManager> mGutsManager;
 
     private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
 
+    private boolean mExpandedVisible;
+
+    private NotificationPanelViewController mNotificationPanelViewController;
+    private NotificationPresenter mPresenter;
+    private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
+    private ShadeVisibilityListener mShadeVisibilityListener;
+
     @Inject
     public ShadeControllerImpl(
             CommandQueue commandQueue,
+            KeyguardStateController keyguardStateController,
             StatusBarStateController statusBarStateController,
-            NotificationShadeWindowController notificationShadeWindowController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+            StatusBarWindowController statusBarWindowController,
+            NotificationShadeWindowController notificationShadeWindowController,
             WindowManager windowManager,
-            Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
-            Lazy<AssistManager> assistManagerLazy
+            Lazy<AssistManager> assistManagerLazy,
+            Lazy<NotificationGutsManager> gutsManager
     ) {
         mCommandQueue = commandQueue;
         mStatusBarStateController = statusBarStateController;
+        mStatusBarWindowController = statusBarWindowController;
+        mGutsManager = gutsManager;
         mNotificationShadeWindowController = notificationShadeWindowController;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mDisplayId = windowManager.getDefaultDisplay().getDisplayId();
-        // TODO: Remove circular reference to CentralSurfaces when possible.
-        mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
+        mKeyguardStateController = keyguardStateController;
         mAssistManagerLazy = assistManagerLazy;
     }
 
     @Override
-    public void instantExpandNotificationsPanel() {
+    public void instantExpandShade() {
         // Make our window larger and the panel expanded.
-        getCentralSurfaces().makeExpandedVisible(true /* force */);
-        getNotificationPanelViewController().expand(false /* animate */);
+        makeExpandedVisible(true /* force */);
+        mNotificationPanelViewController.expand(false /* animate */);
         mCommandQueue.recomputeDisableFlags(mDisplayId, false /* animate */);
     }
 
     @Override
-    public void animateCollapsePanels() {
-        animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+    public void animateCollapseShade() {
+        animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
     }
 
     @Override
-    public void animateCollapsePanels(int flags) {
-        animateCollapsePanels(flags, false /* force */, false /* delayed */,
-                1.0f /* speedUpFactor */);
+    public void animateCollapseShade(int flags) {
+        animateCollapsePanels(flags, false, false, 1.0f);
     }
 
     @Override
-    public void animateCollapsePanels(int flags, boolean force) {
-        animateCollapsePanels(flags, force, false /* delayed */, 1.0f /* speedUpFactor */);
+    public void animateCollapseShadeForced() {
+        animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true, false, 1.0f);
     }
 
     @Override
-    public void animateCollapsePanels(int flags, boolean force, boolean delayed) {
-        animateCollapsePanels(flags, force, delayed, 1.0f /* speedUpFactor */);
+    public void animateCollapseShadeDelayed() {
+        animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true, true, 1.0f);
     }
 
     @Override
@@ -111,34 +128,26 @@
             return;
         }
         if (SPEW) {
-            Log.d(TAG, "animateCollapse():"
-                    + " mExpandedVisible=" + getCentralSurfaces().isExpandedVisible()
-                    + " flags=" + flags);
+            Log.d(TAG,
+                    "animateCollapse(): mExpandedVisible=" + mExpandedVisible + "flags=" + flags);
         }
-
-        // TODO(b/62444020): remove when this bug is fixed
-        Log.v(TAG, "NotificationShadeWindow: " + getNotificationShadeWindowView()
-                + " canPanelBeCollapsed(): "
-                + getNotificationPanelViewController().canPanelBeCollapsed());
         if (getNotificationShadeWindowView() != null
-                && getNotificationPanelViewController().canPanelBeCollapsed()
+                && mNotificationPanelViewController.canPanelBeCollapsed()
                 && (flags & CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL) == 0) {
             // release focus immediately to kick off focus change transition
             mNotificationShadeWindowController.setNotificationShadeFocusable(false);
 
-            getCentralSurfaces().getNotificationShadeWindowViewController().cancelExpandHelper();
-            getNotificationPanelViewController()
-                    .collapsePanel(true /* animate */, delayed, speedUpFactor);
+            mNotificationShadeWindowViewController.cancelExpandHelper();
+            mNotificationPanelViewController.collapsePanel(true, delayed, speedUpFactor);
         }
     }
 
-
     @Override
     public boolean closeShadeIfOpen() {
-        if (!getNotificationPanelViewController().isFullyCollapsed()) {
+        if (!mNotificationPanelViewController.isFullyCollapsed()) {
             mCommandQueue.animateCollapsePanels(
                     CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
-            getCentralSurfaces().visibilityChanged(false);
+            notifyVisibilityChanged(false);
             mAssistManagerLazy.get().hideAssist();
         }
         return false;
@@ -146,21 +155,19 @@
 
     @Override
     public boolean isShadeOpen() {
-        NotificationPanelViewController controller =
-                getNotificationPanelViewController();
-        return controller.isExpanding() || controller.isFullyExpanded();
+        return mNotificationPanelViewController.isExpanding()
+                || mNotificationPanelViewController.isFullyExpanded();
     }
 
     @Override
     public void postOnShadeExpanded(Runnable executable) {
-        getNotificationPanelViewController().addOnGlobalLayoutListener(
+        mNotificationPanelViewController.addOnGlobalLayoutListener(
                 new ViewTreeObserver.OnGlobalLayoutListener() {
                     @Override
                     public void onGlobalLayout() {
-                        if (getCentralSurfaces().getNotificationShadeWindowView()
-                                .isVisibleToUser()) {
-                            getNotificationPanelViewController().removeOnGlobalLayoutListener(this);
-                            getNotificationPanelViewController().postToView(executable);
+                        if (getNotificationShadeWindowView().isVisibleToUser()) {
+                            mNotificationPanelViewController.removeOnGlobalLayoutListener(this);
+                            mNotificationPanelViewController.postToView(executable);
                         }
                     }
                 });
@@ -183,12 +190,11 @@
     }
 
     @Override
-    public boolean collapsePanel() {
-        if (!getNotificationPanelViewController().isFullyCollapsed()) {
+    public boolean collapseShade() {
+        if (!mNotificationPanelViewController.isFullyCollapsed()) {
             // close the shade if it was open
-            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
-                    true /* force */, true /* delayed */);
-            getCentralSurfaces().visibilityChanged(false);
+            animateCollapseShadeDelayed();
+            notifyVisibilityChanged(false);
 
             return true;
         } else {
@@ -197,33 +203,131 @@
     }
 
     @Override
-    public void collapsePanel(boolean animate) {
+    public void collapseShade(boolean animate) {
         if (animate) {
-            boolean willCollapse = collapsePanel();
+            boolean willCollapse = collapseShade();
             if (!willCollapse) {
                 runPostCollapseRunnables();
             }
-        } else if (!getPresenter().isPresenterFullyCollapsed()) {
-            getCentralSurfaces().instantCollapseNotificationPanel();
-            getCentralSurfaces().visibilityChanged(false);
+        } else if (!mPresenter.isPresenterFullyCollapsed()) {
+            instantCollapseShade();
+            notifyVisibilityChanged(false);
         } else {
             runPostCollapseRunnables();
         }
     }
 
-    private CentralSurfaces getCentralSurfaces() {
-        return mCentralSurfacesOptionalLazy.get().get();
+    @Override
+    public void onStatusBarTouch(MotionEvent event) {
+        if (event.getAction() == MotionEvent.ACTION_UP) {
+            if (mExpandedVisible) {
+                animateCollapseShade();
+            }
+        }
     }
 
-    private NotificationPresenter getPresenter() {
-        return getCentralSurfaces().getPresenter();
+    @Override
+    public void instantCollapseShade() {
+        mNotificationPanelViewController.instantCollapse();
+        runPostCollapseRunnables();
     }
 
-    protected NotificationShadeWindowView getNotificationShadeWindowView() {
-        return getCentralSurfaces().getNotificationShadeWindowView();
+    @Override
+    public void makeExpandedVisible(boolean force) {
+        if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
+        if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) {
+            return;
+        }
+
+        mExpandedVisible = true;
+
+        // Expand the window to encompass the full screen in anticipation of the drag.
+        // It's only possible to do atomically because the status bar is at the top of the screen!
+        mNotificationShadeWindowController.setPanelVisible(true);
+
+        notifyVisibilityChanged(true);
+        mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */);
+        notifyExpandedVisibleChanged(true);
     }
 
-    private NotificationPanelViewController getNotificationPanelViewController() {
-        return getCentralSurfaces().getNotificationPanelViewController();
+    @Override
+    public void makeExpandedInvisible() {
+        if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible);
+
+        if (!mExpandedVisible || getNotificationShadeWindowView() == null) {
+            return;
+        }
+
+        // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868)
+        mNotificationPanelViewController.collapsePanel(false, false, 1.0f);
+
+        mNotificationPanelViewController.closeQs();
+
+        mExpandedVisible = false;
+        notifyVisibilityChanged(false);
+
+        // Update the visibility of notification shade and status bar window.
+        mNotificationShadeWindowController.setPanelVisible(false);
+        mStatusBarWindowController.setForceStatusBarVisible(false);
+
+        // Close any guts that might be visible
+        mGutsManager.get().closeAndSaveGuts(
+                true /* removeLeavebehind */,
+                true /* force */,
+                true /* removeControls */,
+                -1 /* x */,
+                -1 /* y */,
+                true /* resetMenu */);
+
+        runPostCollapseRunnables();
+        notifyExpandedVisibleChanged(false);
+        mCommandQueue.recomputeDisableFlags(
+                mDisplayId,
+                mNotificationPanelViewController.hideStatusBarIconsWhenExpanded() /* animate */);
+
+        // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
+        // the bouncer appear animation.
+        if (!mKeyguardStateController.isShowing()) {
+            WindowManagerGlobal.getInstance().trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
+        }
+    }
+
+    @Override
+    public boolean isExpandedVisible() {
+        return mExpandedVisible;
+    }
+
+    @Override
+    public void setVisibilityListener(ShadeVisibilityListener listener) {
+        mShadeVisibilityListener = listener;
+    }
+
+    private void notifyVisibilityChanged(boolean visible) {
+        mShadeVisibilityListener.visibilityChanged(visible);
+    }
+
+    private void notifyExpandedVisibleChanged(boolean expandedVisible) {
+        mShadeVisibilityListener.expandedVisibleChanged(expandedVisible);
+    }
+
+    @Override
+    public void setNotificationPresenter(NotificationPresenter presenter) {
+        mPresenter = presenter;
+    }
+
+    @Override
+    public void setNotificationShadeWindowViewController(
+            NotificationShadeWindowViewController controller) {
+        mNotificationShadeWindowViewController = controller;
+    }
+
+    private NotificationShadeWindowView getNotificationShadeWindowView() {
+        return mNotificationShadeWindowViewController.getView();
+    }
+
+    @Override
+    public void setNotificationPanelViewController(
+            NotificationPanelViewController notificationPanelViewController) {
+        mNotificationPanelViewController = notificationPanelViewController;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index 143c697..bd5b8f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -94,7 +94,11 @@
     private val views: Sequence<View>
         get() = if (!this::tl.isInitialized) sequenceOf() else sequenceOf(tl, tr, br, bl)
 
-    private var showingListener: ShowingListener? = null
+    var showingListener: ShowingListener? = null
+        set(value) {
+            field = value
+        }
+        get() = field
 
     init {
         contentInsetsProvider.addCallback(object : StatusBarContentInsetsChangedListener {
@@ -147,10 +151,6 @@
         return uiExecutor
     }
 
-    fun setShowingListener(l: ShowingListener?) {
-        showingListener = l
-    }
-
     @UiThread
     fun setNewRotation(rot: Int) {
         dlog("updateRotation: $rot")
@@ -219,7 +219,7 @@
 
     // Update the gravity and margins of the privacy views
     @UiThread
-    private fun updateRotations(rotation: Int, paddingTop: Int) {
+    open fun updateRotations(rotation: Int, paddingTop: Int) {
         // To keep a view in the corner, its gravity is always the description of its current corner
         // Therefore, just figure out which view is in which corner. This turns out to be something
         // like (myCorner - rot) mod 4, where topLeft = 0, topRight = 1, etc. and portrait = 0, and
@@ -250,7 +250,7 @@
     }
 
     @UiThread
-    private fun setCornerSizes(state: ViewState) {
+    open fun setCornerSizes(state: ViewState) {
         // StatusBarContentInsetsProvider can tell us the location of the privacy indicator dot
         // in every rotation. The only thing we need to check is rtl
         val rtl = state.layoutRtl
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
index 64f87ca..b56bae1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
@@ -54,8 +54,6 @@
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
-import java.util.Collections;
-
 import javax.inject.Inject;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 0ce9656..f21db0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -154,7 +154,7 @@
         // If the user selected Priority and the previous selection was not priority, show a
         // People Tile add request.
         if (mSelectedAction == ACTION_FAVORITE && getPriority() != mSelectedAction) {
-            mShadeController.animateCollapsePanels();
+            mShadeController.animateCollapseShade();
             mPeopleSpaceWidgetManager.requestPinAppWidget(mShortcutInfo, new Bundle());
         }
         mGutsContainer.closeControls(v, /* save= */ true);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 073bd4b..b519aef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -1401,10 +1401,10 @@
             mExpandedHeight = height;
             setIsExpanded(height > 0);
             int minExpansionHeight = getMinExpansionHeight();
-            if (height < minExpansionHeight) {
+            if (height < minExpansionHeight && !mShouldUseSplitNotificationShade) {
                 mClipRect.left = 0;
                 mClipRect.right = getWidth();
-                mClipRect.top = getNotificationsClippingTopBound();
+                mClipRect.top = 0;
                 mClipRect.bottom = (int) height;
                 height = minExpansionHeight;
                 setRequestedClipBounds(mClipRect);
@@ -1466,17 +1466,6 @@
         notifyAppearChangedListeners();
     }
 
-    private int getNotificationsClippingTopBound() {
-        if (isHeadsUpTransition()) {
-            // HUN in split shade can go higher than bottom of NSSL when swiping up so we want
-            // to give it extra clipping margin. Because clipping has rounded corners, we also
-            // need to account for that corner clipping.
-            return -mAmbientState.getStackTopMargin() - mCornerRadius;
-        } else {
-            return 0;
-        }
-    }
-
     private void notifyAppearChangedListeners() {
         float appear;
         float expandAmount;
@@ -4236,7 +4225,7 @@
                 mShadeNeedsToClose = false;
                 postDelayed(
                         () -> {
-                            mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+                            mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
                         },
                         DELAY_BEFORE_SHADE_CLOSE /* delayMillis */);
             }
@@ -5139,6 +5128,7 @@
             println(pw, "intrinsicPadding", mIntrinsicPadding);
             println(pw, "topPadding", mTopPadding);
             println(pw, "bottomPadding", mBottomPadding);
+            mNotificationStackSizeCalculator.dump(pw, args);
         });
         pw.println();
         pw.println("Contents:");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index ae854e2..25f99c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import com.android.systemui.util.Compile
 import com.android.systemui.util.children
+import java.io.PrintWriter
 import javax.inject.Inject
 import kotlin.math.max
 import kotlin.math.min
@@ -53,6 +54,8 @@
     @Main private val resources: Resources
 ) {
 
+    private lateinit var lastComputeHeightLog : String
+
     /**
      * Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow shelf.
      * If there are exactly 1 + mMaxKeyguardNotifications, and they fit in the available space
@@ -114,7 +117,9 @@
         shelfIntrinsicHeight: Float
     ): Int {
         log { "\n" }
-        val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight)
+
+        val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight,
+            /* computeHeight= */ false)
 
         var maxNotifications =
             stackHeightSequence.lastIndexWhile { heightResult ->
@@ -157,18 +162,21 @@
         shelfIntrinsicHeight: Float
     ): Float {
         log { "\n" }
+        lastComputeHeightLog = ""
         val heightPerMaxNotifications =
-            computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight)
+            computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight,
+                    /* computeHeight= */ true)
 
         val (notificationsHeight, shelfHeightWithSpaceBefore) =
             heightPerMaxNotifications.elementAtOrElse(maxNotifications) {
                 heightPerMaxNotifications.last() // Height with all notifications visible.
             }
-        log {
-            "computeHeight(maxNotifications=$maxNotifications," +
+        lastComputeHeightLog += "\ncomputeHeight(maxNotifications=$maxNotifications," +
                 "shelfIntrinsicHeight=$shelfIntrinsicHeight) -> " +
                 "${notificationsHeight + shelfHeightWithSpaceBefore}" +
                 " = ($notificationsHeight + $shelfHeightWithSpaceBefore)"
+        log {
+            lastComputeHeightLog
         }
         return notificationsHeight + shelfHeightWithSpaceBefore
     }
@@ -184,7 +192,8 @@
 
     private fun computeHeightPerNotificationLimit(
         stack: NotificationStackScrollLayout,
-        shelfHeight: Float
+        shelfHeight: Float,
+        computeHeight: Boolean
     ): Sequence<StackHeight> = sequence {
         log { "computeHeightPerNotificationLimit" }
 
@@ -213,9 +222,14 @@
                             currentIndex = firstViewInShelfIndex)
                     spaceBeforeShelf + shelfHeight
                 }
+
+            val currentLog = "computeHeight | i=$i notificationsHeight=$notifications " +
+                "shelfHeightWithSpaceBefore=$shelfWithSpaceBefore"
+            if (computeHeight) {
+                lastComputeHeightLog += "\n" + currentLog
+            }
             log {
-                "i=$i notificationsHeight=$notifications " +
-                    "shelfHeightWithSpaceBefore=$shelfWithSpaceBefore"
+                currentLog
             }
             yield(
                 StackHeight(
@@ -260,6 +274,10 @@
         return size
     }
 
+    fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.println("NotificationStackSizeCalculator lastComputeHeightLog = $lastComputeHeightLog")
+    }
+
     private fun ExpandableView.isShowable(onLockscreen: Boolean): Boolean {
         if (visibility == GONE || hasNoContentHeight()) return false
         if (onLockscreen) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 01ca667..0ec7c62 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -193,8 +193,6 @@
 
     void animateExpandSettingsPanel(@Nullable String subpanel);
 
-    void animateCollapsePanels(int flags, boolean force);
-
     void collapsePanelOnMainThread();
 
     void togglePanel();
@@ -282,8 +280,6 @@
 
     void postAnimateOpenPanels();
 
-    boolean isExpandedVisible();
-
     boolean isPanelExpanded();
 
     void onInputFocusTransfer(boolean start, boolean cancel, float velocity);
@@ -495,12 +491,13 @@
 
     void updateNotificationPanelTouchState();
 
+    /**
+     * TODO(b/257041702) delete this
+     * @deprecated Use ShadeController#makeExpandedVisible
+     */
+    @Deprecated
     void makeExpandedVisible(boolean force);
 
-    void instantCollapseNotificationPanel();
-
-    void visibilityChanged(boolean visible);
-
     int getDisplayId();
 
     int getRotation();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index f3482f4..6b72e96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -209,7 +209,7 @@
     public void animateExpandNotificationsPanel() {
         if (CentralSurfaces.SPEW) {
             Log.d(CentralSurfaces.TAG,
-                    "animateExpand: mExpandedVisible=" + mCentralSurfaces.isExpandedVisible());
+                    "animateExpand: mExpandedVisible=" + mShadeController.isExpandedVisible());
         }
         if (!mCommandQueue.panelsEnabled()) {
             return;
@@ -222,7 +222,7 @@
     public void animateExpandSettingsPanel(@Nullable String subPanel) {
         if (CentralSurfaces.SPEW) {
             Log.d(CentralSurfaces.TAG,
-                    "animateExpand: mExpandedVisible=" + mCentralSurfaces.isExpandedVisible());
+                    "animateExpand: mExpandedVisible=" + mShadeController.isExpandedVisible());
         }
         if (!mCommandQueue.panelsEnabled()) {
             return;
@@ -276,7 +276,7 @@
 
         if ((diff1 & StatusBarManager.DISABLE_EXPAND) != 0) {
             if ((state1 & StatusBarManager.DISABLE_EXPAND) != 0) {
-                mShadeController.animateCollapsePanels();
+                mShadeController.animateCollapseShade();
             }
         }
 
@@ -293,7 +293,7 @@
         if ((diff2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
             mCentralSurfaces.updateQsExpansionEnabled();
             if ((state2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
-                mShadeController.animateCollapsePanels();
+                mShadeController.animateCollapseShade();
             }
         }
 
@@ -550,7 +550,7 @@
     @Override
     public void togglePanel() {
         if (mCentralSurfaces.isPanelExpanded()) {
-            mShadeController.animateCollapsePanels();
+            mShadeController.animateCollapseShade();
         } else {
             animateExpandNotificationsPanel();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 32ea8d6..d988772 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -58,7 +58,6 @@
 import android.app.WallpaperManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
-import android.content.ComponentCallbacks2;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -406,12 +405,6 @@
 
     /** */
     @Override
-    public void animateCollapsePanels(int flags, boolean force) {
-        mCommandQueueCallbacks.animateCollapsePanels(flags, force);
-    }
-
-    /** */
-    @Override
     public void togglePanel() {
         mCommandQueueCallbacks.togglePanel();
     }
@@ -493,8 +486,6 @@
 
     private View mReportRejectedTouch;
 
-    private boolean mExpandedVisible;
-
     private final NotificationGutsManager mGutsManager;
     private final NotificationLogger mNotificationLogger;
     private final ShadeExpansionStateManager mShadeExpansionStateManager;
@@ -893,6 +884,8 @@
         updateDisplaySize();
         mStatusBarHideIconsForBouncerManager.setDisplayId(mDisplayId);
 
+        initShadeVisibilityListener();
+
         // start old BaseStatusBar.start().
         mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
         mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
@@ -1083,6 +1076,25 @@
                                 requestTopUi, componentTag))));
     }
 
+    @VisibleForTesting
+    void initShadeVisibilityListener() {
+        mShadeController.setVisibilityListener(new ShadeController.ShadeVisibilityListener() {
+            @Override
+            public void visibilityChanged(boolean visible) {
+                onShadeVisibilityChanged(visible);
+            }
+
+            @Override
+            public void expandedVisibleChanged(boolean expandedVisible) {
+                if (expandedVisible) {
+                    setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true);
+                } else {
+                    onExpandedInvisible();
+                }
+            }
+        });
+    }
+
     private void onFoldedStateChanged(boolean isFolded, boolean willGoToSleep) {
         Trace.beginSection("CentralSurfaces#onFoldedStateChanged");
         onFoldedStateChangedInternal(isFolded, willGoToSleep);
@@ -1228,7 +1240,7 @@
 
         mNotificationPanelViewController.initDependencies(
                 this,
-                this::makeExpandedInvisible,
+                mShadeController::makeExpandedInvisible,
                 mNotificationShelfController);
 
         BackDropView backdrop = mNotificationShadeWindowView.findViewById(R.id.backdrop);
@@ -1431,6 +1443,7 @@
         mRemoteInputManager.addControllerCallback(mNotificationShadeWindowController);
         mStackScrollerController.setNotificationActivityStarter(mNotificationActivityStarter);
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
+        mShadeController.setNotificationPresenter(mPresenter);
         mNotificationsController.initialize(
                 this,
                 mPresenter,
@@ -1480,11 +1493,7 @@
         return (v, event) -> {
             mAutoHideController.checkUserAutoHide(event);
             mRemoteInputManager.checkRemoteInputOutside(event);
-            if (event.getAction() == MotionEvent.ACTION_UP) {
-                if (mExpandedVisible) {
-                    mShadeController.animateCollapsePanels();
-                }
-            }
+            mShadeController.onStatusBarTouch(event);
             return mNotificationShadeWindowView.onTouchEvent(event);
         };
     }
@@ -1506,6 +1515,9 @@
         mNotificationShadeWindowViewController.setupExpandedStatusBar();
         mNotificationPanelViewController =
                 mCentralSurfacesComponent.getNotificationPanelViewController();
+        mShadeController.setNotificationPanelViewController(mNotificationPanelViewController);
+        mShadeController.setNotificationShadeWindowViewController(
+                mNotificationShadeWindowViewController);
         mCentralSurfacesComponent.getLockIconViewController().init();
         mStackScrollerController =
                 mCentralSurfacesComponent.getNotificationStackScrollLayoutController();
@@ -1827,7 +1839,7 @@
                 && isLaunchForActivity) {
             onClosingFinished();
         } else {
-            mShadeController.collapsePanel(true /* animate */);
+            mShadeController.collapseShade(true /* animate */);
         }
     }
 
@@ -1838,7 +1850,7 @@
             onClosingFinished();
         }
         if (launchIsFullScreen) {
-            instantCollapseNotificationPanel();
+            mShadeController.instantCollapseShade();
         }
     }
 
@@ -1928,33 +1940,13 @@
     }
 
     @Override
-    public void makeExpandedVisible(boolean force) {
-        if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
-        if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) {
-            return;
-        }
-
-        mExpandedVisible = true;
-
-        // Expand the window to encompass the full screen in anticipation of the drag.
-        // This is only possible to do atomically because the status bar is at the top of the screen!
-        mNotificationShadeWindowController.setPanelVisible(true);
-
-        visibilityChanged(true);
-        mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */);
-        setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true);
-    }
-
-    @Override
     public void postAnimateCollapsePanels() {
-        mMainExecutor.execute(mShadeController::animateCollapsePanels);
+        mMainExecutor.execute(mShadeController::animateCollapseShade);
     }
 
     @Override
     public void postAnimateForceCollapsePanels() {
-        mMainExecutor.execute(
-                () -> mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE,
-                true /* force */));
+        mMainExecutor.execute(mShadeController::animateCollapseShadeForced);
     }
 
     @Override
@@ -1963,11 +1955,6 @@
     }
 
     @Override
-    public boolean isExpandedVisible() {
-        return mExpandedVisible;
-    }
-
-    @Override
     public boolean isPanelExpanded() {
         return mPanelExpanded;
     }
@@ -1996,46 +1983,13 @@
         }
     }
 
-    void makeExpandedInvisible() {
-        if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible);
-
-        if (!mExpandedVisible || mNotificationShadeWindowView == null) {
-            return;
-        }
-
-        // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868)
-        mNotificationPanelViewController.collapsePanel(/*animate=*/ false, false /* delayed*/,
-                1.0f /* speedUpFactor */);
-
-        mNotificationPanelViewController.closeQs();
-
-        mExpandedVisible = false;
-        visibilityChanged(false);
-
-        // Update the visibility of notification shade and status bar window.
-        mNotificationShadeWindowController.setPanelVisible(false);
-        mStatusBarWindowController.setForceStatusBarVisible(false);
-
-        // Close any guts that might be visible
-        mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
-                true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
-
-        mShadeController.runPostCollapseRunnables();
+    private void onExpandedInvisible() {
         setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
         if (!mNotificationActivityStarter.isCollapsingToShowActivityOverLockscreen()) {
             showBouncerOrLockScreenIfKeyguard();
         } else if (DEBUG) {
             Log.d(TAG, "Not showing bouncer due to activity showing over lockscreen");
         }
-        mCommandQueue.recomputeDisableFlags(
-                mDisplayId,
-                mNotificationPanelViewController.hideStatusBarIconsWhenExpanded() /* animate */);
-
-        // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
-        // the bouncer appear animation.
-        if (!mKeyguardStateController.isShowing()) {
-            WindowManagerGlobal.getInstance().trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
-        }
     }
 
     /** Called when a touch event occurred on {@link PhoneStatusBarView}. */
@@ -2072,7 +2026,8 @@
             final boolean upOrCancel =
                     event.getAction() == MotionEvent.ACTION_UP ||
                     event.getAction() == MotionEvent.ACTION_CANCEL;
-            setInteracting(StatusBarManager.WINDOW_STATUS_BAR, !upOrCancel || mExpandedVisible);
+            setInteracting(StatusBarManager.WINDOW_STATUS_BAR,
+                    !upOrCancel || mShadeController.isExpandedVisible());
         }
     }
 
@@ -2221,7 +2176,7 @@
         IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
         synchronized (mQueueLock) {
             pw.println("Current Status Bar state:");
-            pw.println("  mExpandedVisible=" + mExpandedVisible);
+            pw.println("  mExpandedVisible=" + mShadeController.isExpandedVisible());
             pw.println("  mDisplayMetrics=" + mDisplayMetrics);
             pw.println("  mStackScroller: " + CentralSurfaces.viewInfo(mStackScroller));
             pw.println("  mStackScroller: " + CentralSurfaces.viewInfo(mStackScroller)
@@ -2536,10 +2491,8 @@
                     }
                 }
                 if (dismissShade) {
-                    if (mExpandedVisible && !mBouncerShowing) {
-                        mShadeController.animateCollapsePanels(
-                                CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
-                                true /* force */, true /* delayed*/);
+                    if (mShadeController.isExpandedVisible() && !mBouncerShowing) {
+                        mShadeController.animateCollapseShadeDelayed();
                     } else {
                         // Do it after DismissAction has been processed to conserve the needed
                         // ordering.
@@ -2581,7 +2534,7 @@
                             flags |= CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL;
                         }
                     }
-                    mShadeController.animateCollapsePanels(flags);
+                    mShadeController.animateCollapseShade(flags);
                 }
             } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
                 if (mNotificationShadeWindowController != null) {
@@ -2696,10 +2649,9 @@
                 com.android.systemui.R.dimen.physical_power_button_center_screen_location_y));
     }
 
-    // Visibility reporting
     protected void handleVisibleToUserChanged(boolean visibleToUser) {
         if (visibleToUser) {
-            handleVisibleToUserChangedImpl(visibleToUser);
+            onVisibleToUser();
             mNotificationLogger.startNotificationLogging();
 
             if (!mIsBackCallbackRegistered) {
@@ -2716,7 +2668,7 @@
             }
         } else {
             mNotificationLogger.stopNotificationLogging();
-            handleVisibleToUserChangedImpl(visibleToUser);
+            onInvisibleToUser();
 
             if (mIsBackCallbackRegistered) {
                 ViewRootImpl viewRootImpl = getViewRootImpl();
@@ -2736,41 +2688,38 @@
         }
     }
 
-    // Visibility reporting
-    void handleVisibleToUserChangedImpl(boolean visibleToUser) {
-        if (visibleToUser) {
-            /* The LEDs are turned off when the notification panel is shown, even just a little bit.
-             * See also CentralSurfaces.setPanelExpanded for another place where we attempt to do
-             * this.
-             */
-            boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
-            boolean clearNotificationEffects =
-                    !mPresenter.isPresenterFullyCollapsed() &&
-                            (mState == StatusBarState.SHADE
-                                    || mState == StatusBarState.SHADE_LOCKED);
-            int notificationLoad = mNotificationsController.getActiveNotificationsCount();
-            if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) {
-                notificationLoad = 1;
-            }
-            final int finalNotificationLoad = notificationLoad;
-            mUiBgExecutor.execute(() -> {
-                try {
-                    mBarService.onPanelRevealed(clearNotificationEffects,
-                            finalNotificationLoad);
-                } catch (RemoteException ex) {
-                    // Won't fail unless the world has ended.
-                }
-            });
-        } else {
-            mUiBgExecutor.execute(() -> {
-                try {
-                    mBarService.onPanelHidden();
-                } catch (RemoteException ex) {
-                    // Won't fail unless the world has ended.
-                }
-            });
+    void onVisibleToUser() {
+        /* The LEDs are turned off when the notification panel is shown, even just a little bit.
+         * See also CentralSurfaces.setPanelExpanded for another place where we attempt to do
+         * this.
+         */
+        boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
+        boolean clearNotificationEffects =
+                !mPresenter.isPresenterFullyCollapsed() && (mState == StatusBarState.SHADE
+                        || mState == StatusBarState.SHADE_LOCKED);
+        int notificationLoad = mNotificationsController.getActiveNotificationsCount();
+        if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) {
+            notificationLoad = 1;
         }
+        final int finalNotificationLoad = notificationLoad;
+        mUiBgExecutor.execute(() -> {
+            try {
+                mBarService.onPanelRevealed(clearNotificationEffects,
+                        finalNotificationLoad);
+            } catch (RemoteException ex) {
+                // Won't fail unless the world has ended.
+            }
+        });
+    }
 
+    void onInvisibleToUser() {
+        mUiBgExecutor.execute(() -> {
+            try {
+                mBarService.onPanelHidden();
+            } catch (RemoteException ex) {
+                // Won't fail unless the world has ended.
+            }
+        });
     }
 
     private void logStateToEventlog() {
@@ -2948,7 +2897,7 @@
     private void updatePanelExpansionForKeyguard() {
         if (mState == StatusBarState.KEYGUARD && mBiometricUnlockController.getMode()
                 != BiometricUnlockController.MODE_WAKE_AND_UNLOCK && !mBouncerShowing) {
-            mShadeController.instantExpandNotificationsPanel();
+            mShadeController.instantExpandShade();
         }
     }
 
@@ -3067,7 +3016,7 @@
             // too heavy for the CPU and GPU on any device.
             mNavigationBarController.disableAnimationsDuringHide(mDisplayId, delay);
         } else if (!mNotificationPanelViewController.isCollapsing()) {
-            instantCollapseNotificationPanel();
+            mShadeController.instantCollapseShade();
         }
 
         // Keyguard state has changed, but QS is not listening anymore. Make sure to update the tile
@@ -3225,8 +3174,7 @@
     @Override
     public boolean onMenuPressed() {
         if (shouldUnlockOnMenuPressed()) {
-            mShadeController.animateCollapsePanels(
-                    CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */);
+            mShadeController.animateCollapseShadeForced();
             return true;
         }
         return false;
@@ -3271,7 +3219,7 @@
         if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED
                 && !isBouncerShowingOverDream()) {
             if (mNotificationPanelViewController.canPanelBeCollapsed()) {
-                mShadeController.animateCollapsePanels();
+                mShadeController.animateCollapseShade();
             }
             return true;
         }
@@ -3281,8 +3229,7 @@
     @Override
     public boolean onSpacePressed() {
         if (mDeviceInteractive && mState != StatusBarState.SHADE) {
-            mShadeController.animateCollapsePanels(
-                    CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */);
+            mShadeController.animateCollapseShadeForced();
             return true;
         }
         return false;
@@ -3322,12 +3269,6 @@
         }
     }
 
-    @Override
-    public void instantCollapseNotificationPanel() {
-        mNotificationPanelViewController.instantCollapse();
-        mShadeController.runPostCollapseRunnables();
-    }
-
     /**
      * Collapse the panel directly if we are on the main thread, post the collapsing on the main
      * thread if we are not.
@@ -3335,9 +3276,9 @@
     @Override
     public void collapsePanelOnMainThread() {
         if (Looper.getMainLooper().isCurrentThread()) {
-            mShadeController.collapsePanel();
+            mShadeController.collapseShade();
         } else {
-            mContext.getMainExecutor().execute(mShadeController::collapsePanel);
+            mContext.getMainExecutor().execute(mShadeController::collapseShade);
         }
     }
 
@@ -3477,7 +3418,7 @@
             mNotificationShadeWindowViewController.cancelCurrentTouch();
         }
         if (mPanelExpanded && mState == StatusBarState.SHADE) {
-            mShadeController.animateCollapsePanels();
+            mShadeController.animateCollapseShade();
         }
     }
 
@@ -3540,7 +3481,7 @@
             // The unlocked screen off and fold to aod animations might use our LightRevealScrim -
             // we need to be expanded for it to be visible.
             if (mDozeParameters.shouldShowLightRevealScrim()) {
-                makeExpandedVisible(true);
+                mShadeController.makeExpandedVisible(true);
             }
 
             DejankUtils.stopDetectingBlockingIpcs(tag);
@@ -3569,7 +3510,7 @@
                 // If we are waking up during the screen off animation, we should undo making the
                 // expanded visible (we did that so the LightRevealScrim would be visible).
                 if (mScreenOffAnimationController.shouldHideLightRevealScrimOnWakeUp()) {
-                    makeExpandedInvisible();
+                    mShadeController.makeExpandedInvisible();
                 }
 
             });
@@ -3624,6 +3565,12 @@
         mNotificationIconAreaController.setAnimationsEnabled(!disabled);
     }
 
+    //TODO(b/257041702) delete
+    @Override
+    public void makeExpandedVisible(boolean force) {
+        mShadeController.makeExpandedVisible(force);
+    }
+
     final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
         @Override
         public void onScreenTurningOn(Runnable onDrawn) {
@@ -3904,8 +3851,7 @@
                 Settings.Secure.putInt(mContext.getContentResolver(),
                         Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
                 if (BANNER_ACTION_SETUP.equals(action)) {
-                    mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
-                            true /* force */);
+                    mShadeController.animateCollapseShadeForced();
                     mContext.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_REDACTION)
                             .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
 
@@ -3967,7 +3913,7 @@
                     action.run();
                 }).start();
 
-                return collapsePanel ? mShadeController.collapsePanel() : willAnimateOnKeyguard;
+                return collapsePanel ? mShadeController.collapseShade() : willAnimateOnKeyguard;
             }
 
             @Override
@@ -4062,8 +4008,7 @@
         mMainExecutor.execute(runnable);
     }
 
-    @Override
-    public void visibilityChanged(boolean visible) {
+    private void onShadeVisibilityChanged(boolean visible) {
         if (mVisible != visible) {
             mVisible = visible;
             if (!visible) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 44ad604..f9d316b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -469,6 +469,9 @@
             // Don't expand to the bouncer. Instead transition back to the lock screen (see
             // CentralSurfaces#showBouncerOrLockScreenIfKeyguard)
             return;
+        } else if (mKeyguardStateController.isOccluded()
+                && !mDreamOverlayStateController.isOverlayActive()) {
+            return;
         } else if (needsFullscreenBouncer()) {
             if (mPrimaryBouncer != null) {
                 mPrimaryBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index b6ae4a0..05bf860 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -260,11 +260,11 @@
 
         if (showOverLockscreen) {
             mShadeController.addPostCollapseAction(runnable);
-            mShadeController.collapsePanel(true /* animate */);
+            mShadeController.collapseShade(true /* animate */);
         } else if (mKeyguardStateController.isShowing()
                 && mCentralSurfaces.isOccluded()) {
             mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
-            mShadeController.collapsePanel();
+            mShadeController.collapseShade();
         } else {
             runnable.run();
         }
@@ -406,7 +406,7 @@
 
     private void expandBubbleStack(NotificationEntry entry) {
         mBubblesManagerOptional.get().expandStackAndSelectBubble(entry);
-        mShadeController.collapsePanel();
+        mShadeController.collapseShade();
     }
 
     private void startNotificationIntent(
@@ -593,9 +593,9 @@
 
     private void collapseOnMainThread() {
         if (Looper.getMainLooper().isCurrentThread()) {
-            mShadeController.collapsePanel();
+            mShadeController.collapseShade();
         } else {
-            mMainThreadHandler.post(mShadeController::collapsePanel);
+            mMainThreadHandler.post(mShadeController::collapseShade);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 8a49850..7fe01825 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -180,7 +180,7 @@
                 }
             };
             mShadeController.postOnShadeExpanded(clickPendingViewRunnable);
-            mShadeController.instantExpandNotificationsPanel();
+            mShadeController.instantExpandShade();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index c5b697c..4c59874 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -493,7 +493,7 @@
 
     fun showUserSwitcher(context: Context, expandable: Expandable) {
         if (!featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
-            showDialog(ShowDialogRequestModel.ShowUserSwitcherDialog)
+            showDialog(ShowDialogRequestModel.ShowUserSwitcherDialog(expandable))
             return
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
index 85c2964..14cc3e7 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
@@ -18,11 +18,13 @@
 package com.android.systemui.user.domain.model
 
 import android.os.UserHandle
+import com.android.systemui.animation.Expandable
 import com.android.systemui.qs.user.UserSwitchDialogController
 
 /** Encapsulates a request to show a dialog. */
 sealed class ShowDialogRequestModel(
     open val dialogShower: UserSwitchDialogController.DialogShower? = null,
+    open val expandable: Expandable? = null,
 ) {
     data class ShowAddUserDialog(
         val userHandle: UserHandle,
@@ -45,5 +47,7 @@
     ) : ShowDialogRequestModel(dialogShower)
 
     /** Show the user switcher dialog */
-    object ShowUserSwitcherDialog : ShowDialogRequestModel()
+    data class ShowUserSwitcherDialog(
+        override val expandable: Expandable?,
+    ) : ShowDialogRequestModel()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/DialogShowerImpl.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/DialogShowerImpl.kt
new file mode 100644
index 0000000..3fe2a7b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/DialogShowerImpl.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 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.systemui.user.ui.dialog
+
+import android.app.Dialog
+import android.content.DialogInterface
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower
+
+/** Extracted from [UserSwitchDialogController] */
+class DialogShowerImpl(
+    private val animateFrom: Dialog,
+    private val dialogLaunchAnimator: DialogLaunchAnimator,
+) : DialogInterface by animateFrom, DialogShower {
+    override fun showDialog(dialog: Dialog, cuj: DialogCuj) {
+        dialogLaunchAnimator.showFromDialog(dialog, animateFrom = animateFrom, cuj)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt
index ed25898..b8ae257 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt
@@ -60,6 +60,7 @@
         setView(gridFrame)
 
         adapter.linkToViewGroup(gridFrame.findViewById(R.id.grid))
+        adapter.injectDialogShower(DialogShowerImpl(this, dialogLaunchAnimator))
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
index 4141054..d451230 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
@@ -133,7 +133,10 @@
                     }
                 currentDialog = dialog
 
-                if (request.dialogShower != null && dialogCuj != null) {
+                val controller = request.expandable?.dialogLaunchController(dialogCuj)
+                if (controller != null) {
+                    dialogLaunchAnimator.get().show(dialog, controller)
+                } else if (request.dialogShower != null && dialogCuj != null) {
                     request.dialogShower?.showDialog(dialog, dialogCuj)
                 } else {
                     dialog.show()
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index ad97ef4..5df4a5b 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -29,6 +29,7 @@
 import android.hardware.display.DisplayManager.DisplayListener;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.Looper;
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -102,6 +103,15 @@
     }
 
     @Override
+    public Looper onProvideEngineLooper() {
+        // Receive messages on mWorker thread instead of SystemUI's main handler.
+        // All other wallpapers have their own process, and they can receive messages on their own
+        // main handler without any delay. But since ImageWallpaper lives in SystemUI, performance
+        // of the image wallpaper could be negatively affected when SystemUI's main handler is busy.
+        return mWorker != null ? mWorker.getLooper() : super.onProvideEngineLooper();
+    }
+
+    @Override
     public void onCreate() {
         super.onCreate();
         mWorker = new HandlerThread(TAG);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index a4384d5..7033ccd 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -549,7 +549,7 @@
         } catch (RemoteException e) {
             Log.e(TAG, e.getMessage());
         }
-        mShadeController.collapsePanel(true);
+        mShadeController.collapseShade(true);
         if (entry.getRow() != null) {
             entry.getRow().updateBubbleButton();
         }
@@ -597,7 +597,7 @@
         }
 
         if (shouldBubble) {
-            mShadeController.collapsePanel(true);
+            mShadeController.collapseShade(true);
             if (entry.getRow() != null) {
                 entry.getRow().updateBubbleButton();
             }
diff --git a/packages/SystemUI/tests/robolectric/config/robolectric.properties b/packages/SystemUI/tests/robolectric/config/robolectric.properties
new file mode 100644
index 0000000..2a75bd9
--- /dev/null
+++ b/packages/SystemUI/tests/robolectric/config/robolectric.properties
@@ -0,0 +1,16 @@
+#
+# Copyright (C) 2022 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.
+#
+sdk=NEWEST_SDK
\ No newline at end of file
diff --git a/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiResourceLoadingTest.java b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiResourceLoadingTest.java
new file mode 100644
index 0000000..188dff2
--- /dev/null
+++ b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiResourceLoadingTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 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.systemui.robotests;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+import static com.google.common.truth.Truth.assertThat;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SysuiResourceLoadingTest extends SysuiRoboBase {
+    @Test
+    public void testResources() {
+        assertThat(getContext().getString(com.android.systemui.R.string.app_label))
+                .isEqualTo("System UI");
+        assertThat(getContext().getString(com.android.systemui.tests.R.string.test_content))
+                .isNotEmpty();
+    }
+}
diff --git a/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiRoboBase.java b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiRoboBase.java
new file mode 100644
index 0000000..d9686bb
--- /dev/null
+++ b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiRoboBase.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 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.systemui.robotests;
+
+import android.content.Context;
+
+import androidx.test.InstrumentationRegistry;
+
+public class SysuiRoboBase {
+    public Context getContext() {
+        return InstrumentationRegistry.getContext();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
index cedde58..32c5b3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
@@ -36,8 +36,8 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
-import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.FakeSharedPreferences
 import com.android.systemui.util.mockito.mock
@@ -89,6 +89,7 @@
                             .thenReturn(FakeSharedPreferences())
                     },
                 userTracker = userTracker,
+                broadcastDispatcher = fakeBroadcastDispatcher,
             )
         val quickAffordanceRepository =
             KeyguardQuickAffordanceRepository(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
index 623becf..7205f30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
@@ -37,25 +37,29 @@
 
     @Mock private lateinit var cameraGestureHelper: CameraGestureHelper
     @Mock private lateinit var context: Context
+
     private lateinit var underTest: CameraQuickAffordanceConfig
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        underTest = CameraQuickAffordanceConfig(
+
+        underTest =
+            CameraQuickAffordanceConfig(
                 context,
-                cameraGestureHelper,
-        )
+            ) {
+                cameraGestureHelper
+            }
     }
 
     @Test
     fun `affordance triggered -- camera launch called`() {
-        //when
+        // When
         val result = underTest.onTriggered(null)
 
-        //then
+        // Then
         verify(cameraGestureHelper)
-                .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
+            .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
         assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
index 8ef921e..552b8cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
@@ -89,6 +89,7 @@
                             .thenReturn(FakeSharedPreferences())
                     },
                 userTracker = FakeUserTracker(),
+                broadcastDispatcher = fakeBroadcastDispatcher,
             )
         settings = FakeSettings()
         settings.putInt(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
index d8ee9f1..6a2376b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
@@ -17,6 +17,7 @@
 
 package com.android.systemui.keyguard.data.quickaffordance
 
+import android.content.Intent
 import android.content.SharedPreferences
 import android.content.pm.UserInfo
 import androidx.test.filters.SmallTest
@@ -27,10 +28,15 @@
 import com.android.systemui.util.FakeSharedPreferences
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.toList
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.resetMain
 import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -38,8 +44,12 @@
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(JUnit4::class)
 class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() {
@@ -60,15 +70,23 @@
             sharedPrefs.getOrPut(userId) { FakeSharedPreferences() }
         }
         userTracker = FakeUserTracker()
+        val dispatcher = UnconfinedTestDispatcher()
+        Dispatchers.setMain(dispatcher)
 
         underTest =
             KeyguardQuickAffordanceSelectionManager(
                 context = context,
                 userFileManager = userFileManager,
                 userTracker = userTracker,
+                broadcastDispatcher = fakeBroadcastDispatcher,
             )
     }
 
+    @After
+    fun tearDown() {
+        Dispatchers.resetMain()
+    }
+
     @Test
     fun setSelections() = runTest {
         overrideResource(R.array.config_keyguardQuickAffordanceDefaults, arrayOf<String>())
@@ -318,6 +336,22 @@
         job.cancel()
     }
 
+    @Test
+    fun `responds to backup and restore by reloading the selections from disk`() = runTest {
+        overrideResource(R.array.config_keyguardQuickAffordanceDefaults, arrayOf<String>())
+        val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>()
+        val job =
+            launch(UnconfinedTestDispatcher()) {
+                underTest.selections.toList(affordanceIdsBySlotId)
+            }
+        clearInvocations(userFileManager)
+
+        fakeBroadcastDispatcher.registeredReceivers.firstOrNull()?.onReceive(context, Intent())
+
+        verify(userFileManager, atLeastOnce()).getSharedPreferences(anyString(), anyInt(), anyInt())
+        job.cancel()
+    }
+
     private fun assertSelections(
         observed: Map<String, List<String>>?,
         expected: Map<String, List<String>>,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
index 5c75417..652fae9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -76,6 +76,7 @@
                             .thenReturn(FakeSharedPreferences())
                     },
                 userTracker = FakeUserTracker(),
+                broadcastDispatcher = fakeBroadcastDispatcher,
             )
 
         underTest =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index c2650ec..ba7c40b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -252,6 +252,7 @@
                             .thenReturn(FakeSharedPreferences())
                     },
                 userTracker = userTracker,
+                broadcastDispatcher = fakeBroadcastDispatcher,
             )
         val quickAffordanceRepository =
             KeyguardQuickAffordanceRepository(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index b790306..8d0c4ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -113,6 +113,7 @@
                             .thenReturn(FakeSharedPreferences())
                     },
                 userTracker = userTracker,
+                broadcastDispatcher = fakeBroadcastDispatcher,
             )
         val quickAffordanceRepository =
             KeyguardQuickAffordanceRepository(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 8b166bd..32849cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -136,6 +136,7 @@
                             .thenReturn(FakeSharedPreferences())
                     },
                 userTracker = userTracker,
+                broadcastDispatcher = fakeBroadcastDispatcher,
             )
         val quickAffordanceRepository =
             KeyguardQuickAffordanceRepository(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
index 6d9b01e..020a866 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
@@ -50,24 +50,20 @@
 
     lateinit var userFileManager: UserFileManagerImpl
     lateinit var backgroundExecutor: FakeExecutor
-    @Mock
-    lateinit var userManager: UserManager
-    @Mock
-    lateinit var broadcastDispatcher: BroadcastDispatcher
+    @Mock lateinit var userManager: UserManager
+    @Mock lateinit var broadcastDispatcher: BroadcastDispatcher
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         backgroundExecutor = FakeExecutor(FakeSystemClock())
-        userFileManager = UserFileManagerImpl(context, userManager,
-            broadcastDispatcher, backgroundExecutor)
+        userFileManager =
+            UserFileManagerImpl(context, userManager, broadcastDispatcher, backgroundExecutor)
     }
 
     @After
     fun end() {
-        val dir = Environment.buildPath(
-            context.filesDir,
-            UserFileManagerImpl.ID)
+        val dir = Environment.buildPath(context.filesDir, UserFileManagerImpl.ID)
         dir.deleteRecursively()
     }
 
@@ -82,13 +78,14 @@
     @Test
     fun testGetSharedPreferences() {
         val secondarySharedPref = userFileManager.getSharedPreferences(TEST_FILE_NAME, 0, 11)
-        val secondaryUserDir = Environment.buildPath(
-            context.filesDir,
-            UserFileManagerImpl.ID,
-            "11",
-            UserFileManagerImpl.SHARED_PREFS,
-            TEST_FILE_NAME
-        )
+        val secondaryUserDir =
+            Environment.buildPath(
+                context.filesDir,
+                UserFileManagerImpl.ID,
+                "11",
+                UserFileManagerImpl.SHARED_PREFS,
+                TEST_FILE_NAME
+            )
 
         assertThat(secondarySharedPref).isNotNull()
         assertThat(secondaryUserDir.exists())
@@ -101,32 +98,35 @@
         val userFileManager = spy(userFileManager)
         userFileManager.start()
         verify(userFileManager).clearDeletedUserData()
-        verify(broadcastDispatcher).registerReceiver(any(BroadcastReceiver::class.java),
-            any(IntentFilter::class.java),
-            any(Executor::class.java), isNull(), eq(Context.RECEIVER_EXPORTED), isNull())
+        verify(broadcastDispatcher)
+            .registerReceiver(
+                any(BroadcastReceiver::class.java),
+                any(IntentFilter::class.java),
+                any(Executor::class.java),
+                isNull(),
+                eq(Context.RECEIVER_EXPORTED),
+                isNull()
+            )
     }
 
     @Test
     fun testClearDeletedUserData() {
-        val dir = Environment.buildPath(
-            context.filesDir,
-            UserFileManagerImpl.ID,
-            "11",
-            "files"
-        )
+        val dir = Environment.buildPath(context.filesDir, UserFileManagerImpl.ID, "11", "files")
         dir.mkdirs()
-        val file = Environment.buildPath(
-            context.filesDir,
-            UserFileManagerImpl.ID,
-            "11",
-            "files",
-            TEST_FILE_NAME
-        )
-        val secondaryUserDir = Environment.buildPath(
-            context.filesDir,
-            UserFileManagerImpl.ID,
-            "11",
-        )
+        val file =
+            Environment.buildPath(
+                context.filesDir,
+                UserFileManagerImpl.ID,
+                "11",
+                "files",
+                TEST_FILE_NAME
+            )
+        val secondaryUserDir =
+            Environment.buildPath(
+                context.filesDir,
+                UserFileManagerImpl.ID,
+                "11",
+            )
         file.createNewFile()
         assertThat(secondaryUserDir.exists()).isTrue()
         assertThat(file.exists()).isTrue()
@@ -139,15 +139,16 @@
 
     @Test
     fun testEnsureParentDirExists() {
-        val file = Environment.buildPath(
-            context.filesDir,
-            UserFileManagerImpl.ID,
-            "11",
-            "files",
-            TEST_FILE_NAME
-        )
+        val file =
+            Environment.buildPath(
+                context.filesDir,
+                UserFileManagerImpl.ID,
+                "11",
+                "files",
+                TEST_FILE_NAME
+            )
         assertThat(file.parentFile.exists()).isFalse()
-        userFileManager.ensureParentDirExists(file)
+        UserFileManagerImpl.ensureParentDirExists(file)
         assertThat(file.parentFile.exists()).isTrue()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index e1346ea..0000c32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -118,13 +118,6 @@
     }
 
     @Test
-    public void testCollapsePanels() {
-        mCommandQueue.animateCollapsePanels();
-        waitForIdleSync();
-        verify(mCallbacks).animateCollapsePanels(eq(0), eq(false));
-    }
-
-    @Test
     public void testExpandSettings() {
         String panel = "some_panel";
         mCommandQueue.animateExpandSettingsPanel(panel);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
index ed2afe7..915924f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
@@ -41,7 +41,6 @@
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
-import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerFake;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index d5bfe1f..c17c5b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -136,7 +136,7 @@
                 StatusBarManager.DISABLE2_NOTIFICATION_SHADE, false);
 
         verify(mCentralSurfaces).updateQsExpansionEnabled();
-        verify(mShadeController).animateCollapsePanels();
+        verify(mShadeController).animateCollapseShade();
 
         // Trying to open it does nothing.
         mSbcqCallbacks.animateExpandNotificationsPanel();
@@ -154,7 +154,7 @@
         mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE,
                 StatusBarManager.DISABLE2_NONE, false);
         verify(mCentralSurfaces).updateQsExpansionEnabled();
-        verify(mShadeController, never()).animateCollapsePanels();
+        verify(mShadeController, never()).animateCollapseShade();
 
         // Can now be opened.
         mSbcqCallbacks.animateExpandNotificationsPanel();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 013e727..ed84e42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -392,10 +392,21 @@
             return null;
         }).when(mNotificationShadeWindowController).batchApplyWindowLayoutParams(any());
 
-        mShadeController = spy(new ShadeControllerImpl(mCommandQueue,
-                mStatusBarStateController, mNotificationShadeWindowController,
-                mStatusBarKeyguardViewManager, mContext.getSystemService(WindowManager.class),
-                () -> Optional.of(mCentralSurfaces), () -> mAssistManager));
+        mShadeController = spy(new ShadeControllerImpl(
+                mCommandQueue,
+                mKeyguardStateController,
+                mStatusBarStateController,
+                mStatusBarKeyguardViewManager,
+                mStatusBarWindowController,
+                mNotificationShadeWindowController,
+                mContext.getSystemService(WindowManager.class),
+                () -> mAssistManager,
+                () -> mNotificationGutsManager
+        ));
+        mShadeController.setNotificationPanelViewController(mNotificationPanelViewController);
+        mShadeController.setNotificationShadeWindowViewController(
+                mNotificationShadeWindowViewController);
+        mShadeController.setNotificationPresenter(mNotificationPresenter);
 
         when(mOperatorNameViewControllerFactory.create(any()))
                 .thenReturn(mOperatorNameViewController);
@@ -492,6 +503,7 @@
                 return mViewRootImpl;
             }
         };
+        mCentralSurfaces.initShadeVisibilityListener();
         when(mViewRootImpl.getOnBackInvokedDispatcher())
                 .thenReturn(mOnBackInvokedDispatcher);
         when(mKeyguardViewMediator.registerCentralSurfaces(
@@ -807,7 +819,7 @@
 
         when(mNotificationPanelViewController.canPanelBeCollapsed()).thenReturn(true);
         mOnBackInvokedCallback.getValue().onBackInvoked();
-        verify(mShadeController).animateCollapsePanels();
+        verify(mShadeController).animateCollapseShade();
     }
 
     @Test
@@ -1030,7 +1042,7 @@
     }
 
     @Test
-    public void collapseShade_callsAnimateCollapsePanels_whenExpanded() {
+    public void collapseShade_callsanimateCollapseShade_whenExpanded() {
         // GIVEN the shade is expanded
         mCentralSurfaces.onShadeExpansionFullyChanged(true);
         mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
@@ -1038,12 +1050,12 @@
         // WHEN collapseShade is called
         mCentralSurfaces.collapseShade();
 
-        // VERIFY that animateCollapsePanels is called
-        verify(mShadeController).animateCollapsePanels();
+        // VERIFY that animateCollapseShade is called
+        verify(mShadeController).animateCollapseShade();
     }
 
     @Test
-    public void collapseShade_doesNotCallAnimateCollapsePanels_whenCollapsed() {
+    public void collapseShade_doesNotCallanimateCollapseShade_whenCollapsed() {
         // GIVEN the shade is collapsed
         mCentralSurfaces.onShadeExpansionFullyChanged(false);
         mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
@@ -1051,12 +1063,12 @@
         // WHEN collapseShade is called
         mCentralSurfaces.collapseShade();
 
-        // VERIFY that animateCollapsePanels is NOT called
-        verify(mShadeController, never()).animateCollapsePanels();
+        // VERIFY that animateCollapseShade is NOT called
+        verify(mShadeController, never()).animateCollapseShade();
     }
 
     @Test
-    public void collapseShadeForBugReport_callsAnimateCollapsePanels_whenFlagDisabled() {
+    public void collapseShadeForBugReport_callsanimateCollapseShade_whenFlagDisabled() {
         // GIVEN the shade is expanded & flag enabled
         mCentralSurfaces.onShadeExpansionFullyChanged(true);
         mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
@@ -1065,12 +1077,12 @@
         // WHEN collapseShadeForBugreport is called
         mCentralSurfaces.collapseShadeForBugreport();
 
-        // VERIFY that animateCollapsePanels is called
-        verify(mShadeController).animateCollapsePanels();
+        // VERIFY that animateCollapseShade is called
+        verify(mShadeController).animateCollapseShade();
     }
 
     @Test
-    public void collapseShadeForBugReport_doesNotCallAnimateCollapsePanels_whenFlagEnabled() {
+    public void collapseShadeForBugReport_doesNotCallanimateCollapseShade_whenFlagEnabled() {
         // GIVEN the shade is expanded & flag enabled
         mCentralSurfaces.onShadeExpansionFullyChanged(true);
         mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
@@ -1079,8 +1091,8 @@
         // WHEN collapseShadeForBugreport is called
         mCentralSurfaces.collapseShadeForBugreport();
 
-        // VERIFY that animateCollapsePanels is called
-        verify(mShadeController, never()).animateCollapsePanels();
+        // VERIFY that animateCollapseShade is called
+        verify(mShadeController, never()).animateCollapseShade();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index bf5186b..e467d93 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -307,6 +307,17 @@
     }
 
     @Test
+    public void onPanelExpansionChanged_neverTranslatesBouncerWhenOccluded() {
+        when(mKeyguardStateController.isOccluded()).thenReturn(true);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+                expansionEvent(
+                        /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+                        /* expanded= */ true,
+                        /* tracking= */ false));
+        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+    }
+
+    @Test
     public void onPanelExpansionChanged_neverTranslatesBouncerWhenShowBouncer() {
         // Since KeyguardBouncer.EXPANSION_VISIBLE = 0 panel expansion, if the unlock is dismissing
         // the bouncer, there may be an onPanelExpansionChanged(0) call to collapse the panel
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index ce54d78..cae414a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -263,7 +263,7 @@
         while (!runnables.isEmpty()) runnables.remove(0).run();
 
         // Then
-        verify(mShadeController, atLeastOnce()).collapsePanel();
+        verify(mShadeController, atLeastOnce()).collapseShade();
 
         verify(mActivityLaunchAnimator).startPendingIntentWithAnimation(any(),
                 eq(false) /* animate */, any(), any());
@@ -296,7 +296,7 @@
         verify(mBubblesManager).expandStackAndSelectBubble(eq(mBubbleNotificationRow.getEntry()));
 
         // This is called regardless, and simply short circuits when there is nothing to do.
-        verify(mShadeController, atLeastOnce()).collapsePanel();
+        verify(mShadeController, atLeastOnce()).collapseShade();
 
         verify(mAssistManager).hideAssist();
 
@@ -329,7 +329,7 @@
         // Then
         verify(mBubblesManager).expandStackAndSelectBubble(eq(mBubbleNotificationRow.getEntry()));
 
-        verify(mShadeController, atLeastOnce()).collapsePanel();
+        verify(mShadeController, atLeastOnce()).collapseShade();
 
         verify(mAssistManager).hideAssist();
 
@@ -357,7 +357,7 @@
         // Then
         verify(mBubblesManager).expandStackAndSelectBubble(mBubbleNotificationRow.getEntry());
 
-        verify(mShadeController, atLeastOnce()).collapsePanel();
+        verify(mShadeController, atLeastOnce()).collapseShade();
 
         verify(mAssistManager).hideAssist();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index 50d239d..ec555c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -813,7 +813,8 @@
             val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
 
             // Dialog is shown.
-            assertThat(dialogRequest).isEqualTo(ShowDialogRequestModel.ShowUserSwitcherDialog)
+            assertThat(dialogRequest)
+                .isEqualTo(ShowDialogRequestModel.ShowUserSwitcherDialog(expandable))
 
             underTest.onDialogShown()
             assertThat(dialogRequest).isNull()
diff --git a/services/api/current.txt b/services/api/current.txt
index 42ae10e..834ed2f 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -76,6 +76,7 @@
     method @Nullable public String getSdkLibraryName();
     method @NonNull public java.util.List<com.android.server.pm.pkg.AndroidPackageSplit> getSplits();
     method @Nullable public String getStaticSharedLibraryName();
+    method @NonNull public java.util.UUID getStorageUuid();
     method public int getTargetSdkVersion();
     method public boolean isDebuggable();
     method public boolean isIsolatedSplitLoading();
@@ -99,6 +100,7 @@
     method public int getAppId();
     method @NonNull public String getPackageName();
     method @Nullable public String getPrimaryCpuAbi();
+    method @Nullable public String getSeInfo();
     method @Nullable public String getSecondaryCpuAbi();
     method @NonNull public com.android.server.pm.pkg.PackageUserState getStateForUser(@NonNull android.os.UserHandle);
     method @NonNull public java.util.List<com.android.server.pm.pkg.SharedLibrary> getUsesLibraries();
diff --git a/services/companion/java/com/android/server/companion/virtual/SensorController.java b/services/companion/java/com/android/server/companion/virtual/SensorController.java
new file mode 100644
index 0000000..ec7e993
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/virtual/SensorController.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2022 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.companion.virtual;
+
+import android.annotation.NonNull;
+import android.companion.virtual.sensor.IVirtualSensorStateChangeCallback;
+import android.companion.virtual.sensor.VirtualSensorConfig;
+import android.companion.virtual.sensor.VirtualSensorEvent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.sensors.SensorManagerInternal;
+
+import java.io.PrintWriter;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Objects;
+
+/** Controls virtual sensors, including their lifecycle and sensor event dispatch. */
+public class SensorController {
+
+    private static final String TAG = "SensorController";
+
+    private final Object mLock;
+    private final int mVirtualDeviceId;
+    @GuardedBy("mLock")
+    private final Map<IBinder, SensorDescriptor> mSensorDescriptors = new ArrayMap<>();
+
+    private final SensorManagerInternal mSensorManagerInternal;
+
+    public SensorController(@NonNull Object lock, int virtualDeviceId) {
+        mLock = lock;
+        mVirtualDeviceId = virtualDeviceId;
+        mSensorManagerInternal = LocalServices.getService(SensorManagerInternal.class);
+    }
+
+    void close() {
+        synchronized (mLock) {
+            final Iterator<Map.Entry<IBinder, SensorDescriptor>> iterator =
+                    mSensorDescriptors.entrySet().iterator();
+            if (iterator.hasNext()) {
+                final Map.Entry<IBinder, SensorDescriptor> entry = iterator.next();
+                final IBinder token = entry.getKey();
+                final SensorDescriptor sensorDescriptor = entry.getValue();
+                iterator.remove();
+                closeSensorDescriptorLocked(token, sensorDescriptor);
+            }
+        }
+    }
+
+    void createSensor(@NonNull IBinder deviceToken, @NonNull VirtualSensorConfig config) {
+        Objects.requireNonNull(deviceToken);
+        Objects.requireNonNull(config);
+        try {
+            createSensorInternal(deviceToken, config);
+        } catch (SensorCreationException e) {
+            throw new RuntimeException(
+                    "Failed to create virtual sensor '" + config.getName() + "'.", e);
+        }
+    }
+
+    private void createSensorInternal(IBinder deviceToken, VirtualSensorConfig config)
+            throws SensorCreationException {
+        final SensorManagerInternal.RuntimeSensorStateChangeCallback runtimeSensorCallback =
+                (enabled, samplingPeriodMicros, batchReportLatencyMicros) -> {
+                    IVirtualSensorStateChangeCallback callback = config.getStateChangeCallback();
+                    if (callback != null) {
+                        try {
+                            callback.onStateChanged(
+                                    enabled, samplingPeriodMicros, batchReportLatencyMicros);
+                        } catch (RemoteException e) {
+                            throw new RuntimeException("Failed to call sensor callback.", e);
+                        }
+                    }
+                };
+
+        final int handle = mSensorManagerInternal.createRuntimeSensor(mVirtualDeviceId,
+                config.getType(), config.getName(),
+                config.getVendor() == null ? "" : config.getVendor(),
+                runtimeSensorCallback);
+        if (handle <= 0) {
+            throw new SensorCreationException("Received an invalid virtual sensor handle.");
+        }
+
+        // The handle is valid from here, so ensure that all failures clean it up.
+        final BinderDeathRecipient binderDeathRecipient;
+        try {
+            binderDeathRecipient = new BinderDeathRecipient(deviceToken);
+            deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
+        } catch (RemoteException e) {
+            mSensorManagerInternal.removeRuntimeSensor(handle);
+            throw new SensorCreationException("Client died before sensor could be created.", e);
+        }
+
+        synchronized (mLock) {
+            SensorDescriptor sensorDescriptor = new SensorDescriptor(
+                    handle, config.getType(), config.getName(), binderDeathRecipient);
+            mSensorDescriptors.put(deviceToken, sensorDescriptor);
+        }
+    }
+
+    boolean sendSensorEvent(@NonNull IBinder token, @NonNull VirtualSensorEvent event) {
+        Objects.requireNonNull(token);
+        Objects.requireNonNull(event);
+        synchronized (mLock) {
+            final SensorDescriptor sensorDescriptor = mSensorDescriptors.get(token);
+            if (sensorDescriptor == null) {
+                throw new IllegalArgumentException("Could not send sensor event for given token");
+            }
+            return mSensorManagerInternal.sendSensorEvent(
+                    sensorDescriptor.getHandle(), sensorDescriptor.getType(),
+                    event.getTimestampNanos(), event.getValues());
+        }
+    }
+
+    void unregisterSensor(@NonNull IBinder token) {
+        Objects.requireNonNull(token);
+        synchronized (mLock) {
+            final SensorDescriptor sensorDescriptor = mSensorDescriptors.remove(token);
+            if (sensorDescriptor == null) {
+                throw new IllegalArgumentException("Could not unregister sensor for given token");
+            }
+            closeSensorDescriptorLocked(token, sensorDescriptor);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void closeSensorDescriptorLocked(IBinder token, SensorDescriptor sensorDescriptor) {
+        token.unlinkToDeath(sensorDescriptor.getDeathRecipient(), /* flags= */ 0);
+        final int handle = sensorDescriptor.getHandle();
+        mSensorManagerInternal.removeRuntimeSensor(handle);
+    }
+
+
+    void dump(@NonNull PrintWriter fout) {
+        fout.println("    SensorController: ");
+        synchronized (mLock) {
+            fout.println("      Active descriptors: ");
+            for (SensorDescriptor sensorDescriptor : mSensorDescriptors.values()) {
+                fout.println("        handle: " + sensorDescriptor.getHandle());
+                fout.println("          type: " + sensorDescriptor.getType());
+                fout.println("          name: " + sensorDescriptor.getName());
+            }
+        }
+    }
+
+    @VisibleForTesting
+    void addSensorForTesting(IBinder deviceToken, int handle, int type, String name) {
+        synchronized (mLock) {
+            mSensorDescriptors.put(deviceToken,
+                    new SensorDescriptor(handle, type, name, () -> {}));
+        }
+    }
+
+    @VisibleForTesting
+    Map<IBinder, SensorDescriptor> getSensorDescriptors() {
+        synchronized (mLock) {
+            return mSensorDescriptors;
+        }
+    }
+
+    @VisibleForTesting
+    static final class SensorDescriptor {
+
+        private final int mHandle;
+        private final IBinder.DeathRecipient mDeathRecipient;
+        private final int mType;
+        private final String mName;
+
+        SensorDescriptor(int handle, int type, String name, IBinder.DeathRecipient deathRecipient) {
+            mHandle = handle;
+            mDeathRecipient = deathRecipient;
+            mType = type;
+            mName = name;
+        }
+        public int getHandle() {
+            return mHandle;
+        }
+        public int getType() {
+            return mType;
+        }
+        public String getName() {
+            return mName;
+        }
+        public IBinder.DeathRecipient getDeathRecipient() {
+            return mDeathRecipient;
+        }
+    }
+
+    private final class BinderDeathRecipient implements IBinder.DeathRecipient {
+        private final IBinder mDeviceToken;
+
+        BinderDeathRecipient(IBinder deviceToken) {
+            mDeviceToken = deviceToken;
+        }
+
+        @Override
+        public void binderDied() {
+            // All callers are expected to call {@link VirtualDevice#unregisterSensor} before
+            // quitting, which removes this death recipient. If this is invoked, the remote end
+            // died, or they disposed of the object without properly unregistering.
+            Slog.e(TAG, "Virtual sensor controller binder died");
+            unregisterSensor(mDeviceToken);
+        }
+    }
+
+    /** An internal exception that is thrown to indicate an error when opening a virtual sensor. */
+    private static class SensorCreationException extends Exception {
+        SensorCreationException(String message) {
+            super(message);
+        }
+        SensorCreationException(String message, Exception cause) {
+            super(message, cause);
+        }
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 7e82918..828f302 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -38,6 +38,8 @@
 import android.companion.virtual.VirtualDeviceParams;
 import android.companion.virtual.audio.IAudioConfigChangedCallback;
 import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.companion.virtual.sensor.VirtualSensorConfig;
+import android.companion.virtual.sensor.VirtualSensorEvent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -76,6 +78,7 @@
 import java.io.PrintWriter;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.function.Consumer;
 
@@ -98,6 +101,7 @@
     private final int mOwnerUid;
     private final int mDeviceId;
     private final InputController mInputController;
+    private final SensorController mSensorController;
     private VirtualAudioController mVirtualAudioController;
     @VisibleForTesting
     final Set<Integer> mVirtualDisplayIds = new ArraySet<>();
@@ -160,6 +164,7 @@
                 ownerUid,
                 deviceId,
                 /* inputController= */ null,
+                /* sensorController= */ null,
                 listener,
                 pendingTrampolineCallback,
                 activityListener,
@@ -175,6 +180,7 @@
             int ownerUid,
             int deviceId,
             InputController inputController,
+            SensorController sensorController,
             OnDeviceCloseListener listener,
             PendingTrampolineCallback pendingTrampolineCallback,
             IVirtualDeviceActivityListener activityListener,
@@ -198,6 +204,11 @@
         } else {
             mInputController = inputController;
         }
+        if (sensorController == null) {
+            mSensorController = new SensorController(mVirtualDeviceLock, mDeviceId);
+        } else {
+            mSensorController = sensorController;
+        }
         mListener = listener;
         try {
             token.linkToDeath(this, 0);
@@ -319,11 +330,12 @@
         mListener.onClose(mAssociationInfo.getId());
         mAppToken.unlinkToDeath(this, 0);
 
-        final long token = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             mInputController.close();
+            mSensorController.close();
         } finally {
-            Binder.restoreCallingIdentity(token);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -403,12 +415,12 @@
                                 + "this virtual device");
             }
         }
-        final long token = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             mInputController.createDpad(deviceName, vendorId, productId, deviceToken,
                     displayId);
         } finally {
-            Binder.restoreCallingIdentity(token);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -430,12 +442,12 @@
                                 + "this virtual device");
             }
         }
-        final long token = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             mInputController.createKeyboard(deviceName, vendorId, productId, deviceToken,
                     displayId);
         } finally {
-            Binder.restoreCallingIdentity(token);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -457,11 +469,11 @@
                                 + "virtual device");
             }
         }
-        final long token = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             mInputController.createMouse(deviceName, vendorId, productId, deviceToken, displayId);
         } finally {
-            Binder.restoreCallingIdentity(token);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -491,12 +503,12 @@
                             + screenSize);
         }
 
-        final long token = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             mInputController.createTouchscreen(deviceName, vendorId, productId,
                     deviceToken, displayId, screenSize);
         } finally {
-            Binder.restoreCallingIdentity(token);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -506,92 +518,92 @@
                 android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
                 "Permission required to unregister this input device");
 
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             mInputController.unregisterInputDevice(token);
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
     @Override // Binder call
     public int getInputDeviceId(IBinder token) {
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.getInputDeviceId(token);
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
 
     @Override // Binder call
     public boolean sendDpadKeyEvent(IBinder token, VirtualKeyEvent event) {
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendDpadKeyEvent(token, event);
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
     @Override // Binder call
     public boolean sendKeyEvent(IBinder token, VirtualKeyEvent event) {
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendKeyEvent(token, event);
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
     @Override // Binder call
     public boolean sendButtonEvent(IBinder token, VirtualMouseButtonEvent event) {
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendButtonEvent(token, event);
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
     @Override // Binder call
     public boolean sendTouchEvent(IBinder token, VirtualTouchEvent event) {
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendTouchEvent(token, event);
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
     @Override // Binder call
     public boolean sendRelativeEvent(IBinder token, VirtualMouseRelativeEvent event) {
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendRelativeEvent(token, event);
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
     @Override // Binder call
     public boolean sendScrollEvent(IBinder token, VirtualMouseScrollEvent event) {
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendScrollEvent(token, event);
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
     @Override // Binder call
     public PointF getCursorPosition(IBinder token) {
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.getCursorPosition(token);
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -601,7 +613,7 @@
                 android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
                 "Permission required to unregister this input device");
 
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mVirtualDeviceLock) {
                 mDefaultShowPointerIcon = showPointerIcon;
@@ -610,7 +622,50 @@
                 }
             }
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override // Binder call
+    public void createVirtualSensor(
+            @NonNull IBinder deviceToken,
+            @NonNull VirtualSensorConfig config) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+                "Permission required to create a virtual sensor");
+        Objects.requireNonNull(config);
+        Objects.requireNonNull(deviceToken);
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mSensorController.createSensor(deviceToken, config);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override // Binder call
+    public void unregisterSensor(@NonNull IBinder token) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+                "Permission required to unregister a virtual sensor");
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mSensorController.unregisterSensor(token);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override // Binder call
+    public boolean sendSensorEvent(@NonNull IBinder token, @NonNull VirtualSensorEvent event) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+                "Permission required to send a virtual sensor event");
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            return mSensorController.sendSensorEvent(token, event);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -627,6 +682,7 @@
             fout.println("    mDefaultShowPointerIcon: " + mDefaultShowPointerIcon);
         }
         mInputController.dump(fout);
+        mSensorController.dump(fout);
     }
 
     GenericWindowPolicyController createWindowPolicyController(
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index fe26700..2b62f69 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -32,6 +32,7 @@
 import android.companion.virtual.VirtualDeviceManager;
 import android.companion.virtual.VirtualDeviceParams;
 import android.content.Context;
+import android.content.Intent;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.display.IVirtualDisplayCallback;
 import android.hardware.display.VirtualDisplayConfig;
@@ -280,7 +281,22 @@
                             @Override
                             public void onClose(int associationId) {
                                 synchronized (mVirtualDeviceManagerLock) {
-                                    mVirtualDevices.remove(associationId);
+                                    VirtualDeviceImpl removedDevice =
+                                            mVirtualDevices.removeReturnOld(associationId);
+                                    if (removedDevice != null) {
+                                        Intent i = new Intent(
+                                                VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED);
+                                        i.putExtra(
+                                                VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID,
+                                                removedDevice.getDeviceId());
+                                        i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                                        final long identity = Binder.clearCallingIdentity();
+                                        try {
+                                            getContext().sendBroadcastAsUser(i, UserHandle.ALL);
+                                        } finally {
+                                            Binder.restoreCallingIdentity(identity);
+                                        }
+                                    }
                                     mAppsOnVirtualDevices.remove(associationId);
                                     if (cameraAccessController != null) {
                                         cameraAccessController.stopObservingIfNeeded();
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 68880bd..6cd7ce8 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -310,27 +310,27 @@
                 Bundle packageMeasurement = measurePackage(packageInfo);
                 results.add(packageMeasurement);
 
-                if (record) {
+                if (record && (mba_status == MBA_STATUS_UPDATED_PRELOAD)) {
                     // compute digests of signing info
                     String[] signerDigestHexStrings = computePackageSignerSha256Digests(
                             packageInfo.signingInfo);
 
                     // now we should have all the bits for the atom
-                    /*  TODO: Uncomment and test after merging new atom definition.
+                    byte[] cDigest = packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST);
                     FrameworkStatsLog.write(FrameworkStatsLog.MOBILE_BUNDLED_APP_INFO_GATHERED,
                             packageInfo.packageName,
                             packageInfo.getLongVersionCode(),
-                            HexEncoding.encodeToString(packageMeasurement.getByteArray(
-                                    BUNDLE_CONTENT_DIGEST), false),
+                            (cDigest != null) ? HexEncoding.encodeToString(
+                                    packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST),
+                                    false) : null,
                             packageMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM),
                             signerDigestHexStrings, // signer_cert_digest
-                            mba_status,                 // mba_status
+                            mba_status,             // mba_status
                             null,                   // initiator
                             null,                   // initiator_signer_digest
                             null,                   // installer
                             null                    // originator
                     );
-                     */
                 }
             }
             if (DEBUG) {
@@ -377,12 +377,13 @@
                     }
 
                     // we should now have all the info needed for the atom
-                    /*  TODO: Uncomment and test after merging new atom definition.
+                    byte[] cDigest = packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST);
                     FrameworkStatsLog.write(FrameworkStatsLog.MOBILE_BUNDLED_APP_INFO_GATHERED,
                             packageInfo.packageName,
                             packageInfo.getLongVersionCode(),
-                            HexEncoding.encodeToString(packageMeasurement.getByteArray(
-                                    BUNDLE_CONTENT_DIGEST), false),
+                            (cDigest != null) ? HexEncoding.encodeToString(
+                                    packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST),
+                                    false) : null,
                             packageMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM),
                             signerDigestHexStrings,
                             MBA_STATUS_NEW_INSTALL,   // mba_status
@@ -391,7 +392,6 @@
                             installer,
                             originator
                     );
-                     */
                 }
             }
             if (DEBUG) {
@@ -489,24 +489,10 @@
                         Integer algorithmId = entry.getKey();
                         byte[] contentDigest = entry.getValue();
 
-                        // TODO(b/259348134): consider refactoring the following to a helper method
-                        switch (algorithmId) {
-                            case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256:
-                                pw.print("CHUNKED_SHA256:");
-                                break;
-                            case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512:
-                                pw.print("CHUNKED_SHA512:");
-                                break;
-                            case ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
-                                pw.print("VERITY_CHUNKED_SHA256:");
-                                break;
-                            case ApkSigningBlockUtils.CONTENT_DIGEST_SHA256:
-                                pw.print("SHA256:");
-                                break;
-                            default:
-                                pw.print("UNKNOWN_ALGO_ID(" + algorithmId + "):");
-                        }
+                        pw.print(translateContentDigestAlgorithmIdToString(algorithmId));
+                        pw.print(":");
                         pw.print(HexEncoding.encodeToString(contentDigest, false));
+                        pw.print("\n");
                     }
                 }
 
@@ -533,31 +519,13 @@
                             pw.println("ERROR: Failed to compute package content digest for "
                                     + origPackageFilepath);
                         } else {
-                            // TODO(b/259348134): consider refactoring this to a helper method
                             for (Map.Entry<Integer, byte[]> entry : contentDigests.entrySet()) {
                                 Integer algorithmId = entry.getKey();
                                 byte[] contentDigest = entry.getValue();
-                                pw.print("|--> Pre-installed package content digest algorithm: ");
-                                switch (algorithmId) {
-                                    case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256:
-                                        pw.print("CHUNKED_SHA256");
-                                        break;
-                                    case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512:
-                                        pw.print("CHUNKED_SHA512");
-                                        break;
-                                    case ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
-                                        pw.print("VERITY_CHUNKED_SHA256");
-                                        break;
-                                    case ApkSigningBlockUtils.CONTENT_DIGEST_SHA256:
-                                        pw.print("SHA256");
-                                        break;
-                                    default:
-                                        pw.print("UNKNOWN");
-                                }
-                                pw.print("\n");
-                                pw.print("|--> Pre-installed package content digest: ");
-                                pw.print(HexEncoding.encodeToString(contentDigest, false));
-                                pw.print("\n");
+                                pw.println("|--> Pre-installed package content digest: "
+                                        + HexEncoding.encodeToString(contentDigest, false));
+                                pw.println("|--> Pre-installed package content digest algorithm: "
+                                        + translateContentDigestAlgorithmIdToString(algorithmId));
                             }
                         }
                     }
@@ -739,7 +707,6 @@
                         pw.print(packageName + ","
                                 + packageInfo.getLongVersionCode() + ",");
                         printPackageMeasurements(packageInfo, pw);
-                        pw.print("\n");
 
                         if (verbose) {
                             ModuleInfo moduleInfo;
@@ -802,7 +769,6 @@
                             pw.print(packageInfo.packageName + ",");
                             pw.print(packageInfo.getLongVersionCode() + ",");
                             printPackageMeasurements(packageInfo, pw);
-                            pw.print("\n");
 
                             if (verbose) {
                                 printModuleDetails(module, pw);
@@ -858,7 +824,6 @@
                         pw.print(packageInfo.packageName + ",");
                         pw.print(packageInfo.getLongVersionCode() + ",");
                         printPackageMeasurements(packageInfo, pw);
-                        pw.print("\n");
 
                         if (verbose) {
                             printAppDetails(packageInfo, printLibraries, pw);
@@ -1079,6 +1044,21 @@
         FrameworkStatsLog.write(FrameworkStatsLog.VBMETA_DIGEST_REPORTED, mVbmetaDigest);
     }
 
+    private String translateContentDigestAlgorithmIdToString(int algorithmId) {
+        switch (algorithmId) {
+            case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256:
+                return "CHUNKED_SHA256";
+            case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512:
+                return "CHUNKED_SHA512";
+            case ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
+                return "VERITY_CHUNKED_SHA256";
+            case ApkSigningBlockUtils.CONTENT_DIGEST_SHA256:
+                return "SHA256";
+            default:
+                return "UNKNOWN_ALGO_ID(" + algorithmId + ")";
+        }
+    }
+
     @NonNull
     private List<PackageInfo> getCurrentInstalledApexs() {
         List<PackageInfo> results = new ArrayList<>();
diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java
index 540ed4c..3487613 100644
--- a/services/core/java/com/android/server/DockObserver.java
+++ b/services/core/java/com/android/server/DockObserver.java
@@ -19,6 +19,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.database.ContentObserver;
 import android.media.AudioManager;
 import android.media.Ringtone;
 import android.media.RingtoneManager;
@@ -73,6 +74,7 @@
     private final boolean mAllowTheaterModeWakeFromDock;
 
     private final List<ExtconStateConfig> mExtconStateConfigs;
+    private DeviceProvisionedObserver mDeviceProvisionedObserver;
 
     static final class ExtconStateProvider {
         private final Map<String, String> mState;
@@ -110,7 +112,7 @@
                 Slog.w(TAG, "No state file found at: " + stateFilePath);
                 return new ExtconStateProvider(new HashMap<>());
             } catch (Exception e) {
-                Slog.e(TAG, "" , e);
+                Slog.e(TAG, "", e);
                 return new ExtconStateProvider(new HashMap<>());
             }
         }
@@ -136,7 +138,7 @@
 
     private static List<ExtconStateConfig> loadExtconStateConfigs(Context context) {
         String[] rows = context.getResources().getStringArray(
-            com.android.internal.R.array.config_dockExtconStateMapping);
+                com.android.internal.R.array.config_dockExtconStateMapping);
         try {
             ArrayList<ExtconStateConfig> configs = new ArrayList<>();
             for (String row : rows) {
@@ -167,6 +169,7 @@
                 com.android.internal.R.bool.config_allowTheaterModeWakeFromDock);
         mKeepDreamingWhenUndocking = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_keepDreamingWhenUndocking);
+        mDeviceProvisionedObserver = new DeviceProvisionedObserver(mHandler);
 
         mExtconStateConfigs = loadExtconStateConfigs(context);
 
@@ -199,15 +202,19 @@
         if (phase == PHASE_ACTIVITY_MANAGER_READY) {
             synchronized (mLock) {
                 mSystemReady = true;
-
-                // don't bother broadcasting undocked here
-                if (mReportedDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
-                    updateLocked();
-                }
+                mDeviceProvisionedObserver.onSystemReady();
+                updateIfDockedLocked();
             }
         }
     }
 
+    private void updateIfDockedLocked() {
+        // don't bother broadcasting undocked here
+        if (mReportedDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
+            updateLocked();
+        }
+    }
+
     private void setActualDockStateLocked(int newState) {
         mActualDockState = newState;
         if (!mUpdatesStopped) {
@@ -252,8 +259,7 @@
 
             // Skip the dock intent if not yet provisioned.
             final ContentResolver cr = getContext().getContentResolver();
-            if (Settings.Global.getInt(cr,
-                    Settings.Global.DEVICE_PROVISIONED, 0) == 0) {
+            if (!mDeviceProvisionedObserver.isDeviceProvisioned()) {
                 Slog.i(TAG, "Device not provisioned, skipping dock broadcast");
                 return;
             }
@@ -419,4 +425,48 @@
             }
         }
     }
+
+    private final class DeviceProvisionedObserver extends ContentObserver {
+        private boolean mRegistered;
+
+        public DeviceProvisionedObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            synchronized (mLock) {
+                updateRegistration();
+                if (isDeviceProvisioned()) {
+                    // Send the dock broadcast if device is docked after provisioning.
+                    updateIfDockedLocked();
+                }
+            }
+        }
+
+        void onSystemReady() {
+            updateRegistration();
+        }
+
+        private void updateRegistration() {
+            boolean register = !isDeviceProvisioned();
+            if (register == mRegistered) {
+                return;
+            }
+            final ContentResolver resolver = getContext().getContentResolver();
+            if (register) {
+                resolver.registerContentObserver(
+                        Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+                        false, this);
+            } else {
+                resolver.unregisterContentObserver(this);
+            }
+            mRegistered = register;
+        }
+
+        boolean isDeviceProvisioned() {
+            return Settings.Global.getInt(getContext().getContentResolver(),
+                    Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index b56d1fc..b4ab254 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -447,6 +447,13 @@
                 thread.start();
                 break;
             case LEVEL_FACTORY_RESET:
+                // Before the completion of Reboot, if any crash happens then PackageWatchdog
+                // escalates to next level i.e. factory reset, as they happen in separate threads.
+                // Adding a check to prevent factory reset to execute before above reboot completes.
+                // Note: this reboot property is not persistent resets after reboot is completed.
+                if (isRebootPropertySet()) {
+                    break;
+                }
                 SystemProperties.set(PROP_ATTEMPTING_FACTORY_RESET, "true");
                 runnable = new Runnable() {
                     @Override
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index ca86021c..bd90d85 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -52,8 +52,8 @@
 import android.telephony.Annotation.RadioPowerState;
 import android.telephony.Annotation.SrvccState;
 import android.telephony.BarringInfo;
-import android.telephony.CallAttributes;
 import android.telephony.CallQuality;
+import android.telephony.CallState;
 import android.telephony.CellIdentity;
 import android.telephony.CellInfo;
 import android.telephony.CellSignalStrength;
@@ -82,6 +82,7 @@
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
 import android.telephony.emergency.EmergencyNumber;
+import android.telephony.ims.ImsCallSession;
 import android.telephony.ims.ImsReasonInfo;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -349,9 +350,9 @@
 
     private CallQuality[] mCallQuality;
 
-    private CallAttributes[] mCallAttributes;
+    private ArrayList<List<CallState>> mCallStateLists;
 
-    // network type of the call associated with the mCallAttributes and mCallQuality
+    // network type of the call associated with the mCallStateLists and mCallQuality
     private int[] mCallNetworkType;
 
     private int[] mSrvccState;
@@ -687,7 +688,6 @@
             mCallPreciseDisconnectCause = copyOf(mCallPreciseDisconnectCause, mNumPhones);
             mCallQuality = copyOf(mCallQuality, mNumPhones);
             mCallNetworkType = copyOf(mCallNetworkType, mNumPhones);
-            mCallAttributes = copyOf(mCallAttributes, mNumPhones);
             mOutgoingCallEmergencyNumber = copyOf(mOutgoingCallEmergencyNumber, mNumPhones);
             mOutgoingSmsEmergencyNumber = copyOf(mOutgoingSmsEmergencyNumber, mNumPhones);
             mTelephonyDisplayInfos = copyOf(mTelephonyDisplayInfos, mNumPhones);
@@ -707,6 +707,7 @@
                 cutListToSize(mLinkCapacityEstimateLists, mNumPhones);
                 cutListToSize(mCarrierPrivilegeStates, mNumPhones);
                 cutListToSize(mCarrierServiceStates, mNumPhones);
+                cutListToSize(mCallStateLists, mNumPhones);
                 return;
             }
 
@@ -730,8 +731,7 @@
                 mCallDisconnectCause[i] = DisconnectCause.NOT_VALID;
                 mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID;
                 mCallQuality[i] = createCallQuality();
-                mCallAttributes[i] = new CallAttributes(createPreciseCallState(),
-                        TelephonyManager.NETWORK_TYPE_UNKNOWN, createCallQuality());
+                mCallStateLists.add(i, new ArrayList<>());
                 mCallNetworkType[i] = TelephonyManager.NETWORK_TYPE_UNKNOWN;
                 mPreciseCallState[i] = createPreciseCallState();
                 mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
@@ -799,7 +799,7 @@
         mCallPreciseDisconnectCause = new int[numPhones];
         mCallQuality = new CallQuality[numPhones];
         mCallNetworkType = new int[numPhones];
-        mCallAttributes = new CallAttributes[numPhones];
+        mCallStateLists = new ArrayList<>();
         mPreciseDataConnectionStates = new ArrayList<>();
         mCellInfo = new ArrayList<>(numPhones);
         mImsReasonInfo = new ArrayList<>();
@@ -837,8 +837,7 @@
             mCallDisconnectCause[i] = DisconnectCause.NOT_VALID;
             mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID;
             mCallQuality[i] = createCallQuality();
-            mCallAttributes[i] = new CallAttributes(createPreciseCallState(),
-                    TelephonyManager.NETWORK_TYPE_UNKNOWN, createCallQuality());
+            mCallStateLists.add(i, new ArrayList<>());
             mCallNetworkType[i] = TelephonyManager.NETWORK_TYPE_UNKNOWN;
             mPreciseCallState[i] = createPreciseCallState();
             mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
@@ -1336,7 +1335,7 @@
                 }
                 if (events.contains(TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)) {
                     try {
-                        r.callback.onCallAttributesChanged(mCallAttributes[r.phoneId]);
+                        r.callback.onCallStatesChanged(mCallStateLists.get(r.phoneId));
                     } catch (RemoteException ex) {
                         remove(r.binder);
                     }
@@ -2171,11 +2170,30 @@
         }
     }
 
-    public void notifyPreciseCallState(int phoneId, int subId, int ringingCallState,
-                                       int foregroundCallState, int backgroundCallState) {
+    /**
+     * Send a notification to registrants that the precise call state has changed.
+     *
+     * @param phoneId the phoneId carrying the data connection
+     * @param subId the subscriptionId for the data connection
+     * @param callStates Array of PreciseCallState of foreground, background & ringing calls.
+     * @param imsCallIds Array of IMS call session ID{@link ImsCallSession#getCallId()} for
+     *                   ringing, foreground & background calls.
+     * @param imsServiceTypes Array of IMS call service type for ringing, foreground &
+     *                        background calls.
+     * @param imsCallTypes Array of IMS call type for ringing, foreground & background calls.
+     */
+    public void notifyPreciseCallState(int phoneId, int subId,
+            @Annotation.PreciseCallStates int[] callStates, String[] imsCallIds,
+            @Annotation.ImsCallServiceType int[] imsServiceTypes,
+            @Annotation.ImsCallType int[] imsCallTypes) {
         if (!checkNotifyPermission("notifyPreciseCallState()")) {
             return;
         }
+
+        int ringingCallState = callStates[CallState.CALL_CLASSIFICATION_RINGING];
+        int foregroundCallState = callStates[CallState.CALL_CLASSIFICATION_FOREGROUND];
+        int backgroundCallState = callStates[CallState.CALL_CLASSIFICATION_BACKGROUND];
+
         synchronized (mRecords) {
             if (validatePhoneId(phoneId)) {
                 mRingingCallState[phoneId] = ringingCallState;
@@ -2186,11 +2204,11 @@
                         backgroundCallState,
                         DisconnectCause.NOT_VALID,
                         PreciseDisconnectCause.NOT_VALID);
-                boolean notifyCallAttributes = true;
+                boolean notifyCallState = true;
                 if (mCallQuality == null) {
                     log("notifyPreciseCallState: mCallQuality is null, "
                             + "skipping call attributes");
-                    notifyCallAttributes = false;
+                    notifyCallState = false;
                 } else {
                     // If the precise call state is no longer active, reset the call network type
                     // and call quality.
@@ -2199,8 +2217,65 @@
                         mCallNetworkType[phoneId] = TelephonyManager.NETWORK_TYPE_UNKNOWN;
                         mCallQuality[phoneId] = createCallQuality();
                     }
-                    mCallAttributes[phoneId] = new CallAttributes(mPreciseCallState[phoneId],
-                            mCallNetworkType[phoneId], mCallQuality[phoneId]);
+                    mCallStateLists.get(phoneId).clear();
+                    if (foregroundCallState != PreciseCallState.PRECISE_CALL_STATE_NOT_VALID
+                            && foregroundCallState != PreciseCallState.PRECISE_CALL_STATE_IDLE) {
+                        CallQuality callQuality = mCallQuality[phoneId];
+                        CallState.Builder builder = new CallState.Builder(
+                                callStates[CallState.CALL_CLASSIFICATION_FOREGROUND])
+                                .setNetworkType(mCallNetworkType[phoneId])
+                                .setCallQuality(callQuality)
+                                .setCallClassification(
+                                        CallState.CALL_CLASSIFICATION_FOREGROUND);
+                        if (imsCallIds != null && imsServiceTypes != null && imsCallTypes != null) {
+                            builder = builder
+                                    .setImsCallSessionId(imsCallIds[
+                                            CallState.CALL_CLASSIFICATION_FOREGROUND])
+                                    .setImsCallServiceType(imsServiceTypes[
+                                            CallState.CALL_CLASSIFICATION_FOREGROUND])
+                                    .setImsCallType(imsCallTypes[
+                                            CallState.CALL_CLASSIFICATION_FOREGROUND]);
+                        }
+                        mCallStateLists.get(phoneId).add(builder.build());
+                    }
+                    if (backgroundCallState != PreciseCallState.PRECISE_CALL_STATE_NOT_VALID
+                            && backgroundCallState != PreciseCallState.PRECISE_CALL_STATE_IDLE) {
+                        CallState.Builder builder = new CallState.Builder(
+                                callStates[CallState.CALL_CLASSIFICATION_BACKGROUND])
+                                .setNetworkType(mCallNetworkType[phoneId])
+                                .setCallQuality(createCallQuality())
+                                .setCallClassification(
+                                        CallState.CALL_CLASSIFICATION_BACKGROUND);
+                        if (imsCallIds != null && imsServiceTypes != null && imsCallTypes != null) {
+                            builder = builder
+                                    .setImsCallSessionId(imsCallIds[
+                                            CallState.CALL_CLASSIFICATION_BACKGROUND])
+                                    .setImsCallServiceType(imsServiceTypes[
+                                            CallState.CALL_CLASSIFICATION_BACKGROUND])
+                                    .setImsCallType(imsCallTypes[
+                                            CallState.CALL_CLASSIFICATION_BACKGROUND]);
+                        }
+                        mCallStateLists.get(phoneId).add(builder.build());
+                    }
+                    if (ringingCallState != PreciseCallState.PRECISE_CALL_STATE_NOT_VALID
+                            && ringingCallState != PreciseCallState.PRECISE_CALL_STATE_IDLE) {
+                        CallState.Builder builder = new CallState.Builder(
+                                callStates[CallState.CALL_CLASSIFICATION_RINGING])
+                                .setNetworkType(mCallNetworkType[phoneId])
+                                .setCallQuality(createCallQuality())
+                                .setCallClassification(
+                                        CallState.CALL_CLASSIFICATION_RINGING);
+                        if (imsCallIds != null && imsServiceTypes != null && imsCallTypes != null) {
+                            builder = builder
+                                    .setImsCallSessionId(imsCallIds[
+                                            CallState.CALL_CLASSIFICATION_RINGING])
+                                    .setImsCallServiceType(imsServiceTypes[
+                                            CallState.CALL_CLASSIFICATION_RINGING])
+                                    .setImsCallType(imsCallTypes[
+                                            CallState.CALL_CLASSIFICATION_RINGING]);
+                        }
+                        mCallStateLists.get(phoneId).add(builder.build());
+                    }
                 }
 
                 for (Record r : mRecords) {
@@ -2213,11 +2288,11 @@
                             mRemoveList.add(r.binder);
                         }
                     }
-                    if (notifyCallAttributes && r.matchTelephonyCallbackEvent(
+                    if (notifyCallState && r.matchTelephonyCallbackEvent(
                             TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)
                             && idMatch(r, subId, phoneId)) {
                         try {
-                            r.callback.onCallAttributesChanged(mCallAttributes[phoneId]);
+                            r.callback.onCallStatesChanged(mCallStateLists.get(phoneId));
                         } catch (RemoteException ex) {
                             mRemoveList.add(r.binder);
                         }
@@ -2515,15 +2590,29 @@
                 // merge CallQuality with PreciseCallState and network type
                 mCallQuality[phoneId] = callQuality;
                 mCallNetworkType[phoneId] = callNetworkType;
-                mCallAttributes[phoneId] = new CallAttributes(mPreciseCallState[phoneId],
-                        callNetworkType, callQuality);
+                if (mCallStateLists.get(phoneId).size() > 0
+                        && mCallStateLists.get(phoneId).get(0).getCallState()
+                        == PreciseCallState.PRECISE_CALL_STATE_ACTIVE) {
+                    CallState prev = mCallStateLists.get(phoneId).remove(0);
+                    mCallStateLists.get(phoneId).add(
+                            0, new CallState.Builder(prev.getCallState())
+                                    .setNetworkType(callNetworkType)
+                                    .setCallQuality(callQuality)
+                                    .setCallClassification(prev.getCallClassification())
+                                    .setImsCallSessionId(prev.getImsCallSessionId())
+                                    .setImsCallServiceType(prev.getImsCallServiceType())
+                                    .setImsCallType(prev.getImsCallType()).build());
+                } else {
+                    log("There is no active call to report CallQaulity");
+                    return;
+                }
 
                 for (Record r : mRecords) {
                     if (r.matchTelephonyCallbackEvent(
                             TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)
                             && idMatch(r, subId, phoneId)) {
                         try {
-                            r.callback.onCallAttributesChanged(mCallAttributes[phoneId]);
+                            r.callback.onCallStatesChanged(mCallStateLists.get(phoneId));
                         } catch (RemoteException ex) {
                             mRemoveList.add(r.binder);
                         }
@@ -2991,7 +3080,6 @@
                 pw.println("mSrvccState=" + mSrvccState[i]);
                 pw.println("mCallPreciseDisconnectCause=" + mCallPreciseDisconnectCause[i]);
                 pw.println("mCallQuality=" + mCallQuality[i]);
-                pw.println("mCallAttributes=" + mCallAttributes[i]);
                 pw.println("mCallNetworkType=" + mCallNetworkType[i]);
                 pw.println("mPreciseDataConnectionStates=" + mPreciseDataConnectionStates.get(i));
                 pw.println("mOutgoingCallEmergencyNumber=" + mOutgoingCallEmergencyNumber[i]);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 35b46c1..50be458 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -87,6 +87,7 @@
 import static android.os.Process.isSdkSandboxUid;
 import static android.os.Process.isThreadInProcess;
 import static android.os.Process.killProcess;
+import static android.os.Process.killProcessGroup;
 import static android.os.Process.killProcessQuiet;
 import static android.os.Process.myPid;
 import static android.os.Process.myUid;
@@ -952,13 +953,6 @@
             }
             return false;
         }
-
-        boolean doRemoveIfNoThreadInternal(int pid, ProcessRecord app) {
-            if (app == null || app.getThread() != null) {
-                return false;
-            }
-            return doRemoveInternal(pid, app);
-        }
     }
 
     private final PendingStartActivityUids mPendingStartActivityUids;
@@ -990,7 +984,7 @@
      * method.
      */
     @GuardedBy("this")
-    void removePidLocked(int pid, ProcessRecord app) {
+    boolean removePidLocked(int pid, ProcessRecord app) {
         final boolean removed;
         synchronized (mPidsSelfLocked) {
             removed = mPidsSelfLocked.doRemoveInternal(pid, app);
@@ -1001,26 +995,6 @@
             }
             mAtmInternal.onProcessUnMapped(pid);
         }
-    }
-
-    /**
-     * Removes the process record from the map if it doesn't have a thread.
-     * <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this
-     * method.
-     */
-    @GuardedBy("this")
-    private boolean removePidIfNoThreadLocked(ProcessRecord app) {
-        final boolean removed;
-        final int pid = app.getPid();
-        synchronized (mPidsSelfLocked) {
-            removed = mPidsSelfLocked.doRemoveIfNoThreadInternal(pid, app);
-        }
-        if (removed) {
-            synchronized (sActiveProcessInfoSelfLocked) {
-                sActiveProcessInfoSelfLocked.remove(pid);
-            }
-            mAtmInternal.onProcessUnMapped(pid);
-        }
         return removed;
     }
 
@@ -2364,7 +2338,7 @@
         mAppErrors = null;
         mPackageWatchdog = null;
         mAppOpsService = mInjector.getAppOpsService(null /* file */, null /* handler */);
-        mBatteryStatsService = null;
+        mBatteryStatsService = mInjector.getBatteryStatsService();
         mHandler = new MainHandler(handlerThread.getLooper());
         mHandlerThread = handlerThread;
         mConstants = new ActivityManagerConstants(mContext, this, mHandler);
@@ -2379,7 +2353,7 @@
         mIntentFirewall = null;
         mProcessStats = new ProcessStatsService(this, mContext.getCacheDir());
         mCpHelper = new ContentProviderHelper(this, false);
-        mServices = null;
+        mServices = mInjector.getActiveServices(this);
         mSystemThread = null;
         mUiHandler = injector.getUiHandler(null /* service */);
         mUidObserverController = new UidObserverController(mUiHandler);
@@ -4771,7 +4745,7 @@
     @GuardedBy("this")
     void handleProcessStartOrKillTimeoutLocked(ProcessRecord app, boolean isKillTimeout) {
         final int pid = app.getPid();
-        boolean gone = isKillTimeout || removePidIfNoThreadLocked(app);
+        boolean gone = isKillTimeout || removePidLocked(pid, app);
 
         if (gone) {
             if (isKillTimeout) {
@@ -4852,7 +4826,7 @@
     }
 
     @GuardedBy("this")
-    private boolean attachApplicationLocked(@NonNull IApplicationThread thread,
+    private void attachApplicationLocked(@NonNull IApplicationThread thread,
             int pid, int callingUid, long startSeq) {
 
         // Find the application record that is being attached...  either via
@@ -4917,7 +4891,7 @@
                     // Ignore exceptions.
                 }
             }
-            return false;
+            return;
         }
 
         // If this application record is still attached to a previous
@@ -4942,7 +4916,7 @@
             mProcessList.startProcessLocked(app,
                     new HostingRecord(HostingRecord.HOSTING_TYPE_LINK_FAIL, processName),
                     ZYGOTE_POLICY_FLAG_EMPTY);
-            return false;
+            return;
         }
 
         EventLogTags.writeAmProcBound(app.userId, pid, app.processName);
@@ -4965,8 +4939,6 @@
             app.setUnlocked(StorageManager.isUserKeyUnlocked(app.userId));
         }
 
-        mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
-
         boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
         List<ProviderInfo> providers = normalMode
                                             ? mCpHelper.generateApplicationProvidersLocked(app)
@@ -5132,7 +5104,7 @@
             app.killLocked("error during bind", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
                     true);
             handleAppDiedLocked(app, pid, false, true, false /* fromBinderDied */);
-            return false;
+            return;
         }
 
         // Remove this record from the list of starting applications.
@@ -5140,91 +5112,6 @@
         if (DEBUG_PROCESSES && mProcessesOnHold.contains(app)) Slog.v(TAG_PROCESSES,
                 "Attach application locked removing on hold: " + app);
         mProcessesOnHold.remove(app);
-
-        boolean badApp = false;
-        boolean didSomething = false;
-
-        // See if the top visible activity is waiting to run in this process...
-        if (normalMode) {
-            try {
-                didSomething = mAtmInternal.attachApplication(app.getWindowProcessController());
-            } catch (Exception e) {
-                Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
-                badApp = true;
-            }
-        }
-
-        // Find any services that should be running in this process...
-        if (!badApp) {
-            try {
-                didSomething |= mServices.attachApplicationLocked(app, processName);
-                checkTime(startTime, "attachApplicationLocked: after mServices.attachApplicationLocked");
-            } catch (Exception e) {
-                Slog.wtf(TAG, "Exception thrown starting services in " + app, e);
-                badApp = true;
-            }
-        }
-
-        // Check if a next-broadcast receiver is in this process...
-        if (!badApp) {
-            try {
-                for (BroadcastQueue queue : mBroadcastQueues) {
-                    didSomething |= queue.onApplicationAttachedLocked(app);
-                }
-                checkTime(startTime, "attachApplicationLocked: after dispatching broadcasts");
-            } catch (Exception e) {
-                // If the app died trying to launch the receiver we declare it 'bad'
-                Slog.wtf(TAG, "Exception thrown dispatching broadcasts in " + app, e);
-                badApp = true;
-            }
-        }
-
-        // Check whether the next backup agent is in this process...
-        if (!badApp && backupTarget != null && backupTarget.app == app) {
-            if (DEBUG_BACKUP) Slog.v(TAG_BACKUP,
-                    "New app is backup target, launching agent for " + app);
-            notifyPackageUse(backupTarget.appInfo.packageName,
-                             PackageManager.NOTIFY_PACKAGE_USE_BACKUP);
-            try {
-                thread.scheduleCreateBackupAgent(backupTarget.appInfo,
-                        backupTarget.backupMode, backupTarget.userId,
-                        backupTarget.backupDestination);
-            } catch (Exception e) {
-                Slog.wtf(TAG, "Exception thrown creating backup agent in " + app, e);
-                badApp = true;
-            }
-        }
-
-        if (badApp) {
-            app.killLocked("error during init", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
-                    true);
-            handleAppDiedLocked(app, pid, false, true, false /* fromBinderDied */);
-            return false;
-        }
-
-        if (!didSomething) {
-            updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
-            checkTime(startTime, "attachApplicationLocked: after updateOomAdjLocked");
-        }
-
-
-        final HostingRecord hostingRecord = app.getHostingRecord();
-        String shortAction = getShortAction(hostingRecord.getAction());
-        FrameworkStatsLog.write(
-                FrameworkStatsLog.PROCESS_START_TIME,
-                app.info.uid,
-                pid,
-                app.info.packageName,
-                FrameworkStatsLog.PROCESS_START_TIME__TYPE__COLD,
-                app.getStartElapsedTime(),
-                (int) (bindApplicationTimeMillis - app.getStartUptime()),
-                (int) (SystemClock.uptimeMillis() - app.getStartUptime()),
-                hostingRecord.getType(),
-                hostingRecord.getName(),
-                shortAction,
-                HostingRecord.getHostingTypeIdStatsd(hostingRecord.getType()),
-                HostingRecord.getTriggerTypeForStatsd(hostingRecord.getTriggerType()));
-        return true;
     }
 
     @Override
@@ -5241,6 +5128,143 @@
         }
     }
 
+    private void finishAttachApplicationInner(long startSeq, int uid, int pid) {
+        final long startTime = SystemClock.uptimeMillis();
+        // Find the application record that is being attached...  either via
+        // the pid if we are running in multiple processes, or just pull the
+        // next app record if we are emulating process with anonymous threads.
+        final ProcessRecord app;
+        synchronized (mPidsSelfLocked) {
+            app = mPidsSelfLocked.get(pid);
+        }
+
+        if (app != null && app.getStartUid() == uid && app.getStartSeq() == startSeq) {
+            mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
+        } else {
+            Slog.wtf(TAG, "Mismatched or missing ProcessRecord: " + app + ". Pid: " + pid
+                    + ". Uid: " + uid);
+            killProcess(pid);
+            killProcessGroup(uid, pid);
+            mProcessList.noteAppKill(pid, uid,
+                    ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
+                    ApplicationExitInfo.SUBREASON_UNKNOWN,
+                    "wrong startSeq");
+            app.killLocked("unexpected process record",
+                    ApplicationExitInfo.REASON_OTHER, true);
+            return;
+        }
+
+        synchronized (this) {
+            final boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
+            final String processName = app.processName;
+            boolean badApp = false;
+            boolean didSomething = false;
+
+            // See if the top visible activity is waiting to run in this process...
+            if (normalMode) {
+                try {
+                    didSomething = mAtmInternal.attachApplication(app.getWindowProcessController());
+                } catch (Exception e) {
+                    Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
+                    badApp = true;
+                }
+            }
+
+            // Find any services that should be running in this process...
+            if (!badApp) {
+                try {
+                    didSomething |= mServices.attachApplicationLocked(app, processName);
+                    checkTime(startTime, "finishAttachApplicationInner: "
+                            + "after mServices.attachApplicationLocked");
+                } catch (Exception e) {
+                    Slog.wtf(TAG, "Exception thrown starting services in " + app, e);
+                    badApp = true;
+                }
+            }
+
+            // Check if a next-broadcast receiver is in this process...
+            if (!badApp) {
+                try {
+                    for (BroadcastQueue queue : mBroadcastQueues) {
+                        didSomething |= queue.onApplicationAttachedLocked(app);
+                    }
+                    checkTime(startTime, "finishAttachApplicationInner: "
+                            + "after dispatching broadcasts");
+                } catch (Exception e) {
+                    // If the app died trying to launch the receiver we declare it 'bad'
+                    Slog.wtf(TAG, "Exception thrown dispatching broadcasts in " + app, e);
+                    badApp = true;
+                }
+            }
+
+            // Check whether the next backup agent is in this process...
+            final BackupRecord backupTarget = mBackupTargets.get(app.userId);
+            if (!badApp && backupTarget != null && backupTarget.app == app) {
+                if (DEBUG_BACKUP) {
+                    Slog.v(TAG_BACKUP,
+                            "New app is backup target, launching agent for " + app);
+                }
+
+                notifyPackageUse(backupTarget.appInfo.packageName,
+                        PackageManager.NOTIFY_PACKAGE_USE_BACKUP);
+                try {
+                    app.getThread().scheduleCreateBackupAgent(backupTarget.appInfo,
+                            backupTarget.backupMode, backupTarget.userId,
+                            backupTarget.backupDestination);
+                } catch (Exception e) {
+                    Slog.wtf(TAG, "Exception thrown creating backup agent in " + app, e);
+                    badApp = true;
+                }
+            }
+
+            if (badApp) {
+                app.killLocked("error during init",
+                        ApplicationExitInfo.REASON_INITIALIZATION_FAILURE, true);
+                handleAppDiedLocked(app, pid, false, true, false /* fromBinderDied */);
+                return;
+            }
+
+            if (!didSomething) {
+                updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
+                checkTime(startTime, "finishAttachApplicationInner: after updateOomAdjLocked");
+            }
+
+            final HostingRecord hostingRecord = app.getHostingRecord();
+            final String shortAction = getShortAction(hostingRecord.getAction());
+            FrameworkStatsLog.write(
+                    FrameworkStatsLog.PROCESS_START_TIME,
+                    app.info.uid,
+                    pid,
+                    app.info.packageName,
+                    FrameworkStatsLog.PROCESS_START_TIME__TYPE__COLD,
+                    app.getStartElapsedTime(),
+                    (int) (app.getBindApplicationTime() - app.getStartUptime()),
+                    (int) (SystemClock.uptimeMillis() - app.getStartUptime()),
+                    hostingRecord.getType(),
+                    hostingRecord.getName(),
+                    shortAction,
+                    HostingRecord.getHostingTypeIdStatsd(hostingRecord.getType()),
+                    HostingRecord.getTriggerTypeForStatsd(hostingRecord.getTriggerType()));
+        }
+    }
+
+    @Override
+    public final void finishAttachApplication(long startSeq) {
+        final int pid = Binder.getCallingPid();
+        final int uid = Binder.getCallingUid();
+
+        if (pid == MY_PID && uid == SYSTEM_UID) {
+            return;
+        }
+
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            finishAttachApplicationInner(startSeq, uid, pid);
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
     /**
      * @return The last part of the string of an intent's action.
      */
@@ -18805,6 +18829,21 @@
             return new ProcessList();
         }
 
+        /**
+         * Returns the {@link BatteryStatsService} instance
+         */
+        public BatteryStatsService getBatteryStatsService() {
+            return new BatteryStatsService(mContext, SystemServiceManager.ensureSystemDir(),
+                BackgroundThread.get().getHandler());
+        }
+
+        /**
+         * Returns the {@link ActiveServices} instance
+         */
+        public ActiveServices getActiveServices(ActivityManagerService service) {
+            return new ActiveServices(service);
+        }
+
         private boolean ensureHasNetworkManagementInternal() {
             if (mNmi == null) {
                 mNmi = LocalServices.getService(NetworkManagementInternal.class);
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 04ba757..1eebd01 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -153,6 +153,11 @@
             "bcast_extra_running_urgent_process_queues";
     private static final int DEFAULT_EXTRA_RUNNING_URGENT_PROCESS_QUEUES = 1;
 
+    public int MAX_CONSECUTIVE_URGENT_DISPATCHES = DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES;
+    private static final String KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES =
+            "bcast_max_consecutive_urgent_dispatches";
+    private static final int DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES = 3;
+
     /**
      * For {@link BroadcastQueueModernImpl}: Maximum number of active broadcasts
      * to dispatch to a "running" process queue before we retire them back to
@@ -333,6 +338,9 @@
             EXTRA_RUNNING_URGENT_PROCESS_QUEUES = getDeviceConfigInt(
                     KEY_EXTRA_RUNNING_URGENT_PROCESS_QUEUES,
                     DEFAULT_EXTRA_RUNNING_URGENT_PROCESS_QUEUES);
+            MAX_CONSECUTIVE_URGENT_DISPATCHES = getDeviceConfigInt(
+                    KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES,
+                    DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES);
             MAX_RUNNING_ACTIVE_BROADCASTS = getDeviceConfigInt(KEY_MAX_RUNNING_ACTIVE_BROADCASTS,
                     DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS);
             MAX_PENDING_BROADCASTS = getDeviceConfigInt(KEY_MAX_PENDING_BROADCASTS,
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 0f9c775..66d7fc9 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -147,6 +147,12 @@
     private boolean mActiveViaColdStart;
 
     /**
+     * Number of consecutive urgent broadcasts that have been dispatched
+     * since the last non-urgent dispatch.
+     */
+    private int mActiveCountConsecutiveUrgent;
+
+    /**
      * Count of pending broadcasts of these various flavors.
      */
     private int mCountForeground;
@@ -546,19 +552,47 @@
      * {@link #isEmpty()} being false.
      */
     SomeArgs removeNextBroadcast() {
-        ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
+        final ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
+        if (queue == mPendingUrgent) {
+            mActiveCountConsecutiveUrgent++;
+        } else {
+            mActiveCountConsecutiveUrgent = 0;
+        }
         return queue.removeFirst();
     }
 
     @Nullable ArrayDeque<SomeArgs> queueForNextBroadcast() {
-        if (!mPendingUrgent.isEmpty()) {
-            return mPendingUrgent;
-        } else if (!mPending.isEmpty()) {
-            return mPending;
+        ArrayDeque<SomeArgs> nextUrgent = mPendingUrgent.isEmpty() ? null : mPendingUrgent;
+        ArrayDeque<SomeArgs> nextNormal = null;
+        if (!mPending.isEmpty()) {
+            nextNormal = mPending;
         } else if (!mPendingOffload.isEmpty()) {
-            return mPendingOffload;
+            nextNormal = mPendingOffload;
         }
-        return null;
+        // nothing urgent pending, no further decisionmaking
+        if (nextUrgent == null) {
+            return nextNormal;
+        }
+        // nothing but urgent pending, also no further decisionmaking
+        if (nextNormal == null) {
+            return nextUrgent;
+        }
+
+        // Starvation mitigation: although we prioritize urgent broadcasts by default,
+        // we allow non-urgent deliveries to make steady progress even if urgent
+        // broadcasts are arriving faster than they can be dispatched.
+        //
+        // We do not try to defer to the next non-urgent broadcast if that broadcast
+        // is ordered and still blocked on delivery to other recipients.
+        final SomeArgs nextNormalArgs = nextNormal.peekFirst();
+        final BroadcastRecord rNormal = (BroadcastRecord) nextNormalArgs.arg1;
+        final int nextNormalIndex = nextNormalArgs.argi1;
+        final BroadcastRecord rUrgent = (BroadcastRecord) nextUrgent.peekFirst().arg1;
+        final boolean canTakeNormal =
+                mActiveCountConsecutiveUrgent >= constants.MAX_CONSECUTIVE_URGENT_DISPATCHES
+                        && rNormal.enqueueTime <= rUrgent.enqueueTime
+                        && !blockedOnOrderedDispatch(rNormal, nextNormalIndex);
+        return canTakeNormal ? nextNormal : nextUrgent;
     }
 
     /**
@@ -710,6 +744,18 @@
         }
     }
 
+    private boolean blockedOnOrderedDispatch(BroadcastRecord r, int index) {
+        final int blockedUntilTerminalCount = r.blockedUntilTerminalCount[index];
+
+        // We might be blocked waiting for other receivers to finish,
+        // typically for an ordered broadcast or priority traunches
+        if (r.terminalCount < blockedUntilTerminalCount
+                && !isDeliveryStateTerminal(r.getDeliveryState(index))) {
+            return true;
+        }
+        return false;
+    }
+
     /**
      * Update {@link #getRunnableAt()} if it's currently invalidated.
      */
@@ -718,13 +764,11 @@
         if (next != null) {
             final BroadcastRecord r = (BroadcastRecord) next.arg1;
             final int index = next.argi1;
-            final int blockedUntilTerminalCount = r.blockedUntilTerminalCount[index];
             final long runnableAt = r.enqueueTime;
 
-            // We might be blocked waiting for other receivers to finish,
-            // typically for an ordered broadcast or priority traunches
-            if (r.terminalCount < blockedUntilTerminalCount
-                    && !isDeliveryStateTerminal(r.getDeliveryState(index))) {
+            // If we're specifically queued behind other ordered dispatch activity,
+            // we aren't runnable yet
+            if (blockedOnOrderedDispatch(r, index)) {
                 mRunnableAt = Long.MAX_VALUE;
                 mRunnableAtReason = REASON_BLOCKED;
                 return;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index ecea96e..937bbc9c 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2508,7 +2508,7 @@
     }
 
     @GuardedBy("mService")
-    private String isProcStartValidLocked(ProcessRecord app, long expectedStartSeq) {
+    String isProcStartValidLocked(ProcessRecord app, long expectedStartSeq) {
         StringBuilder sb = null;
         if (app.isKilledByAm()) {
             if (sb == null) sb = new StringBuilder();
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 0a8c640..4706c268 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -200,6 +200,11 @@
     private volatile long mStartElapsedTime;
 
     /**
+     * When the process was sent the bindApplication request
+     */
+    private volatile long mBindApplicationTime;
+
+    /**
      * This will be same as {@link #uid} usually except for some apps used during factory testing.
      */
     private volatile int mStartUid;
@@ -739,6 +744,10 @@
         return mStartElapsedTime;
     }
 
+    long getBindApplicationTime() {
+        return mBindApplicationTime;
+    }
+
     int getStartUid() {
         return mStartUid;
     }
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 4d86140..2ea49b3 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -100,7 +100,6 @@
 import android.util.IntArray;
 import android.util.Pair;
 import android.util.SparseArray;
-import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
@@ -177,7 +176,8 @@
     static final int START_USER_SWITCH_FG_MSG = 120;
     static final int COMPLETE_USER_SWITCH_MSG = 130;
     static final int USER_COMPLETED_EVENT_MSG = 140;
-    static final int USER_VISIBILITY_CHANGED_MSG = 150;
+
+    private static final int NO_ARG2 = 0;
 
     // Message constant to clear {@link UserJourneySession} from {@link mUserIdToUserJourneyMap} if
     // the user journey, defined in the UserLifecycleJourneyReported atom for statsd, is not
@@ -437,20 +437,6 @@
     /** @see #getLastUserUnlockingUptime */
     private volatile long mLastUserUnlockingUptime = 0;
 
-    // TODO(b/244333150) remove this array and let UserVisibilityMediator call the listeners
-    // directly, as that class should be responsible for all user visibility logic (for example,
-    // when the foreground user is switched out, its profiles also become invisible)
-    /**
-     * List of visible users (as defined by {@link UserManager#isUserVisible()}).
-     *
-     * <p>It's only used to call {@link UserManagerInternal} when the visibility is changed upon
-     * the user starting or stopping.
-     *
-     * <p>Note: only the key is used, not the value.
-     */
-    @GuardedBy("mLock")
-    private final SparseBooleanArray mVisibleUsers = new SparseBooleanArray();
-
     private final UserLifecycleListener mUserLifecycleListener = new UserLifecycleListener() {
         @Override
         public void onUserCreated(UserInfo user, Object token) {
@@ -1092,24 +1078,11 @@
             // instead.
             userManagerInternal.unassignUserFromDisplayOnStop(userId);
 
-            final boolean visibilityChanged;
-            boolean visibleBefore;
-            synchronized (mLock) {
-                visibleBefore = mVisibleUsers.get(userId);
-                if (visibleBefore) {
-                    deleteVisibleUserLocked(userId);
-                    visibilityChanged = true;
-                } else {
-                    visibilityChanged = false;
-                }
-            }
-
             updateStartedUserArrayLU();
 
             final boolean allowDelayedLockingCopied = allowDelayedLocking;
             Runnable finishUserStoppingAsync = () ->
-                    mHandler.post(() -> finishUserStopping(userId, uss, allowDelayedLockingCopied,
-                            visibilityChanged));
+                    mHandler.post(() -> finishUserStopping(userId, uss, allowDelayedLockingCopied));
 
             if (mInjector.getUserManager().isPreCreated(userId)) {
                 finishUserStoppingAsync.run();
@@ -1146,22 +1119,8 @@
         }
     }
 
-    private void addVisibleUserLocked(@UserIdInt int userId) {
-        if (DEBUG_MU) {
-            Slogf.d(TAG, "adding %d to mVisibleUsers", userId);
-        }
-        mVisibleUsers.put(userId, true);
-    }
-
-    private void deleteVisibleUserLocked(@UserIdInt int userId) {
-        if (DEBUG_MU) {
-            Slogf.d(TAG, "deleting %d from mVisibleUsers", userId);
-        }
-        mVisibleUsers.delete(userId);
-    }
-
     private void finishUserStopping(final int userId, final UserState uss,
-            final boolean allowDelayedLocking, final boolean visibilityChanged) {
+            final boolean allowDelayedLocking) {
         EventLog.writeEvent(EventLogTags.UC_FINISH_USER_STOPPING, userId);
         synchronized (mLock) {
             if (uss.state != UserState.STATE_STOPPING) {
@@ -1179,9 +1138,6 @@
                 BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH,
                 Integer.toString(userId), userId);
         mInjector.getSystemServiceManager().onUserStopping(userId);
-        if (visibilityChanged) {
-            mInjector.onUserVisibilityChanged(userId, /* visible= */ false);
-        }
 
         Runnable finishUserStoppedAsync = () ->
                 mHandler.post(() -> finishUserStopped(uss, allowDelayedLocking));
@@ -1655,25 +1611,13 @@
                     userInfo.profileGroupId, foreground, displayId);
             t.traceEnd();
 
-            boolean visible;
-            switch (result) {
-                case UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE:
-                    visible = true;
-                    break;
-                case UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE:
-                    visible = false;
-                    break;
-                default:
-                    Slogf.wtf(TAG, "Wrong result from assignUserToDisplayOnStart(): %d", result);
-                    // Fall through
-                case UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE:
-                    Slogf.e(TAG, "%s user(%d) / display (%d) assignment failed: %s",
-                            (foreground ? "fg" : "bg"), userId, displayId,
-                            UserManagerInternal.userAssignmentResultToString(result));
-                    return false;
+            if (result == UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE) {
+                Slogf.e(TAG, "%s user(%d) / display (%d) assignment failed: %s",
+                        (foreground ? "fg" : "bg"), userId, displayId,
+                        UserManagerInternal.userAssignmentResultToString(result));
+                return false;
             }
 
-
             // TODO(b/239982558): might need something similar for bg users on secondary display
             if (foreground && isUserSwitchUiEnabled()) {
                 t.traceBegin("startFreezingScreen");
@@ -1724,15 +1668,7 @@
                 // Make sure the old user is no longer considering the display to be on.
                 mInjector.reportGlobalUsageEvent(UsageEvents.Event.SCREEN_NON_INTERACTIVE);
                 boolean userSwitchUiEnabled;
-                // TODO(b/244333150): temporary state until the callback logic is moved to
-                // UserVisibilityManager
-                int previousCurrentUserId; boolean notifyPreviousCurrentUserId;
                 synchronized (mLock) {
-                    previousCurrentUserId = mCurrentUserId;
-                    notifyPreviousCurrentUserId = mVisibleUsers.get(previousCurrentUserId);
-                    if (notifyPreviousCurrentUserId) {
-                        deleteVisibleUserLocked(previousCurrentUserId);
-                    }
                     mCurrentUserId = userId;
                     mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up
                     userSwitchUiEnabled = mUserSwitchUiEnabled;
@@ -1753,10 +1689,6 @@
                         mInjector.getWindowManager().lockNow(null);
                     }
                 }
-                if (notifyPreviousCurrentUserId) {
-                    mHandler.sendMessage(mHandler.obtainMessage(USER_VISIBILITY_CHANGED_MSG,
-                            previousCurrentUserId, 0));
-                }
 
             } else {
                 final Integer currentUserIdInt = mCurrentUserId;
@@ -1768,12 +1700,6 @@
             }
             t.traceEnd();
 
-            if (visible) {
-                synchronized (mLock) {
-                    addVisibleUserLocked(userId);
-                }
-            }
-
             // Make sure user is in the started state.  If it is currently
             // stopping, we need to knock that off.
             if (uss.state == UserState.STATE_STOPPING) {
@@ -1810,20 +1736,10 @@
                 // Booting up a new user, need to tell system services about it.
                 // Note that this is on the same handler as scheduling of broadcasts,
                 // which is important because it needs to go first.
-                mHandler.sendMessage(mHandler.obtainMessage(USER_START_MSG, userId,
-                        visible ? 1 : 0));
+                mHandler.sendMessage(mHandler.obtainMessage(USER_START_MSG, userId, NO_ARG2));
                 t.traceEnd();
             }
 
-            if (visible) {
-                // User was already running and became visible (for example, when switching to a
-                // user that was started in the background before), so it's necessary to explicitly
-                // notify the services (while when the user starts from BOOTING, USER_START_MSG
-                // takes care of that.
-                mHandler.sendMessage(
-                        mHandler.obtainMessage(USER_VISIBILITY_CHANGED_MSG, userId, 1));
-            }
-
             t.traceBegin("sendMessages");
             if (foreground) {
                 mHandler.sendMessage(mHandler.obtainMessage(USER_CURRENT_MSG, userId, oldUserId));
@@ -2248,11 +2164,6 @@
         uss.switching = false;
         stopGuestOrEphemeralUserIfBackground(oldUserId);
         stopUserOnSwitchIfEnforced(oldUserId);
-        if (oldUserId == UserHandle.USER_SYSTEM) {
-            // System user is never stopped, but its visibility is changed (as it is brought to the
-            // background)
-            updateSystemUserVisibility(t, /* visible= */ false);
-        }
 
         t.traceEnd(); // end continueUserSwitch
     }
@@ -2614,27 +2525,10 @@
             // Don't need to call on HSUM because it will be called when the system user is
             // restarted on background
             mInjector.onUserStarting(UserHandle.USER_SYSTEM);
-            mInjector.onUserVisibilityChanged(UserHandle.USER_SYSTEM, /* visible= */ true);
+            mInjector.onSystemUserVisibilityChanged(/* visible= */ true);
         }
     }
 
-    private void updateSystemUserVisibility(TimingsTraceAndSlog t, boolean visible) {
-        t.traceBegin("update-system-userVisibility-" + visible);
-        if (DEBUG_MU) {
-            Slogf.d(TAG, "updateSystemUserVisibility(): visible=%b", visible);
-        }
-        int userId = UserHandle.USER_SYSTEM;
-        synchronized (mLock) {
-            if (visible) {
-                addVisibleUserLocked(userId);
-            } else {
-                deleteVisibleUserLocked(userId);
-            }
-        }
-        mInjector.onUserVisibilityChanged(userId, visible);
-        t.traceEnd();
-    }
-
     /**
      * Refreshes the internal caches related to user profiles.
      *
@@ -3032,9 +2926,6 @@
                     proto.end(uToken);
                 }
             }
-            for (int i = 0; i < mVisibleUsers.size(); i++) {
-                proto.write(UserControllerProto.VISIBLE_USERS_ARRAY, mVisibleUsers.keyAt(i));
-            }
             proto.write(UserControllerProto.CURRENT_USER, mCurrentUserId);
             for (int i = 0; i < mCurrentProfileIds.length; i++) {
                 proto.write(UserControllerProto.CURRENT_PROFILES, mCurrentProfileIds[i]);
@@ -3094,7 +2985,6 @@
                 pw.println("  mSwitchingToSystemUserMessage: " + mSwitchingToSystemUserMessage);
             }
             pw.println("  mLastUserUnlockingUptime: " + mLastUserUnlockingUptime);
-            pw.println("  mVisibleUsers: " + mVisibleUsers);
         }
     }
 
@@ -3212,10 +3102,6 @@
             case COMPLETE_USER_SWITCH_MSG:
                 completeUserSwitch(msg.arg1);
                 break;
-            case USER_VISIBILITY_CHANGED_MSG:
-                mInjector.onUserVisibilityChanged(/* userId= */ msg.arg1,
-                        /* visible= */ msg.arg2 == 1);
-                break;
         }
         return false;
     }
@@ -3750,8 +3636,8 @@
             getSystemServiceManager().onUserStarting(TimingsTraceAndSlog.newAsyncLog(), userId);
         }
 
-        void onUserVisibilityChanged(@UserIdInt int userId, boolean visible) {
-            getUserManagerInternal().onUserVisibilityChanged(userId, visible);
+        void onSystemUserVisibilityChanged(boolean visible) {
+            getUserManagerInternal().onSystemUserVisibilityChanged(visible);
         }
     }
 }
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index b92c163..f650255 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -40,6 +40,7 @@
 import android.app.GameState;
 import android.app.IGameManagerService;
 import android.app.IGameModeListener;
+import android.app.StatsManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -76,6 +77,7 @@
 import android.util.AttributeSet;
 import android.util.KeyValueListParser;
 import android.util.Slog;
+import android.util.StatsEvent;
 import android.util.Xml;
 
 import com.android.internal.annotations.GuardedBy;
@@ -104,6 +106,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Service to manage game related features.
@@ -338,7 +341,18 @@
                                     + " and userId " + userId);
                             break;
                         }
+                        if (mHandler.hasMessages(CANCEL_GAME_LOADING_MODE)) {
+                            mHandler.removeMessages(CANCEL_GAME_LOADING_MODE);
+                        }
                         mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, isLoading);
+                        if (isLoading) {
+                            int loadingBoostDuration = getLoadingBoostDuration(packageName, userId);
+                            loadingBoostDuration = loadingBoostDuration > 0 ? loadingBoostDuration
+                                    : LOADING_BOOST_MAX_DURATION;
+                            mHandler.sendMessageDelayed(
+                                    mHandler.obtainMessage(CANCEL_GAME_LOADING_MODE),
+                                    loadingBoostDuration);
+                        }
                     }
                     break;
                 }
@@ -438,6 +452,7 @@
     public void setGameState(String packageName, @NonNull GameState gameState,
             @UserIdInt int userId) {
         if (!isPackageGame(packageName, userId)) {
+            Slog.d(TAG, "No-op for attempt to set game state for non-game app: " + packageName);
             // Restrict to games only.
             return;
         }
@@ -921,6 +936,7 @@
         public void onBootPhase(int phase) {
             if (phase == PHASE_BOOT_COMPLETED) {
                 mService.onBootCompleted();
+                mService.registerStatsCallbacks();
             }
         }
 
@@ -964,11 +980,8 @@
         }
     }
 
-    private @GameMode int[] getAvailableGameModesUnchecked(String packageName) {
-        final GamePackageConfiguration config;
-        synchronized (mDeviceConfigLock) {
-            config = mConfigs.get(packageName);
-        }
+    private @GameMode int[] getAvailableGameModesUnchecked(String packageName, int userId) {
+        final GamePackageConfiguration config = getConfig(packageName, userId);
         if (config == null) {
             return new int[]{GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM};
         }
@@ -991,9 +1004,13 @@
      */
     @Override
     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
-    public @GameMode int[] getAvailableGameModes(String packageName) throws SecurityException {
+    public @GameMode int[] getAvailableGameModes(String packageName, int userId)
+            throws SecurityException {
         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
-        return getAvailableGameModesUnchecked(packageName);
+        if (!isPackageGame(packageName, userId)) {
+            return new int[]{};
+        }
+        return getAvailableGameModesUnchecked(packageName, userId);
     }
 
     private @GameMode int getGameModeFromSettingsUnchecked(String packageName,
@@ -1060,7 +1077,6 @@
         // Check the caller has the necessary permission.
         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
 
-        // Restrict to games only.
         if (!isPackageGame(packageName, userId)) {
             return null;
         }
@@ -1090,7 +1106,7 @@
         } else {
             return new GameModeInfo.Builder()
                     .setActiveGameMode(activeGameMode)
-                    .setAvailableGameModes(getAvailableGameModesUnchecked(packageName))
+                    .setAvailableGameModes(getAvailableGameModesUnchecked(packageName, userId))
                     .build();
         }
     }
@@ -1104,9 +1120,11 @@
     public void setGameMode(String packageName, @GameMode int gameMode, int userId)
             throws SecurityException {
         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
-
-        if (!isPackageGame(packageName, userId) || gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
-            // Restrict to games and valid game modes only.
+        if (gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
+            Slog.d(TAG, "No-op for attempt to set UNSUPPORTED mode for app: " + packageName);
+            return;
+        } else if (!isPackageGame(packageName, userId)) {
+            Slog.d(TAG, "No-op for attempt to set game mode for non-game app: " + packageName);
             return;
         }
         int fromGameMode;
@@ -1139,6 +1157,15 @@
         sendUserMessage(userId, WRITE_SETTINGS, "SET_GAME_MODE", WRITE_DELAY_MILLIS);
         sendUserMessage(userId, WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
                 "SET_GAME_MODE", 0 /*delayMillis*/);
+        int gameUid = -1;
+        try {
+            gameUid = mPackageManager.getPackageUidAsUser(packageName, userId);
+        } catch (NameNotFoundException ex) {
+            Slog.d(TAG, "Cannot find the UID for package " + packageName + " under user " + userId);
+        }
+        FrameworkStatsLog.write(FrameworkStatsLog.GAME_MODE_CHANGED, gameUid,
+                Binder.getCallingUid(), gameModeToStatsdGameMode(fromGameMode),
+                gameModeToStatsdGameMode(gameMode));
     }
 
     /**
@@ -1205,17 +1232,16 @@
                 Binder.getCallingUid(), userId, false, true, "notifyGraphicsEnvironmentSetup",
                 "com.android.server.app.GameManagerService");
 
-        // Restrict to games only.
-        if (!isPackageGame(packageName, userId)) {
-            return;
-        }
-
         if (!isValidPackageName(packageName, userId)) {
+            Slog.d(TAG, "No-op for attempt to notify graphics env setup for different package"
+                    + "than caller with uid: " + Binder.getCallingUid());
             return;
         }
 
         final int gameMode = getGameMode(packageName, userId);
         if (gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
+            Slog.d(TAG, "No-op for attempt to notify graphics env setup for non-game app: "
+                    + packageName);
             return;
         }
         int loadingBoostDuration = getLoadingBoostDuration(packageName, userId);
@@ -1330,6 +1356,11 @@
             GameModeConfiguration gameModeConfig, int userId)
             throws SecurityException, IllegalArgumentException {
         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
+        if (!isPackageGame(packageName, userId)) {
+            Slog.d(TAG, "No-op for attempt to update custom game mode for non-game app: "
+                    + packageName);
+            return;
+        }
         synchronized (mLock) {
             if (!mSettings.containsKey(userId)) {
                 throw new IllegalArgumentException("User " + userId + " wasn't started");
@@ -1353,11 +1384,21 @@
         }
         GamePackageConfiguration.GameModeConfiguration internalConfig =
                 configOverride.getOrAddDefaultGameModeConfiguration(GameManager.GAME_MODE_CUSTOM);
+        int gameUid = -1;
+        try {
+            gameUid = mPackageManager.getPackageUidAsUser(packageName, userId);
+        } catch (NameNotFoundException ex) {
+            Slog.d(TAG, "Cannot find the UID for package " + packageName + " under user " + userId);
+        }
+        FrameworkStatsLog.write(FrameworkStatsLog.GAME_MODE_CONFIGURATION_CHANGED, gameUid,
+                Binder.getCallingUid(), gameModeToStatsdGameMode(GameManager.GAME_MODE_CUSTOM),
+                internalConfig.getScaling(), gameModeConfig.getScalingFactor(),
+                internalConfig.getFps(), gameModeConfig.getFpsOverride());
         internalConfig.updateFromPublicGameModeConfig(gameModeConfig);
 
         Slog.i(TAG, "Updated custom game mode config for package: " + packageName
                 + " with FPS=" + internalConfig.getFps() + ";Scaling="
-                + internalConfig.getScaling());
+                + internalConfig.getScaling() + " under user " + userId);
     }
 
     /**
@@ -1618,11 +1659,6 @@
     public void resetGameModeConfigOverride(String packageName, @UserIdInt int userId,
             @GameMode int gameModeToReset) throws SecurityException {
         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
-        final GamePackageConfiguration deviceConfig;
-        synchronized (mDeviceConfigLock) {
-            deviceConfig = mConfigs.get(packageName);
-        }
-
         // resets GamePackageConfiguration of a given packageName.
         // If a gameMode is specified, only reset the GameModeConfiguration of the gameMode.
         synchronized (mLock) {
@@ -1938,13 +1974,99 @@
         LocalServices.addService(GameManagerInternal.class, new LocalService());
     }
 
-    private String dumpDeviceConfigs() {
-        StringBuilder out = new StringBuilder();
-        for (String key : mConfigs.keySet()) {
-            out.append("[\nName: ").append(key)
-                    .append("\nConfig: ").append(mConfigs.get(key).toString()).append("\n]");
+    private void registerStatsCallbacks() {
+        final StatsManager statsManager = mContext.getSystemService(StatsManager.class);
+        statsManager.setPullAtomCallback(
+                FrameworkStatsLog.GAME_MODE_INFO,
+                null, // use default PullAtomMetadata values
+                BackgroundThread.getExecutor(),
+                this::onPullAtom);
+        statsManager.setPullAtomCallback(
+                FrameworkStatsLog.GAME_MODE_CONFIGURATION,
+                null, // use default PullAtomMetadata values
+                BackgroundThread.getExecutor(),
+                this::onPullAtom);
+        statsManager.setPullAtomCallback(
+                FrameworkStatsLog.GAME_MODE_LISTENER,
+                null, // use default PullAtomMetadata values
+                BackgroundThread.getExecutor(),
+                this::onPullAtom);
+    }
+
+    private int onPullAtom(int atomTag, @NonNull List<StatsEvent> data) {
+        if (atomTag == FrameworkStatsLog.GAME_MODE_INFO
+                || atomTag == FrameworkStatsLog.GAME_MODE_CONFIGURATION) {
+            int userId = ActivityManager.getCurrentUser();
+            Set<String> packages;
+            synchronized (mDeviceConfigLock) {
+                packages = mConfigs.keySet();
+            }
+            for (String p : packages) {
+                GamePackageConfiguration config = getConfig(p, userId);
+                if (config == null) {
+                    continue;
+                }
+                int uid = -1;
+                try {
+                    uid = mPackageManager.getPackageUidAsUser(p, userId);
+                } catch (NameNotFoundException ex) {
+                    Slog.d(TAG,
+                            "Cannot find UID for package " + p + " under user handle id " + userId);
+                }
+                if (atomTag == FrameworkStatsLog.GAME_MODE_INFO) {
+                    data.add(
+                            FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.GAME_MODE_INFO, uid,
+                                    gameModesToStatsdGameModes(config.getOptedInGameModes()),
+                                    gameModesToStatsdGameModes(config.getAvailableGameModes())));
+                } else if (atomTag == FrameworkStatsLog.GAME_MODE_CONFIGURATION) {
+                    for (int gameMode : config.getAvailableGameModes()) {
+                        GamePackageConfiguration.GameModeConfiguration modeConfig =
+                                config.getGameModeConfiguration(gameMode);
+                        if (modeConfig != null) {
+                            data.add(FrameworkStatsLog.buildStatsEvent(
+                                    FrameworkStatsLog.GAME_MODE_CONFIGURATION, uid,
+                                    gameModeToStatsdGameMode(gameMode), modeConfig.getFps(),
+                                    modeConfig.getScaling()));
+                        }
+                    }
+                }
+            }
+        } else if (atomTag == FrameworkStatsLog.GAME_MODE_LISTENER) {
+            synchronized (mGameModeListenerLock) {
+                data.add(FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.GAME_MODE_LISTENER,
+                        mGameModeListeners.size()));
+            }
         }
-        return out.toString();
+        return android.app.StatsManager.PULL_SUCCESS;
+    }
+
+    private static int[] gameModesToStatsdGameModes(int[] modes) {
+        if (modes == null) {
+            return null;
+        }
+        int[] statsdModes = new int[modes.length];
+        int i = 0;
+        for (int mode : modes) {
+            statsdModes[i++] = gameModeToStatsdGameMode(mode);
+        }
+        return statsdModes;
+    }
+
+    private static int gameModeToStatsdGameMode(int mode) {
+        switch (mode) {
+            case GameManager.GAME_MODE_BATTERY:
+                return FrameworkStatsLog.GAME_MODE_CONFIGURATION__GAME_MODE__GAME_MODE_BATTERY;
+            case GameManager.GAME_MODE_PERFORMANCE:
+                return FrameworkStatsLog.GAME_MODE_CONFIGURATION__GAME_MODE__GAME_MODE_PERFORMANCE;
+            case GameManager.GAME_MODE_CUSTOM:
+                return FrameworkStatsLog.GAME_MODE_CONFIGURATION__GAME_MODE__GAME_MODE_CUSTOM;
+            case GameManager.GAME_MODE_STANDARD:
+                return FrameworkStatsLog.GAME_MODE_CONFIGURATION__GAME_MODE__GAME_MODE_STANDARD;
+            case GameManager.GAME_MODE_UNSUPPORTED:
+                return FrameworkStatsLog.GAME_MODE_CONFIGURATION__GAME_MODE__GAME_MODE_UNSUPPORTED;
+            default:
+                return FrameworkStatsLog.GAME_MODE_CONFIGURATION__GAME_MODE__GAME_MODE_UNSPECIFIED;
+        }
     }
 
     private static int gameStateModeToStatsdGameState(int mode) {
diff --git a/services/core/java/com/android/server/app/GameManagerShellCommand.java b/services/core/java/com/android/server/app/GameManagerShellCommand.java
index cdbffbe..aa9b77c 100644
--- a/services/core/java/com/android/server/app/GameManagerShellCommand.java
+++ b/services/core/java/com/android/server/app/GameManagerShellCommand.java
@@ -105,7 +105,9 @@
                 ServiceManager.getServiceOrThrow(Context.GAME_SERVICE));
         boolean batteryModeSupported = false;
         boolean perfModeSupported = false;
-        int[] modes = service.getAvailableGameModes(packageName);
+        int userId = userIdStr != null ? Integer.parseInt(userIdStr)
+                : ActivityManager.getCurrentUser();
+        int[] modes = service.getAvailableGameModes(packageName, userId);
         for (int mode : modes) {
             if (mode == GameManager.GAME_MODE_PERFORMANCE) {
                 perfModeSupported = true;
@@ -113,8 +115,6 @@
                 batteryModeSupported = true;
             }
         }
-        int userId = userIdStr != null ? Integer.parseInt(userIdStr)
-                : ActivityManager.getCurrentUser();
         switch (gameMode.toLowerCase()) {
             case "1":
             case "standard":
@@ -223,7 +223,7 @@
 
         boolean batteryModeSupported = false;
         boolean perfModeSupported = false;
-        int [] modes = gameManagerService.getAvailableGameModes(packageName);
+        int [] modes = gameManagerService.getAvailableGameModes(packageName, userId);
 
         for (int mode : modes) {
             if (mode == GameManager.GAME_MODE_PERFORMANCE) {
diff --git a/services/core/java/com/android/server/app/TEST_MAPPING b/services/core/java/com/android/server/app/TEST_MAPPING
index feb2b4f..82840ee 100644
--- a/services/core/java/com/android/server/app/TEST_MAPPING
+++ b/services/core/java/com/android/server/app/TEST_MAPPING
@@ -9,6 +9,23 @@
       ]
     },
     {
+      "name": "CtsStatsdAtomHostTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "include-filter": "android.cts.statsdatom.gamemanager"
+        }
+      ],
+      "file_patterns": [
+        "(/|^)GameManagerService.java"
+      ]
+    },
+    {
       "name": "FrameworksMockingServicesTests",
       "options": [
         {
diff --git a/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
similarity index 97%
rename from services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java
rename to services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
index f6fff35..8d1da71 100644
--- a/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
@@ -20,7 +20,7 @@
 import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES;
 import static android.app.AppOpsManager.opRestrictsRead;
 
-import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
+import static com.android.server.appop.AppOpsServiceImpl.ModeCallback.ALL_OPS;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -56,7 +56,7 @@
  * Legacy implementation for App-ops service's app-op mode (uid and package) storage and access.
  * In the future this class will also include mode callbacks and op restrictions.
  */
-public class LegacyAppOpsServiceInterfaceImpl implements AppOpsServiceInterface {
+public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface {
 
     static final String TAG = "LegacyAppOpsServiceInterfaceImpl";
 
@@ -84,9 +84,9 @@
     private static final int UID_ANY = -2;
 
 
-    LegacyAppOpsServiceInterfaceImpl(PersistenceScheduler persistenceScheduler,
-            @NonNull Object lock, Handler handler, Context context,
-            SparseArray<int[]> switchedOps) {
+    AppOpsCheckingServiceImpl(PersistenceScheduler persistenceScheduler,
+                              @NonNull Object lock, Handler handler, Context context,
+                              SparseArray<int[]> switchedOps) {
         this.mPersistenceScheduler = persistenceScheduler;
         this.mLock = lock;
         this.mHandler = handler;
@@ -456,7 +456,7 @@
             final ArraySet<String> reportedPackageNames = callbackSpecs.valueAt(i);
             if (reportedPackageNames == null) {
                 mHandler.sendMessage(PooledLambda.obtainMessage(
-                        LegacyAppOpsServiceInterfaceImpl::notifyOpChanged,
+                        AppOpsCheckingServiceImpl::notifyOpChanged,
                         this, callback, code, uid, (String) null));
 
             } else {
@@ -464,7 +464,7 @@
                 for (int j = 0; j < reportedPackageCount; j++) {
                     final String reportedPackageName = reportedPackageNames.valueAt(j);
                     mHandler.sendMessage(PooledLambda.obtainMessage(
-                            LegacyAppOpsServiceInterfaceImpl::notifyOpChanged,
+                            AppOpsCheckingServiceImpl::notifyOpChanged,
                             this, callback, code, uid, reportedPackageName));
                 }
             }
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
new file mode 100644
index 0000000..d8d0d48
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2022 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.appop;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.AppOpsManager.Mode;
+import android.util.ArraySet;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+import java.io.PrintWriter;
+
+/**
+ * Interface for accessing and modifying modes for app-ops i.e. package and uid modes.
+ * This interface also includes functions for added and removing op mode watchers.
+ * In the future this interface will also include op restrictions.
+ */
+public interface AppOpsCheckingServiceInterface {
+    /**
+     * Returns a copy of non-default app-ops with op as keys and their modes as values for a uid.
+     * Returns an empty SparseIntArray if nothing is set.
+     * @param uid for which we need the app-ops and their modes.
+     */
+    SparseIntArray getNonDefaultUidModes(int uid);
+
+    /**
+     * Returns the app-op mode for a particular app-op of a uid.
+     * Returns default op mode if the op mode for particular uid and op is not set.
+     * @param uid user id for which we need the mode.
+     * @param op app-op for which we need the mode.
+     * @return mode of the app-op.
+     */
+    int getUidMode(int uid, int op);
+
+    /**
+     * Set the app-op mode for a particular uid and op.
+     * The mode is not set if the mode is the same as the default mode for the op.
+     * @param uid user id for which we want to set the mode.
+     * @param op app-op for which we want to set the mode.
+     * @param mode mode for the app-op.
+     * @return true if op mode is changed.
+     */
+    boolean setUidMode(int uid, int op, @Mode int mode);
+
+    /**
+     * Gets the app-op mode for a particular package.
+     * Returns default op mode if the op mode for the particular package is not set.
+     * @param packageName package name for which we need the op mode.
+     * @param op app-op for which we need the mode.
+     * @param userId user id associated with the package.
+     * @return the mode of the app-op.
+     */
+    int getPackageMode(@NonNull String packageName, int op, @UserIdInt int userId);
+
+    /**
+     * Sets the app-op mode for a particular package.
+     * @param packageName package name for which we need to set the op mode.
+     * @param op app-op for which we need to set the mode.
+     * @param mode the mode of the app-op.
+     * @param userId user id associated with the package.
+     *
+     */
+    void setPackageMode(@NonNull String packageName, int op, @Mode int mode, @UserIdInt int userId);
+
+    /**
+     * Stop tracking any app-op modes for a package.
+     * @param packageName Name of the package for which we want to remove all mode tracking.
+     * @param userId user id associated with the package.
+     */
+    boolean removePackage(@NonNull String packageName,  @UserIdInt int userId);
+
+    /**
+     * Stop tracking any app-op modes for this uid.
+     * @param uid user id for which we want to remove all tracking.
+     */
+    void removeUid(int uid);
+
+    /**
+     * Returns true if all uid modes for this uid are
+     * in default state.
+     * @param uid user id
+     */
+    boolean areUidModesDefault(int uid);
+
+    /**
+     * Returns true if all package modes for this package name are
+     * in default state.
+     * @param packageName package name.
+     * @param userId user id associated with the package.
+     */
+    boolean arePackageModesDefault(String packageName, @UserIdInt int userId);
+
+    /**
+     * Stop tracking app-op modes for all uid and packages.
+     */
+    void clearAllModes();
+
+    /**
+     * Registers changedListener to listen to op's mode change.
+     * @param changedListener the listener that must be trigger on the op's mode change.
+     * @param op op representing the app-op whose mode change needs to be listened to.
+     */
+    void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener, int op);
+
+    /**
+     * Registers changedListener to listen to package's app-op's mode change.
+     * @param changedListener the listener that must be trigger on the mode change.
+     * @param packageName of the package whose app-op's mode change needs to be listened to.
+     */
+    void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener,
+            @NonNull String packageName);
+
+    /**
+     * Stop the changedListener from triggering on any mode change.
+     * @param changedListener the listener that needs to be removed.
+     */
+    void removeListener(@NonNull OnOpModeChangedListener changedListener);
+
+    /**
+     * Temporary API which will be removed once we can safely untangle the methods that use this.
+     * Returns a set of OnOpModeChangedListener that are listening for op's mode changes.
+     * @param op app-op whose mode change is being listened to.
+     */
+    ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op);
+
+    /**
+     * Temporary API which will be removed once we can safely untangle the methods that use this.
+     * Returns a set of OnOpModeChangedListener that are listening for package's op's mode changes.
+     * @param packageName of package whose app-op's mode change is being listened to.
+     */
+    ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(@NonNull String packageName);
+
+    /**
+     * Temporary API which will be removed once we can safely untangle the methods that use this.
+     * Notify that the app-op's mode is changed by triggering the change listener.
+     * @param op App-op whose mode has changed
+     * @param uid user id associated with the app-op (or, if UID_ANY, notifies all users)
+     */
+    void notifyWatchersOfChange(int op, int uid);
+
+    /**
+     * Temporary API which will be removed once we can safely untangle the methods that use this.
+     * Notify that the app-op's mode is changed by triggering the change listener.
+     * @param changedListener the change listener.
+     * @param op App-op whose mode has changed
+     * @param uid user id associated with the app-op
+     * @param packageName package name that is associated with the app-op
+     */
+    void notifyOpChanged(@NonNull OnOpModeChangedListener changedListener, int op, int uid,
+            @Nullable String packageName);
+
+    /**
+     * Temporary API which will be removed once we can safely untangle the methods that use this.
+     * Notify that the app-op's mode is changed to all packages associated with the uid by
+     * triggering the appropriate change listener.
+     * @param op App-op whose mode has changed
+     * @param uid user id associated with the app-op
+     * @param onlyForeground true if only watchers that
+     * @param callbackToIgnore callback that should be ignored.
+     */
+    void notifyOpChangedForAllPkgsInUid(int op, int uid, boolean onlyForeground,
+            @Nullable OnOpModeChangedListener callbackToIgnore);
+
+    /**
+     * TODO: Move hasForegroundWatchers and foregroundOps into this.
+     * Go over the list of app-ops for the uid and mark app-ops with MODE_FOREGROUND in
+     * foregroundOps.
+     * @param uid for which the app-op's mode needs to be marked.
+     * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
+     * @return  foregroundOps.
+     */
+    SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps);
+
+    /**
+     * Go over the list of app-ops for the package name and mark app-ops with MODE_FOREGROUND in
+     * foregroundOps.
+     * @param packageName for which the app-op's mode needs to be marked.
+     * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
+     * @param userId user id associated with the package.
+     * @return foregroundOps.
+     */
+    SparseBooleanArray evalForegroundPackageOps(String packageName,
+            SparseBooleanArray foregroundOps, @UserIdInt int userId);
+
+    /**
+     * Dump op mode and package mode listeners and their details.
+     * @param dumpOp if -1 then op mode listeners for all app-ops are dumped. If it's set to an
+     *               app-op, only the watchers for that app-op are dumped.
+     * @param dumpUid uid for which we want to dump op mode watchers.
+     * @param dumpPackage if not null and if dumpOp is -1, dumps watchers for the package name.
+     * @param printWriter writer to dump to.
+     */
+    boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter);
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
index adfd2af..af5b07e 100644
--- a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
@@ -42,7 +42,7 @@
 
     private Context mContext;
     private Handler mHandler;
-    private AppOpsServiceInterface mAppOpsServiceInterface;
+    private AppOpsCheckingServiceInterface mAppOpsServiceInterface;
 
     // Map from (Object token) to (int code) to (boolean restricted)
     private final ArrayMap<Object, SparseBooleanArray> mGlobalRestrictions = new ArrayMap<>();
@@ -56,7 +56,7 @@
             mUserRestrictionExcludedPackageTags = new ArrayMap<>();
 
     public AppOpsRestrictionsImpl(Context context, Handler handler,
-            AppOpsServiceInterface appOpsServiceInterface) {
+            AppOpsCheckingServiceInterface appOpsServiceInterface) {
         mContext = context;
         mHandler = handler;
         mAppOpsServiceInterface = appOpsServiceInterface;
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 7e00c32..39338c6 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -18,55 +18,22 @@
 
 import static android.app.AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE;
 import static android.app.AppOpsManager.ATTRIBUTION_FLAG_TRUSTED;
-import static android.app.AppOpsManager.CALL_BACK_ON_SWITCHED_OP;
-import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG;
-import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
-import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
-import static android.app.AppOpsManager.FILTER_BY_UID;
-import static android.app.AppOpsManager.HISTORY_FLAG_GET_ATTRIBUTION_CHAINS;
-import static android.app.AppOpsManager.HistoricalOpsRequestFilter;
-import static android.app.AppOpsManager.KEY_BG_STATE_SETTLE_TIME;
-import static android.app.AppOpsManager.KEY_FG_SERVICE_STATE_SETTLE_TIME;
-import static android.app.AppOpsManager.KEY_TOP_STATE_SETTLE_TIME;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_DEFAULT;
-import static android.app.AppOpsManager.MODE_ERRORED;
-import static android.app.AppOpsManager.MODE_FOREGROUND;
-import static android.app.AppOpsManager.MODE_IGNORED;
-import static android.app.AppOpsManager.OP_CAMERA;
 import static android.app.AppOpsManager.OP_FLAGS_ALL;
 import static android.app.AppOpsManager.OP_FLAG_SELF;
 import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
 import static android.app.AppOpsManager.OP_NONE;
-import static android.app.AppOpsManager.OP_PLAY_AUDIO;
-import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
-import static android.app.AppOpsManager.OP_RECORD_AUDIO;
-import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
-import static android.app.AppOpsManager.OP_VIBRATE;
-import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
-import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
-import static android.app.AppOpsManager.OpEventProxyInfo;
-import static android.app.AppOpsManager.RestrictionBypass;
 import static android.app.AppOpsManager.SAMPLING_STRATEGY_BOOT_TIME_SAMPLING;
 import static android.app.AppOpsManager.SAMPLING_STRATEGY_RARELY_USED;
 import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM;
 import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM_OPS;
-import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE;
 import static android.app.AppOpsManager._NUM_OP;
-import static android.app.AppOpsManager.extractFlagsFromKey;
-import static android.app.AppOpsManager.extractUidStateFromKey;
-import static android.app.AppOpsManager.modeToName;
-import static android.app.AppOpsManager.opAllowSystemBypassRestriction;
 import static android.app.AppOpsManager.opRestrictsRead;
-import static android.app.AppOpsManager.opToName;
 import static android.app.AppOpsManager.opToPublicName;
-import static android.content.Intent.ACTION_PACKAGE_REMOVED;
-import static android.content.Intent.EXTRA_REPLACING;
 import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
 import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
 
-import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
-
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -75,21 +42,16 @@
 import android.app.ActivityManagerInternal;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
-import android.app.AppOpsManager.AttributedOpEntry;
 import android.app.AppOpsManager.AttributionFlags;
 import android.app.AppOpsManager.HistoricalOps;
-import android.app.AppOpsManager.Mode;
-import android.app.AppOpsManager.OpEntry;
 import android.app.AppOpsManager.OpFlags;
 import android.app.AppOpsManagerInternal;
 import android.app.AppOpsManagerInternal.CheckOpsDelegate;
 import android.app.AsyncNotedAppOp;
 import android.app.RuntimeAppOpAccessMessage;
 import android.app.SyncNotedAppOp;
-import android.app.admin.DevicePolicyManagerInternal;
 import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -97,15 +59,11 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PermissionInfo;
-import android.database.ContentObserver;
 import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION;
-import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.PackageTagsList;
 import android.os.Process;
@@ -116,22 +74,14 @@
 import android.os.ServiceManager;
 import android.os.ShellCallback;
 import android.os.ShellCommand;
-import android.os.SystemClock;
 import android.os.UserHandle;
-import android.os.storage.StorageManagerInternal;
-import android.permission.PermissionManager;
-import android.provider.Settings;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.AtomicFile;
-import android.util.KeyValueListParser;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
-import android.util.Xml;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.Immutable;
@@ -143,59 +93,37 @@
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.app.IAppOpsStartedCallback;
 import com.android.internal.app.MessageSamplingConfig;
-import com.android.internal.compat.IPlatformCompat;
-import com.android.internal.os.Clock;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
-import com.android.internal.util.XmlUtils;
 import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.LocalServices;
-import com.android.server.LockGuard;
-import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemServiceManager;
 import com.android.server.pm.PackageList;
-import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.component.ParsedAttribution;
 import com.android.server.policy.AppOpsPolicy;
 
-import dalvik.annotation.optimization.NeverCompile;
-
-import libcore.util.EmptyArray;
-
 import org.json.JSONException;
 import org.json.JSONObject;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.File;
 import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.Scanner;
-import java.util.Set;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.function.Consumer;
 
-public class AppOpsService extends IAppOpsService.Stub implements PersistenceScheduler {
+/**
+ * The system service component to {@link AppOpsManager}.
+ */
+public class AppOpsService extends IAppOpsService.Stub {
+
+    private final AppOpsServiceInterface mAppOpsService;
+
     static final String TAG = "AppOps";
     static final boolean DEBUG = false;
 
@@ -204,57 +132,19 @@
      */
     private final ArraySet<NoteOpTrace> mNoteOpCallerStacktraces = new ArraySet<>();
 
-    private static final int NO_VERSION = -1;
-    /** Increment by one every time and add the corresponding upgrade logic in
-     *  {@link #upgradeLocked(int)} below. The first version was 1 */
-    private static final int CURRENT_VERSION = 1;
-
-    // Write at most every 30 minutes.
-    static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000;
-
     // Constant meaning that any UID should be matched when dispatching callbacks
     private static final int UID_ANY = -2;
 
-    private static final int[] OPS_RESTRICTED_ON_SUSPEND = {
-            OP_PLAY_AUDIO,
-            OP_RECORD_AUDIO,
-            OP_CAMERA,
-            OP_VIBRATE,
-    };
-
     private static final int MAX_UNFORWARDED_OPS = 10;
-    private static final int MAX_UNUSED_POOLED_OBJECTS = 3;
+
     private static final int RARELY_USED_PACKAGES_INITIALIZATION_DELAY_MILLIS = 300000;
 
     final Context mContext;
-    final AtomicFile mFile;
     private final @Nullable File mNoteOpCallerStacktracesFile;
     final Handler mHandler;
 
-    /**
-     * Pool for {@link AttributedOp.OpEventProxyInfoPool} to avoid to constantly reallocate new
-     * objects
-     */
-    @GuardedBy("this")
-    final AttributedOp.OpEventProxyInfoPool mOpEventProxyInfoPool =
-            new AttributedOp.OpEventProxyInfoPool(MAX_UNUSED_POOLED_OBJECTS);
-
-    /**
-     * Pool for {@link AttributedOp.InProgressStartOpEventPool} to avoid to constantly reallocate
-     * new objects
-     */
-    @GuardedBy("this")
-    final AttributedOp.InProgressStartOpEventPool mInProgressStartOpEventPool =
-            new AttributedOp.InProgressStartOpEventPool(mOpEventProxyInfoPool,
-                    MAX_UNUSED_POOLED_OBJECTS);
-
     private final AppOpsManagerInternalImpl mAppOpsManagerInternal
             = new AppOpsManagerInternalImpl();
-    @Nullable private final DevicePolicyManagerInternal dpmi =
-            LocalServices.getService(DevicePolicyManagerInternal.class);
-
-    private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface(
-            ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
 
     /**
      * Registered callbacks, called from {@link #collectAsyncNotedOp}.
@@ -281,54 +171,9 @@
 
     boolean mWriteNoteOpsScheduled;
 
-    boolean mWriteScheduled;
-    boolean mFastWriteScheduled;
-    final Runnable mWriteRunner = new Runnable() {
-        public void run() {
-            synchronized (AppOpsService.this) {
-                mWriteScheduled = false;
-                mFastWriteScheduled = false;
-                AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
-                    @Override protected Void doInBackground(Void... params) {
-                        writeState();
-                        return null;
-                    }
-                };
-                task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null);
-            }
-        }
-    };
-
-    @GuardedBy("this")
-    @VisibleForTesting
-    final SparseArray<UidState> mUidStates = new SparseArray<>();
-
-    volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this);
-
-    /*
-     * These are app op restrictions imposed per user from various parties.
-     */
-    private final ArrayMap<IBinder, ClientUserRestrictionState> mOpUserRestrictions =
-            new ArrayMap<>();
-
-    /*
-     * These are app op restrictions imposed globally from various parties within the system.
-     */
-    private final ArrayMap<IBinder, ClientGlobalRestrictionState> mOpGlobalRestrictions =
-            new ArrayMap<>();
-
-    SparseIntArray mProfileOwners;
-
     private volatile CheckOpsDelegateDispatcher mCheckOpsDelegateDispatcher =
             new CheckOpsDelegateDispatcher(/*policy*/ null, /*delegate*/ null);
 
-    /**
-      * Reverse lookup for {@link AppOpsManager#opToSwitch(int)}. Initialized once and never
-      * changed
-      */
-    private final SparseArray<int[]> mSwitchedOps = new SparseArray<>();
-
-    private ActivityManagerInternal mActivityManagerInternal;
 
     /** Package sampled for message collection in the current session */
     @GuardedBy("this")
@@ -362,545 +207,8 @@
     /** Package Manager internal. Access via {@link #getPackageManagerInternal()} */
     private @Nullable PackageManagerInternal mPackageManagerInternal;
 
-    /** Interface for app-op modes.*/
-    @VisibleForTesting AppOpsServiceInterface mAppOpsServiceInterface;
-
-    /** Interface for app-op restrictions.*/
-    @VisibleForTesting AppOpsRestrictions mAppOpsRestrictions;
-
-    private AppOpsUidStateTracker mUidStateTracker;
-
-    /** Hands the definition of foreground and uid states */
-    @GuardedBy("this")
-    public AppOpsUidStateTracker getUidStateTracker() {
-        if (mUidStateTracker == null) {
-            mUidStateTracker = new AppOpsUidStateTrackerImpl(
-                    LocalServices.getService(ActivityManagerInternal.class),
-                    mHandler,
-                    r -> {
-                        synchronized (AppOpsService.this) {
-                            r.run();
-                        }
-                    },
-                    Clock.SYSTEM_CLOCK, mConstants);
-
-            mUidStateTracker.addUidStateChangedCallback(new HandlerExecutor(mHandler),
-                    this::onUidStateChanged);
-        }
-        return mUidStateTracker;
-    }
-
-    /**
-     * All times are in milliseconds. These constants are kept synchronized with the system
-     * global Settings. Any access to this class or its fields should be done while
-     * holding the AppOpsService lock.
-     */
-    final class Constants extends ContentObserver {
-
-        /**
-         * How long we want for a drop in uid state from top to settle before applying it.
-         * @see Settings.Global#APP_OPS_CONSTANTS
-         * @see AppOpsManager#KEY_TOP_STATE_SETTLE_TIME
-         */
-        public long TOP_STATE_SETTLE_TIME;
-
-        /**
-         * How long we want for a drop in uid state from foreground to settle before applying it.
-         * @see Settings.Global#APP_OPS_CONSTANTS
-         * @see AppOpsManager#KEY_FG_SERVICE_STATE_SETTLE_TIME
-         */
-        public long FG_SERVICE_STATE_SETTLE_TIME;
-
-        /**
-         * How long we want for a drop in uid state from background to settle before applying it.
-         * @see Settings.Global#APP_OPS_CONSTANTS
-         * @see AppOpsManager#KEY_BG_STATE_SETTLE_TIME
-         */
-        public long BG_STATE_SETTLE_TIME;
-
-        private final KeyValueListParser mParser = new KeyValueListParser(',');
-        private ContentResolver mResolver;
-
-        public Constants(Handler handler) {
-            super(handler);
-            updateConstants();
-        }
-
-        public void startMonitoring(ContentResolver resolver) {
-            mResolver = resolver;
-            mResolver.registerContentObserver(
-                    Settings.Global.getUriFor(Settings.Global.APP_OPS_CONSTANTS),
-                    false, this);
-            updateConstants();
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            updateConstants();
-        }
-
-        private void updateConstants() {
-            String value = mResolver != null ? Settings.Global.getString(mResolver,
-                    Settings.Global.APP_OPS_CONSTANTS) : "";
-
-            synchronized (AppOpsService.this) {
-                try {
-                    mParser.setString(value);
-                } catch (IllegalArgumentException e) {
-                    // Failed to parse the settings string, log this and move on
-                    // with defaults.
-                    Slog.e(TAG, "Bad app ops settings", e);
-                }
-                TOP_STATE_SETTLE_TIME = mParser.getDurationMillis(
-                        KEY_TOP_STATE_SETTLE_TIME, 5 * 1000L);
-                FG_SERVICE_STATE_SETTLE_TIME = mParser.getDurationMillis(
-                        KEY_FG_SERVICE_STATE_SETTLE_TIME, 5 * 1000L);
-                BG_STATE_SETTLE_TIME = mParser.getDurationMillis(
-                        KEY_BG_STATE_SETTLE_TIME, 1 * 1000L);
-            }
-        }
-
-        void dump(PrintWriter pw) {
-            pw.println("  Settings:");
-
-            pw.print("    "); pw.print(KEY_TOP_STATE_SETTLE_TIME); pw.print("=");
-            TimeUtils.formatDuration(TOP_STATE_SETTLE_TIME, pw);
-            pw.println();
-            pw.print("    "); pw.print(KEY_FG_SERVICE_STATE_SETTLE_TIME); pw.print("=");
-            TimeUtils.formatDuration(FG_SERVICE_STATE_SETTLE_TIME, pw);
-            pw.println();
-            pw.print("    "); pw.print(KEY_BG_STATE_SETTLE_TIME); pw.print("=");
-            TimeUtils.formatDuration(BG_STATE_SETTLE_TIME, pw);
-            pw.println();
-        }
-    }
-
-    @VisibleForTesting
-    final Constants mConstants;
-
-    @VisibleForTesting
-    final class UidState {
-        public final int uid;
-
-        public ArrayMap<String, Ops> pkgOps;
-
-        // true indicates there is an interested observer, false there isn't but it has such an op
-        //TODO: Move foregroundOps and hasForegroundWatchers into the AppOpsServiceInterface.
-        public SparseBooleanArray foregroundOps;
-        public boolean hasForegroundWatchers;
-
-        public UidState(int uid) {
-            this.uid = uid;
-        }
-
-        public void clear() {
-            mAppOpsServiceInterface.removeUid(uid);
-            if (pkgOps != null) {
-                for (String packageName : pkgOps.keySet()) {
-                    mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
-                }
-            }
-            pkgOps = null;
-        }
-
-        public boolean isDefault() {
-            boolean areAllPackageModesDefault = true;
-            if (pkgOps != null) {
-                for (String packageName : pkgOps.keySet()) {
-                    if (!mAppOpsServiceInterface.arePackageModesDefault(packageName,
-                            UserHandle.getUserId(uid))) {
-                        areAllPackageModesDefault = false;
-                        break;
-                    }
-                }
-            }
-            return (pkgOps == null || pkgOps.isEmpty())
-                    && mAppOpsServiceInterface.areUidModesDefault(uid)
-                    && areAllPackageModesDefault;
-        }
-
-        // Functions for uid mode access and manipulation.
-        public SparseIntArray getNonDefaultUidModes() {
-            return mAppOpsServiceInterface.getNonDefaultUidModes(uid);
-        }
-
-        public int getUidMode(int op) {
-            return mAppOpsServiceInterface.getUidMode(uid, op);
-        }
-
-        public boolean setUidMode(int op, int mode) {
-            return mAppOpsServiceInterface.setUidMode(uid, op, mode);
-        }
-
-        @SuppressWarnings("GuardedBy")
-        int evalMode(int op, int mode) {
-            return getUidStateTracker().evalMode(uid, op, mode);
-        }
-
-        public void evalForegroundOps() {
-            foregroundOps = null;
-            foregroundOps = mAppOpsServiceInterface.evalForegroundUidOps(uid, foregroundOps);
-            if (pkgOps != null) {
-                for (int i = pkgOps.size() - 1; i >= 0; i--) {
-                    foregroundOps = mAppOpsServiceInterface
-                            .evalForegroundPackageOps(pkgOps.valueAt(i).packageName, foregroundOps,
-                                    UserHandle.getUserId(uid));
-                }
-            }
-            hasForegroundWatchers = false;
-            if (foregroundOps != null) {
-                for (int i = 0;  i < foregroundOps.size(); i++) {
-                    if (foregroundOps.valueAt(i)) {
-                        hasForegroundWatchers = true;
-                        break;
-                    }
-                }
-            }
-        }
-
-        @SuppressWarnings("GuardedBy")
-        public int getState() {
-            return getUidStateTracker().getUidState(uid);
-        }
-
-        @SuppressWarnings("GuardedBy")
-        public void dump(PrintWriter pw, long nowElapsed) {
-            getUidStateTracker().dumpUidState(pw, uid, nowElapsed);
-        }
-    }
-
-    final static class Ops extends SparseArray<Op> {
-        final String packageName;
-        final UidState uidState;
-
-        /**
-         * The restriction properties of the package. If {@code null} it could not have been read
-         * yet and has to be refreshed.
-         */
-        @Nullable RestrictionBypass bypass;
-
-        /** Lazily populated cache of attributionTags of this package */
-        final @NonNull ArraySet<String> knownAttributionTags = new ArraySet<>();
-
-        /**
-         * Lazily populated cache of <b>valid</b> attributionTags of this package, a set smaller
-         * than or equal to {@link #knownAttributionTags}.
-         */
-        final @NonNull ArraySet<String> validAttributionTags = new ArraySet<>();
-
-        Ops(String _packageName, UidState _uidState) {
-            packageName = _packageName;
-            uidState = _uidState;
-        }
-    }
-
-    /** Returned from {@link #verifyAndGetBypass(int, String, String, String)}. */
-    private static final class PackageVerificationResult {
-
-        final RestrictionBypass bypass;
-        final boolean isAttributionTagValid;
-
-        PackageVerificationResult(RestrictionBypass bypass, boolean isAttributionTagValid) {
-            this.bypass = bypass;
-            this.isAttributionTagValid = isAttributionTagValid;
-        }
-    }
-
-    final class Op {
-        int op;
-        int uid;
-        final UidState uidState;
-        final @NonNull String packageName;
-
-        /** attributionTag -> AttributedOp */
-        final ArrayMap<String, AttributedOp> mAttributions = new ArrayMap<>(1);
-
-        Op(UidState uidState, String packageName, int op, int uid) {
-            this.op = op;
-            this.uid = uid;
-            this.uidState = uidState;
-            this.packageName = packageName;
-        }
-
-        @Mode int getMode() {
-            return mAppOpsServiceInterface.getPackageMode(packageName, this.op,
-                    UserHandle.getUserId(this.uid));
-        }
-        void setMode(@Mode int mode) {
-            mAppOpsServiceInterface.setPackageMode(packageName, this.op, mode,
-                    UserHandle.getUserId(this.uid));
-        }
-
-        void removeAttributionsWithNoTime() {
-            for (int i = mAttributions.size() - 1; i >= 0; i--) {
-                if (!mAttributions.valueAt(i).hasAnyTime()) {
-                    mAttributions.removeAt(i);
-                }
-            }
-        }
-
-        private @NonNull AttributedOp getOrCreateAttribution(@NonNull Op parent,
-                @Nullable String attributionTag) {
-            AttributedOp attributedOp;
-
-            attributedOp = mAttributions.get(attributionTag);
-            if (attributedOp == null) {
-                attributedOp = new AttributedOp(AppOpsService.this, attributionTag, parent);
-                mAttributions.put(attributionTag, attributedOp);
-            }
-
-            return attributedOp;
-        }
-
-        @NonNull OpEntry createEntryLocked() {
-            final int numAttributions = mAttributions.size();
-
-            final ArrayMap<String, AppOpsManager.AttributedOpEntry> attributionEntries =
-                    new ArrayMap<>(numAttributions);
-            for (int i = 0; i < numAttributions; i++) {
-                attributionEntries.put(mAttributions.keyAt(i),
-                        mAttributions.valueAt(i).createAttributedOpEntryLocked());
-            }
-
-            return new OpEntry(op, getMode(), attributionEntries);
-        }
-
-        @NonNull OpEntry createSingleAttributionEntryLocked(@Nullable String attributionTag) {
-            final int numAttributions = mAttributions.size();
-
-            final ArrayMap<String, AttributedOpEntry> attributionEntries = new ArrayMap<>(1);
-            for (int i = 0; i < numAttributions; i++) {
-                if (Objects.equals(mAttributions.keyAt(i), attributionTag)) {
-                    attributionEntries.put(mAttributions.keyAt(i),
-                            mAttributions.valueAt(i).createAttributedOpEntryLocked());
-                    break;
-                }
-            }
-
-            return new OpEntry(op, getMode(), attributionEntries);
-        }
-
-        boolean isRunning() {
-            final int numAttributions = mAttributions.size();
-            for (int i = 0; i < numAttributions; i++) {
-                if (mAttributions.valueAt(i).isRunning()) {
-                    return true;
-                }
-            }
-
-            return false;
-        }
-    }
-
-    final ArrayMap<IBinder, ModeCallback> mModeWatchers = new ArrayMap<>();
-    final ArrayMap<IBinder, SparseArray<ActiveCallback>> mActiveWatchers = new ArrayMap<>();
-    final ArrayMap<IBinder, SparseArray<StartedCallback>> mStartedWatchers = new ArrayMap<>();
-    final ArrayMap<IBinder, SparseArray<NotedCallback>> mNotedWatchers = new ArrayMap<>();
     final AudioRestrictionManager mAudioRestrictionManager = new AudioRestrictionManager();
 
-    final class ModeCallback extends OnOpModeChangedListener implements DeathRecipient  {
-        /** If mWatchedOpCode==ALL_OPS notify for ops affected by the switch-op */
-        public static final int ALL_OPS = -2;
-
-        // Need to keep this only because stopWatchingMode needs an IAppOpsCallback.
-        // Otherwise we can just use the IBinder object.
-        private final IAppOpsCallback mCallback;
-
-        ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOpCode,
-                int callingUid, int callingPid) {
-            super(watchingUid, flags, watchedOpCode, callingUid, callingPid);
-            this.mCallback = callback;
-            try {
-                mCallback.asBinder().linkToDeath(this, 0);
-            } catch (RemoteException e) {
-                /*ignored*/
-            }
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("ModeCallback{");
-            sb.append(Integer.toHexString(System.identityHashCode(this)));
-            sb.append(" watchinguid=");
-            UserHandle.formatUid(sb, getWatchingUid());
-            sb.append(" flags=0x");
-            sb.append(Integer.toHexString(getFlags()));
-            switch (getWatchedOpCode()) {
-                case OP_NONE:
-                    break;
-                case ALL_OPS:
-                    sb.append(" op=(all)");
-                    break;
-                default:
-                    sb.append(" op=");
-                    sb.append(opToName(getWatchedOpCode()));
-                    break;
-            }
-            sb.append(" from uid=");
-            UserHandle.formatUid(sb, getCallingUid());
-            sb.append(" pid=");
-            sb.append(getCallingPid());
-            sb.append('}');
-            return sb.toString();
-        }
-
-        void unlinkToDeath() {
-            mCallback.asBinder().unlinkToDeath(this, 0);
-        }
-
-        @Override
-        public void binderDied() {
-            stopWatchingMode(mCallback);
-        }
-
-        @Override
-        public void onOpModeChanged(int op, int uid, String packageName) throws RemoteException {
-            mCallback.opChanged(op, uid, packageName);
-        }
-    }
-
-    final class ActiveCallback implements DeathRecipient {
-        final IAppOpsActiveCallback mCallback;
-        final int mWatchingUid;
-        final int mCallingUid;
-        final int mCallingPid;
-
-        ActiveCallback(IAppOpsActiveCallback callback, int watchingUid, int callingUid,
-                int callingPid) {
-            mCallback = callback;
-            mWatchingUid = watchingUid;
-            mCallingUid = callingUid;
-            mCallingPid = callingPid;
-            try {
-                mCallback.asBinder().linkToDeath(this, 0);
-            } catch (RemoteException e) {
-                /*ignored*/
-            }
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("ActiveCallback{");
-            sb.append(Integer.toHexString(System.identityHashCode(this)));
-            sb.append(" watchinguid=");
-            UserHandle.formatUid(sb, mWatchingUid);
-            sb.append(" from uid=");
-            UserHandle.formatUid(sb, mCallingUid);
-            sb.append(" pid=");
-            sb.append(mCallingPid);
-            sb.append('}');
-            return sb.toString();
-        }
-
-        void destroy() {
-            mCallback.asBinder().unlinkToDeath(this, 0);
-        }
-
-        @Override
-        public void binderDied() {
-            stopWatchingActive(mCallback);
-        }
-    }
-
-    final class StartedCallback implements DeathRecipient {
-        final IAppOpsStartedCallback mCallback;
-        final int mWatchingUid;
-        final int mCallingUid;
-        final int mCallingPid;
-
-        StartedCallback(IAppOpsStartedCallback callback, int watchingUid, int callingUid,
-                int callingPid) {
-            mCallback = callback;
-            mWatchingUid = watchingUid;
-            mCallingUid = callingUid;
-            mCallingPid = callingPid;
-            try {
-                mCallback.asBinder().linkToDeath(this, 0);
-            } catch (RemoteException e) {
-                /*ignored*/
-            }
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("StartedCallback{");
-            sb.append(Integer.toHexString(System.identityHashCode(this)));
-            sb.append(" watchinguid=");
-            UserHandle.formatUid(sb, mWatchingUid);
-            sb.append(" from uid=");
-            UserHandle.formatUid(sb, mCallingUid);
-            sb.append(" pid=");
-            sb.append(mCallingPid);
-            sb.append('}');
-            return sb.toString();
-        }
-
-        void destroy() {
-            mCallback.asBinder().unlinkToDeath(this, 0);
-        }
-
-        @Override
-        public void binderDied() {
-            stopWatchingStarted(mCallback);
-        }
-    }
-
-    final class NotedCallback implements DeathRecipient {
-        final IAppOpsNotedCallback mCallback;
-        final int mWatchingUid;
-        final int mCallingUid;
-        final int mCallingPid;
-
-        NotedCallback(IAppOpsNotedCallback callback, int watchingUid, int callingUid,
-                int callingPid) {
-            mCallback = callback;
-            mWatchingUid = watchingUid;
-            mCallingUid = callingUid;
-            mCallingPid = callingPid;
-            try {
-                mCallback.asBinder().linkToDeath(this, 0);
-            } catch (RemoteException e) {
-                /*ignored*/
-            }
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("NotedCallback{");
-            sb.append(Integer.toHexString(System.identityHashCode(this)));
-            sb.append(" watchinguid=");
-            UserHandle.formatUid(sb, mWatchingUid);
-            sb.append(" from uid=");
-            UserHandle.formatUid(sb, mCallingUid);
-            sb.append(" pid=");
-            sb.append(mCallingPid);
-            sb.append('}');
-            return sb.toString();
-        }
-
-        void destroy() {
-            mCallback.asBinder().unlinkToDeath(this, 0);
-        }
-
-        @Override
-        public void binderDied() {
-            stopWatchingNoted(mCallback);
-        }
-    }
-
-    /**
-     * Call {@link AttributedOp#onClientDeath attributedOp.onClientDeath(clientId)}.
-     */
-    static void onClientDeath(@NonNull AttributedOp attributedOp,
-            @NonNull IBinder clientId) {
-        attributedOp.onClientDeath(clientId);
-    }
-
-
     /**
      * Loads the OpsValidation file results into a hashmap {@link #mNoteOpCallerStacktraces}
      * so that we do not log the same operation twice between instances
@@ -925,20 +233,12 @@
     }
 
     public AppOpsService(File storagePath, Handler handler, Context context) {
-        mContext = context;
+        this(handler, context, new AppOpsServiceImpl(storagePath, handler, context));
+    }
 
-        for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) {
-            int switchCode = AppOpsManager.opToSwitch(switchedCode);
-            mSwitchedOps.put(switchCode,
-                    ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode));
-        }
-        mAppOpsServiceInterface =
-                new LegacyAppOpsServiceInterfaceImpl(this, this, handler, context, mSwitchedOps);
-        mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler,
-                mAppOpsServiceInterface);
-
-        LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
-        mFile = new AtomicFile(storagePath, "appops");
+    @VisibleForTesting
+    public AppOpsService(Handler handler, Context context,
+            AppOpsServiceInterface appOpsServiceInterface) {
         if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED) {
             mNoteOpCallerStacktracesFile = new File(SystemServiceManager.ensureSystemDir(),
                     "noteOpStackTraces.json");
@@ -946,185 +246,25 @@
         } else {
             mNoteOpCallerStacktracesFile = null;
         }
+
+        mAppOpsService = appOpsServiceInterface;
+        mContext = context;
         mHandler = handler;
-        mConstants = new Constants(mHandler);
-        readState();
     }
 
+    /**
+     * Publishes binder and local service.
+     */
     public void publish() {
         ServiceManager.addService(Context.APP_OPS_SERVICE, asBinder());
         LocalServices.addService(AppOpsManagerInternal.class, mAppOpsManagerInternal);
     }
 
-    /** Handler for work when packages are removed or updated */
-    private BroadcastReceiver mOnPackageUpdatedReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            String pkgName = intent.getData().getEncodedSchemeSpecificPart();
-            int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
-
-            if (action.equals(ACTION_PACKAGE_REMOVED) && !intent.hasExtra(EXTRA_REPLACING)) {
-                synchronized (AppOpsService.this) {
-                    UidState uidState = mUidStates.get(uid);
-                    if (uidState == null || uidState.pkgOps == null) {
-                        return;
-                    }
-                    mAppOpsServiceInterface.removePackage(pkgName, UserHandle.getUserId(uid));
-                    Ops removedOps = uidState.pkgOps.remove(pkgName);
-                    if (removedOps != null) {
-                        scheduleFastWriteLocked();
-                    }
-                }
-            } else if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) {
-                AndroidPackage pkg = getPackageManagerInternal().getPackage(pkgName);
-                if (pkg == null) {
-                    return;
-                }
-
-                ArrayMap<String, String> dstAttributionTags = new ArrayMap<>();
-                ArraySet<String> attributionTags = new ArraySet<>();
-                attributionTags.add(null);
-                if (pkg.getAttributions() != null) {
-                    int numAttributions = pkg.getAttributions().size();
-                    for (int attributionNum = 0; attributionNum < numAttributions;
-                            attributionNum++) {
-                        ParsedAttribution attribution = pkg.getAttributions().get(attributionNum);
-                        attributionTags.add(attribution.getTag());
-
-                        int numInheritFrom = attribution.getInheritFrom().size();
-                        for (int inheritFromNum = 0; inheritFromNum < numInheritFrom;
-                                inheritFromNum++) {
-                            dstAttributionTags.put(attribution.getInheritFrom().get(inheritFromNum),
-                                    attribution.getTag());
-                        }
-                    }
-                }
-
-                synchronized (AppOpsService.this) {
-                    UidState uidState = mUidStates.get(uid);
-                    if (uidState == null || uidState.pkgOps == null) {
-                        return;
-                    }
-
-                    Ops ops = uidState.pkgOps.get(pkgName);
-                    if (ops == null) {
-                        return;
-                    }
-
-                    // Reset cached package properties to re-initialize when needed
-                    ops.bypass = null;
-                    ops.knownAttributionTags.clear();
-
-                    // Merge data collected for removed attributions into their successor
-                    // attributions
-                    int numOps = ops.size();
-                    for (int opNum = 0; opNum < numOps; opNum++) {
-                        Op op = ops.valueAt(opNum);
-
-                        int numAttributions = op.mAttributions.size();
-                        for (int attributionNum = numAttributions - 1; attributionNum >= 0;
-                                attributionNum--) {
-                            String attributionTag = op.mAttributions.keyAt(attributionNum);
-
-                            if (attributionTags.contains(attributionTag)) {
-                                // attribution still exist after upgrade
-                                continue;
-                            }
-
-                            String newAttributionTag = dstAttributionTags.get(attributionTag);
-
-                            AttributedOp newAttributedOp = op.getOrCreateAttribution(op,
-                                    newAttributionTag);
-                            newAttributedOp.add(op.mAttributions.valueAt(attributionNum));
-                            op.mAttributions.removeAt(attributionNum);
-
-                            scheduleFastWriteLocked();
-                        }
-                    }
-                }
-            }
-        }
-    };
-
+    /**
+     * Finishes boot sequence.
+     */
     public void systemReady() {
-        mConstants.startMonitoring(mContext.getContentResolver());
-        mHistoricalRegistry.systemReady(mContext.getContentResolver());
-
-        IntentFilter packageUpdateFilter = new IntentFilter();
-        packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
-        packageUpdateFilter.addDataScheme("package");
-
-        mContext.registerReceiverAsUser(mOnPackageUpdatedReceiver, UserHandle.ALL,
-                packageUpdateFilter, null, null);
-
-        synchronized (this) {
-            for (int uidNum = mUidStates.size() - 1; uidNum >= 0; uidNum--) {
-                int uid = mUidStates.keyAt(uidNum);
-                UidState uidState = mUidStates.valueAt(uidNum);
-
-                String[] pkgsInUid = getPackagesForUid(uidState.uid);
-                if (ArrayUtils.isEmpty(pkgsInUid)) {
-                    uidState.clear();
-                    mUidStates.removeAt(uidNum);
-                    scheduleFastWriteLocked();
-                    continue;
-                }
-
-                ArrayMap<String, Ops> pkgs = uidState.pkgOps;
-                if (pkgs == null) {
-                    continue;
-                }
-
-                int numPkgs = pkgs.size();
-                for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
-                    String pkg = pkgs.keyAt(pkgNum);
-
-                    String action;
-                    if (!ArrayUtils.contains(pkgsInUid, pkg)) {
-                        action = Intent.ACTION_PACKAGE_REMOVED;
-                    } else {
-                        action = Intent.ACTION_PACKAGE_REPLACED;
-                    }
-
-                    SystemServerInitThreadPool.submit(
-                            () -> mOnPackageUpdatedReceiver.onReceive(mContext, new Intent(action)
-                                    .setData(Uri.fromParts("package", pkg, null))
-                                    .putExtra(Intent.EXTRA_UID, uid)),
-                            "Update app-ops uidState in case package " + pkg + " changed");
-                }
-            }
-        }
-
-        final IntentFilter packageSuspendFilter = new IntentFilter();
-        packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
-        packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
-        mContext.registerReceiverAsUser(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                final int[] changedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
-                final String[] changedPkgs = intent.getStringArrayExtra(
-                        Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                for (int code : OPS_RESTRICTED_ON_SUSPEND) {
-                    ArraySet<OnOpModeChangedListener> onModeChangedListeners;
-                    synchronized (AppOpsService.this) {
-                        onModeChangedListeners =
-                                mAppOpsServiceInterface.getOpModeChangedListeners(code);
-                        if (onModeChangedListeners == null) {
-                            continue;
-                        }
-                    }
-                    for (int i = 0; i < changedUids.length; i++) {
-                        final int changedUid = changedUids[i];
-                        final String changedPkg = changedPkgs[i];
-                        // We trust packagemanager to insert matching uid and packageNames in the
-                        // extras
-                        notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg);
-                    }
-                }
-            }
-        }, UserHandle.ALL, packageSuspendFilter, null, null);
+        mAppOpsService.systemReady();
 
         final IntentFilter packageAddedFilter = new IntentFilter();
         packageAddedFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
@@ -1132,9 +272,8 @@
         mContext.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
-                final Uri data = intent.getData();
 
-                final String packageName = data.getSchemeSpecificPart();
+                final String packageName = intent.getData().getSchemeSpecificPart();
                 PackageInfo pi = getPackageManagerInternal().getPackageInfo(packageName,
                         PackageManager.GET_PERMISSIONS, Process.myUid(), mContext.getUserId());
                 if (isSamplingTarget(pi)) {
@@ -1169,8 +308,6 @@
                         }
                     }
                 });
-
-        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
     }
 
     /**
@@ -1185,132 +322,18 @@
         mCheckOpsDelegateDispatcher = new CheckOpsDelegateDispatcher(policy, delegate);
     }
 
+    /**
+     * Notify when a package is removed
+     */
     public void packageRemoved(int uid, String packageName) {
-        synchronized (this) {
-            UidState uidState = mUidStates.get(uid);
-            if (uidState == null) {
-                return;
-            }
-
-            Ops removedOps = null;
-
-            // Remove any package state if such.
-            if (uidState.pkgOps != null) {
-                removedOps = uidState.pkgOps.remove(packageName);
-                mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
-            }
-
-            // If we just nuked the last package state check if the UID is valid.
-            if (removedOps != null && uidState.pkgOps.isEmpty()
-                    && getPackagesForUid(uid).length <= 0) {
-                uidState.clear();
-                mUidStates.remove(uid);
-            }
-
-            if (removedOps != null) {
-                scheduleFastWriteLocked();
-
-                final int numOps = removedOps.size();
-                for (int opNum = 0; opNum < numOps; opNum++) {
-                    final Op op = removedOps.valueAt(opNum);
-
-                    final int numAttributions = op.mAttributions.size();
-                    for (int attributionNum = 0; attributionNum < numAttributions;
-                            attributionNum++) {
-                        AttributedOp attributedOp = op.mAttributions.valueAt(attributionNum);
-
-                        while (attributedOp.isRunning()) {
-                            attributedOp.finished(attributedOp.mInProgressEvents.keyAt(0));
-                        }
-                        while (attributedOp.isPaused()) {
-                            attributedOp.finished(attributedOp.mPausedInProgressEvents.keyAt(0));
-                        }
-                    }
-                }
-            }
-        }
-
-        mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::clearHistory,
-                    mHistoricalRegistry, uid, packageName));
+        mAppOpsService.packageRemoved(uid, packageName);
     }
 
+    /**
+     * Notify when a uid is removed.
+     */
     public void uidRemoved(int uid) {
-        synchronized (this) {
-            if (mUidStates.indexOfKey(uid) >= 0) {
-                mUidStates.get(uid).clear();
-                mUidStates.remove(uid);
-                scheduleFastWriteLocked();
-            }
-        }
-    }
-
-    // The callback method from ForegroundPolicyInterface
-    private void onUidStateChanged(int uid, int state, boolean foregroundModeMayChange) {
-        synchronized (this) {
-            UidState uidState = getUidStateLocked(uid, true);
-
-            if (uidState != null && foregroundModeMayChange && uidState.hasForegroundWatchers) {
-                for (int fgi = uidState.foregroundOps.size() - 1; fgi >= 0; fgi--) {
-                    if (!uidState.foregroundOps.valueAt(fgi)) {
-                        continue;
-                    }
-                    final int code = uidState.foregroundOps.keyAt(fgi);
-
-                    if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)
-                            && uidState.getUidMode(code) == AppOpsManager.MODE_FOREGROUND) {
-                        mHandler.sendMessage(PooledLambda.obtainMessage(
-                                AppOpsService::notifyOpChangedForAllPkgsInUid,
-                                this, code, uidState.uid, true, null));
-                    } else if (uidState.pkgOps != null) {
-                        final ArraySet<OnOpModeChangedListener> listenerSet =
-                                mAppOpsServiceInterface.getOpModeChangedListeners(code);
-                        if (listenerSet != null) {
-                            for (int cbi = listenerSet.size() - 1; cbi >= 0; cbi--) {
-                                final OnOpModeChangedListener listener = listenerSet.valueAt(cbi);
-                                if ((listener.getFlags()
-                                        & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0
-                                        || !listener.isWatchingUid(uidState.uid)) {
-                                    continue;
-                                }
-                                for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) {
-                                    final Op op = uidState.pkgOps.valueAt(pkgi).get(code);
-                                    if (op == null) {
-                                        continue;
-                                    }
-                                    if (op.getMode() == AppOpsManager.MODE_FOREGROUND) {
-                                        mHandler.sendMessage(PooledLambda.obtainMessage(
-                                                AppOpsService::notifyOpChanged,
-                                                this, listenerSet.valueAt(cbi), code, uidState.uid,
-                                                uidState.pkgOps.keyAt(pkgi)));
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-
-            if (uidState != null && uidState.pkgOps != null) {
-                int numPkgs = uidState.pkgOps.size();
-                for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
-                    Ops ops = uidState.pkgOps.valueAt(pkgNum);
-
-                    int numOps = ops.size();
-                    for (int opNum = 0; opNum < numOps; opNum++) {
-                        Op op = ops.valueAt(opNum);
-
-                        int numAttributions = op.mAttributions.size();
-                        for (int attributionNum = 0; attributionNum < numAttributions;
-                                attributionNum++) {
-                            AttributedOp attributedOp = op.mAttributions.valueAt(
-                                    attributionNum);
-
-                            attributedOp.onUidStateChanged(state);
-                        }
-                    }
-                }
-            }
-        }
+        mAppOpsService.uidRemoved(uid);
     }
 
     /**
@@ -1318,542 +341,60 @@
      */
     public void updateUidProcState(int uid, int procState,
             @ActivityManager.ProcessCapability int capability) {
-        synchronized (this) {
-            getUidStateTracker().updateUidProcState(uid, procState, capability);
-            if (!mUidStates.contains(uid)) {
-                UidState uidState = new UidState(uid);
-                mUidStates.put(uid, uidState);
-                onUidStateChanged(uid,
-                        AppOpsUidStateTracker.processStateToUidState(procState), false);
-            }
-        }
+        mAppOpsService.updateUidProcState(uid, procState, capability);
     }
 
+    /**
+     * Initiates shutdown.
+     */
     public void shutdown() {
-        Slog.w(TAG, "Writing app ops before shutdown...");
-        boolean doWrite = false;
-        synchronized (this) {
-            if (mWriteScheduled) {
-                mWriteScheduled = false;
-                mFastWriteScheduled = false;
-                mHandler.removeCallbacks(mWriteRunner);
-                doWrite = true;
-            }
-        }
-        if (doWrite) {
-            writeState();
-        }
+        mAppOpsService.shutdown();
+
         if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED && mWriteNoteOpsScheduled) {
             writeNoteOps();
         }
-
-        mHistoricalRegistry.shutdown();
-    }
-
-    private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops) {
-        ArrayList<AppOpsManager.OpEntry> resOps = null;
-        if (ops == null) {
-            resOps = new ArrayList<>();
-            for (int j=0; j<pkgOps.size(); j++) {
-                Op curOp = pkgOps.valueAt(j);
-                resOps.add(getOpEntryForResult(curOp));
-            }
-        } else {
-            for (int j=0; j<ops.length; j++) {
-                Op curOp = pkgOps.get(ops[j]);
-                if (curOp != null) {
-                    if (resOps == null) {
-                        resOps = new ArrayList<>();
-                    }
-                    resOps.add(getOpEntryForResult(curOp));
-                }
-            }
-        }
-        return resOps;
-    }
-
-    @Nullable
-    private ArrayList<AppOpsManager.OpEntry> collectUidOps(@NonNull UidState uidState,
-            @Nullable int[] ops) {
-        final SparseIntArray opModes = uidState.getNonDefaultUidModes();
-        if (opModes == null) {
-            return null;
-        }
-
-        int opModeCount = opModes.size();
-        if (opModeCount == 0) {
-            return null;
-        }
-        ArrayList<AppOpsManager.OpEntry> resOps = null;
-        if (ops == null) {
-            resOps = new ArrayList<>();
-            for (int i = 0; i < opModeCount; i++) {
-                int code = opModes.keyAt(i);
-                resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
-            }
-        } else {
-            for (int j=0; j<ops.length; j++) {
-                int code = ops[j];
-                if (opModes.indexOfKey(code) >= 0) {
-                    if (resOps == null) {
-                        resOps = new ArrayList<>();
-                    }
-                    resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
-                }
-            }
-        }
-        return resOps;
-    }
-
-    private static @NonNull OpEntry getOpEntryForResult(@NonNull Op op) {
-        return op.createEntryLocked();
     }
 
     @Override
     public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
-        final int callingUid = Binder.getCallingUid();
-        final boolean hasAllPackageAccess = mContext.checkPermission(
-                Manifest.permission.GET_APP_OPS_STATS, Binder.getCallingPid(),
-                Binder.getCallingUid(), null) == PackageManager.PERMISSION_GRANTED;
-        ArrayList<AppOpsManager.PackageOps> res = null;
-        synchronized (this) {
-            final int uidStateCount = mUidStates.size();
-            for (int i = 0; i < uidStateCount; i++) {
-                UidState uidState = mUidStates.valueAt(i);
-                if (uidState.pkgOps == null || uidState.pkgOps.isEmpty()) {
-                    continue;
-                }
-                ArrayMap<String, Ops> packages = uidState.pkgOps;
-                final int packageCount = packages.size();
-                for (int j = 0; j < packageCount; j++) {
-                    Ops pkgOps = packages.valueAt(j);
-                    ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
-                    if (resOps != null) {
-                        if (res == null) {
-                            res = new ArrayList<>();
-                        }
-                        AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
-                                pkgOps.packageName, pkgOps.uidState.uid, resOps);
-                        // Caller can always see their packages and with a permission all.
-                        if (hasAllPackageAccess || callingUid == pkgOps.uidState.uid) {
-                            res.add(resPackage);
-                        }
-                    }
-                }
-            }
-        }
-        return res;
+        return mAppOpsService.getPackagesForOps(ops);
     }
 
     @Override
     public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName,
             int[] ops) {
-        enforceGetAppOpsStatsPermissionIfNeeded(uid,packageName);
-        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
-        if (resolvedPackageName == null) {
-            return Collections.emptyList();
-        }
-        synchronized (this) {
-            Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null,
-                    /* edit */ false);
-            if (pkgOps == null) {
-                return null;
-            }
-            ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
-            if (resOps == null) {
-                return null;
-            }
-            ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
-            AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
-                    pkgOps.packageName, pkgOps.uidState.uid, resOps);
-            res.add(resPackage);
-            return res;
-        }
-    }
-
-    private void enforceGetAppOpsStatsPermissionIfNeeded(int uid, String packageName) {
-        final int callingUid = Binder.getCallingUid();
-        // We get to access everything
-        if (callingUid == Process.myPid()) {
-            return;
-        }
-        // Apps can access their own data
-        if (uid == callingUid && packageName != null
-                && checkPackage(uid, packageName) == MODE_ALLOWED) {
-            return;
-        }
-        // Otherwise, you need a permission...
-        mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
-                Binder.getCallingPid(), callingUid, null);
-    }
-
-    /**
-     * Verify that historical appop request arguments are valid.
-     */
-    private void ensureHistoricalOpRequestIsValid(int uid, String packageName,
-            String attributionTag, List<String> opNames, int filter, long beginTimeMillis,
-            long endTimeMillis, int flags) {
-        if ((filter & FILTER_BY_UID) != 0) {
-            Preconditions.checkArgument(uid != Process.INVALID_UID);
-        } else {
-            Preconditions.checkArgument(uid == Process.INVALID_UID);
-        }
-
-        if ((filter & FILTER_BY_PACKAGE_NAME) != 0) {
-            Objects.requireNonNull(packageName);
-        } else {
-            Preconditions.checkArgument(packageName == null);
-        }
-
-        if ((filter & FILTER_BY_ATTRIBUTION_TAG) == 0) {
-            Preconditions.checkArgument(attributionTag == null);
-        }
-
-        if ((filter & FILTER_BY_OP_NAMES) != 0) {
-            Objects.requireNonNull(opNames);
-        } else {
-            Preconditions.checkArgument(opNames == null);
-        }
-
-        Preconditions.checkFlagsArgument(filter,
-                FILTER_BY_UID | FILTER_BY_PACKAGE_NAME | FILTER_BY_ATTRIBUTION_TAG
-                        | FILTER_BY_OP_NAMES);
-        Preconditions.checkArgumentNonnegative(beginTimeMillis);
-        Preconditions.checkArgument(endTimeMillis > beginTimeMillis);
-        Preconditions.checkFlagsArgument(flags, OP_FLAGS_ALL);
+        return mAppOpsService.getOpsForPackage(uid, packageName, ops);
     }
 
     @Override
     public void getHistoricalOps(int uid, String packageName, String attributionTag,
             List<String> opNames, int dataType, int filter, long beginTimeMillis,
             long endTimeMillis, int flags, RemoteCallback callback) {
-        PackageManager pm = mContext.getPackageManager();
-
-        ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
-                beginTimeMillis, endTimeMillis, flags);
-        Objects.requireNonNull(callback, "callback cannot be null");
-        ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
-        boolean isSelfRequest = (filter & FILTER_BY_UID) != 0 && uid == Binder.getCallingUid();
-        if (!isSelfRequest) {
-            boolean isCallerInstrumented =
-                    ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
-            boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
-            boolean isCallerPermissionController;
-            try {
-                isCallerPermissionController = pm.getPackageUidAsUser(
-                        mContext.getPackageManager().getPermissionControllerPackageName(), 0,
-                        UserHandle.getUserId(Binder.getCallingUid()))
-                        == Binder.getCallingUid();
-            } catch (PackageManager.NameNotFoundException doesNotHappen) {
-                return;
-            }
-
-            boolean doesCallerHavePermission = mContext.checkPermission(
-                    android.Manifest.permission.GET_HISTORICAL_APP_OPS_STATS,
-                    Binder.getCallingPid(), Binder.getCallingUid())
-                    == PackageManager.PERMISSION_GRANTED;
-
-            if (!isCallerSystem && !isCallerInstrumented && !isCallerPermissionController
-                    && !doesCallerHavePermission) {
-                mHandler.post(() -> callback.sendResult(new Bundle()));
-                return;
-            }
-
-            mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
-                    Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
-        }
-
-        final String[] opNamesArray = (opNames != null)
-                ? opNames.toArray(new String[opNames.size()]) : null;
-
-        Set<String> attributionChainExemptPackages = null;
-        if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
-            attributionChainExemptPackages =
-                    PermissionManager.getIndicatorExemptedPackages(mContext);
-        }
-
-        final String[] chainExemptPkgArray = attributionChainExemptPackages != null
-                ? attributionChainExemptPackages.toArray(
-                        new String[attributionChainExemptPackages.size()]) : null;
-
-        // Must not hold the appops lock
-        mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps,
-                mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
-                filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
-                callback).recycleOnUse());
+        mAppOpsService.getHistoricalOps(uid, packageName, attributionTag, opNames,
+                dataType, filter, beginTimeMillis, endTimeMillis, flags, callback);
     }
 
     @Override
     public void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag,
             List<String> opNames, int dataType, int filter, long beginTimeMillis,
             long endTimeMillis, int flags, RemoteCallback callback) {
-        ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
-                beginTimeMillis, endTimeMillis, flags);
-        Objects.requireNonNull(callback, "callback cannot be null");
-
-        mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
-                Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
-
-        final String[] opNamesArray = (opNames != null)
-                ? opNames.toArray(new String[opNames.size()]) : null;
-
-        Set<String> attributionChainExemptPackages = null;
-        if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
-            attributionChainExemptPackages =
-                    PermissionManager.getIndicatorExemptedPackages(mContext);
-        }
-
-        final String[] chainExemptPkgArray = attributionChainExemptPackages != null
-                ? attributionChainExemptPackages.toArray(
-                new String[attributionChainExemptPackages.size()]) : null;
-
-        // Must not hold the appops lock
-        mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOpsFromDiskRaw,
-                mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
-                filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
-                callback).recycleOnUse());
+        mAppOpsService.getHistoricalOpsFromDiskRaw(uid, packageName, attributionTag,
+                opNames, dataType, filter, beginTimeMillis, endTimeMillis, flags, callback);
     }
 
     @Override
     public void reloadNonHistoricalState() {
-        mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
-                Binder.getCallingPid(), Binder.getCallingUid(), "reloadNonHistoricalState");
-        writeState();
-        readState();
+        mAppOpsService.reloadNonHistoricalState();
     }
 
     @Override
     public List<AppOpsManager.PackageOps> getUidOps(int uid, int[] ops) {
-        mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
-                Binder.getCallingPid(), Binder.getCallingUid(), null);
-        synchronized (this) {
-            UidState uidState = getUidStateLocked(uid, false);
-            if (uidState == null) {
-                return null;
-            }
-            ArrayList<AppOpsManager.OpEntry> resOps = collectUidOps(uidState, ops);
-            if (resOps == null) {
-                return null;
-            }
-            ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
-            AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
-                    null, uidState.uid, resOps);
-            res.add(resPackage);
-            return res;
-        }
-    }
-
-    private void pruneOpLocked(Op op, int uid, String packageName) {
-        op.removeAttributionsWithNoTime();
-
-        if (op.mAttributions.isEmpty()) {
-            Ops ops = getOpsLocked(uid, packageName, null, false, null, /* edit */ false);
-            if (ops != null) {
-                ops.remove(op.op);
-                op.setMode(AppOpsManager.opToDefaultMode(op.op));
-                if (ops.size() <= 0) {
-                    UidState uidState = ops.uidState;
-                    ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
-                    if (pkgOps != null) {
-                        pkgOps.remove(ops.packageName);
-                        mAppOpsServiceInterface.removePackage(ops.packageName,
-                                UserHandle.getUserId(uidState.uid));
-                        if (pkgOps.isEmpty()) {
-                            uidState.pkgOps = null;
-                        }
-                        if (uidState.isDefault()) {
-                            uidState.clear();
-                            mUidStates.remove(uid);
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    private void enforceManageAppOpsModes(int callingPid, int callingUid, int targetUid) {
-        if (callingPid == Process.myPid()) {
-            return;
-        }
-        final int callingUser = UserHandle.getUserId(callingUid);
-        synchronized (this) {
-            if (mProfileOwners != null && mProfileOwners.get(callingUser, -1) == callingUid) {
-                if (targetUid >= 0 && callingUser == UserHandle.getUserId(targetUid)) {
-                    // Profile owners are allowed to change modes but only for apps
-                    // within their user.
-                    return;
-                }
-            }
-        }
-        mContext.enforcePermission(android.Manifest.permission.MANAGE_APP_OPS_MODES,
-                Binder.getCallingPid(), Binder.getCallingUid(), null);
+        return mAppOpsService.getUidOps(uid, ops);
     }
 
     @Override
     public void setUidMode(int code, int uid, int mode) {
-        setUidMode(code, uid, mode, null);
-    }
-
-    private void setUidMode(int code, int uid, int mode,
-            @Nullable IAppOpsCallback permissionPolicyCallback) {
-        if (DEBUG) {
-            Slog.i(TAG, "uid " + uid + " OP_" + opToName(code) + " := " + modeToName(mode)
-                    + " by uid " + Binder.getCallingUid());
-        }
-
-        enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
-        verifyIncomingOp(code);
-        code = AppOpsManager.opToSwitch(code);
-
-        if (permissionPolicyCallback == null) {
-            updatePermissionRevokedCompat(uid, code, mode);
-        }
-
-        int previousMode;
-        synchronized (this) {
-            final int defaultMode = AppOpsManager.opToDefaultMode(code);
-
-            UidState uidState = getUidStateLocked(uid, false);
-            if (uidState == null) {
-                if (mode == defaultMode) {
-                    return;
-                }
-                uidState = new UidState(uid);
-                mUidStates.put(uid, uidState);
-            }
-            if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
-                previousMode = uidState.getUidMode(code);
-            } else {
-                // doesn't look right but is legacy behavior.
-                previousMode = MODE_DEFAULT;
-            }
-
-            if (!uidState.setUidMode(code, mode)) {
-                return;
-            }
-            uidState.evalForegroundOps();
-            if (mode != MODE_ERRORED && mode != previousMode) {
-                updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
-            }
-        }
-
-        notifyOpChangedForAllPkgsInUid(code, uid, false, permissionPolicyCallback);
-        notifyOpChangedSync(code, uid, null, mode, previousMode);
-    }
-
-    /**
-     * Notify that an op changed for all packages in an uid.
-     *
-     * @param code The op that changed
-     * @param uid The uid the op was changed for
-     * @param onlyForeground Only notify watchers that watch for foreground changes
-     */
-    private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground,
-            @Nullable IAppOpsCallback callbackToIgnore) {
-        ModeCallback listenerToIgnore = callbackToIgnore != null
-                ? mModeWatchers.get(callbackToIgnore.asBinder()) : null;
-        mAppOpsServiceInterface.notifyOpChangedForAllPkgsInUid(code, uid, onlyForeground,
-                listenerToIgnore);
-    }
-
-    private void updatePermissionRevokedCompat(int uid, int switchCode, int mode) {
-        PackageManager packageManager = mContext.getPackageManager();
-        if (packageManager == null) {
-            // This can only happen during early boot. At this time the permission state and appop
-            // state are in sync
-            return;
-        }
-
-        String[] packageNames = packageManager.getPackagesForUid(uid);
-        if (ArrayUtils.isEmpty(packageNames)) {
-            return;
-        }
-        String packageName = packageNames[0];
-
-        int[] ops = mSwitchedOps.get(switchCode);
-        for (int code : ops) {
-            String permissionName = AppOpsManager.opToPermission(code);
-            if (permissionName == null) {
-                continue;
-            }
-
-            if (packageManager.checkPermission(permissionName, packageName)
-                    != PackageManager.PERMISSION_GRANTED) {
-                continue;
-            }
-
-            PermissionInfo permissionInfo;
-            try {
-                permissionInfo = packageManager.getPermissionInfo(permissionName, 0);
-            } catch (PackageManager.NameNotFoundException e) {
-                e.printStackTrace();
-                continue;
-            }
-
-            if (!permissionInfo.isRuntime()) {
-                continue;
-            }
-
-            boolean supportsRuntimePermissions = getPackageManagerInternal()
-                    .getUidTargetSdkVersion(uid) >= Build.VERSION_CODES.M;
-
-            UserHandle user = UserHandle.getUserHandleForUid(uid);
-            boolean isRevokedCompat;
-            if (permissionInfo.backgroundPermission != null) {
-                if (packageManager.checkPermission(permissionInfo.backgroundPermission, packageName)
-                        == PackageManager.PERMISSION_GRANTED) {
-                    boolean isBackgroundRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
-
-                    if (isBackgroundRevokedCompat && supportsRuntimePermissions) {
-                        Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
-                                + " permission state, this is discouraged and you should revoke the"
-                                + " runtime permission instead: uid=" + uid + ", switchCode="
-                                + switchCode + ", mode=" + mode + ", permission="
-                                + permissionInfo.backgroundPermission);
-                    }
-
-                    final long identity = Binder.clearCallingIdentity();
-                    try {
-                        packageManager.updatePermissionFlags(permissionInfo.backgroundPermission,
-                                packageName, PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
-                                isBackgroundRevokedCompat
-                                        ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
-                    } finally {
-                        Binder.restoreCallingIdentity(identity);
-                    }
-                }
-
-                isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED
-                        && mode != AppOpsManager.MODE_FOREGROUND;
-            } else {
-                isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
-            }
-
-            if (isRevokedCompat && supportsRuntimePermissions) {
-                Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
-                        + " permission state, this is discouraged and you should revoke the"
-                        + " runtime permission instead: uid=" + uid + ", switchCode="
-                        + switchCode + ", mode=" + mode + ", permission=" + permissionName);
-            }
-
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                packageManager.updatePermissionFlags(permissionName, packageName,
-                        PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, isRevokedCompat
-                                ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-        }
-    }
-
-    private void notifyOpChangedSync(int code, int uid, @NonNull String packageName, int mode,
-            int previousMode) {
-        final StorageManagerInternal storageManagerInternal =
-                LocalServices.getService(StorageManagerInternal.class);
-        if (storageManagerInternal != null) {
-            storageManagerInternal.onAppOpsChanged(code, uid, packageName, mode, previousMode);
-        }
+        mAppOpsService.setUidMode(code, uid, mode, null);
     }
 
     /**
@@ -1866,309 +407,12 @@
      */
     @Override
     public void setMode(int code, int uid, @NonNull String packageName, int mode) {
-        setMode(code, uid, packageName, mode, null);
-    }
-
-    private void setMode(int code, int uid, @NonNull String packageName, int mode,
-            @Nullable IAppOpsCallback permissionPolicyCallback) {
-        enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
-        verifyIncomingOp(code);
-        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
-            return;
-        }
-
-        ArraySet<OnOpModeChangedListener> repCbs = null;
-        code = AppOpsManager.opToSwitch(code);
-
-        PackageVerificationResult pvr;
-        try {
-            pvr = verifyAndGetBypass(uid, packageName, null);
-        } catch (SecurityException e) {
-            Slog.e(TAG, "Cannot setMode", e);
-            return;
-        }
-
-        int previousMode = MODE_DEFAULT;
-        synchronized (this) {
-            UidState uidState = getUidStateLocked(uid, false);
-            Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ true);
-            if (op != null) {
-                if (op.getMode() != mode) {
-                    previousMode = op.getMode();
-                    op.setMode(mode);
-
-                    if (uidState != null) {
-                        uidState.evalForegroundOps();
-                    }
-                    ArraySet<OnOpModeChangedListener> cbs =
-                            mAppOpsServiceInterface.getOpModeChangedListeners(code);
-                    if (cbs != null) {
-                        if (repCbs == null) {
-                            repCbs = new ArraySet<>();
-                        }
-                        repCbs.addAll(cbs);
-                    }
-                    cbs = mAppOpsServiceInterface.getPackageModeChangedListeners(packageName);
-                    if (cbs != null) {
-                        if (repCbs == null) {
-                            repCbs = new ArraySet<>();
-                        }
-                        repCbs.addAll(cbs);
-                    }
-                    if (repCbs != null && permissionPolicyCallback != null) {
-                        repCbs.remove(mModeWatchers.get(permissionPolicyCallback.asBinder()));
-                    }
-                    if (mode == AppOpsManager.opToDefaultMode(op.op)) {
-                        // If going into the default mode, prune this op
-                        // if there is nothing else interesting in it.
-                        pruneOpLocked(op, uid, packageName);
-                    }
-                    scheduleFastWriteLocked();
-                    if (mode != MODE_ERRORED) {
-                        updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
-                    }
-                }
-            }
-        }
-        if (repCbs != null) {
-            mHandler.sendMessage(PooledLambda.obtainMessage(
-                    AppOpsService::notifyOpChanged,
-                    this, repCbs, code, uid, packageName));
-        }
-
-        notifyOpChangedSync(code, uid, packageName, mode, previousMode);
-    }
-
-    private void notifyOpChanged(ArraySet<OnOpModeChangedListener> callbacks, int code,
-            int uid, String packageName) {
-        for (int i = 0; i < callbacks.size(); i++) {
-            final OnOpModeChangedListener callback = callbacks.valueAt(i);
-            notifyOpChanged(callback, code, uid, packageName);
-        }
-    }
-
-    private void notifyOpChanged(OnOpModeChangedListener callback, int code,
-            int uid, String packageName) {
-        mAppOpsServiceInterface.notifyOpChanged(callback, code, uid, packageName);
-    }
-
-    private static ArrayList<ChangeRec> addChange(ArrayList<ChangeRec> reports,
-            int op, int uid, String packageName, int previousMode) {
-        boolean duplicate = false;
-        if (reports == null) {
-            reports = new ArrayList<>();
-        } else {
-            final int reportCount = reports.size();
-            for (int j = 0; j < reportCount; j++) {
-                ChangeRec report = reports.get(j);
-                if (report.op == op && report.pkg.equals(packageName)) {
-                    duplicate = true;
-                    break;
-                }
-            }
-        }
-        if (!duplicate) {
-            reports.add(new ChangeRec(op, uid, packageName, previousMode));
-        }
-
-        return reports;
-    }
-
-    private static HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> addCallbacks(
-            HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks,
-            int op, int uid, String packageName, int previousMode,
-            ArraySet<OnOpModeChangedListener> cbs) {
-        if (cbs == null) {
-            return callbacks;
-        }
-        if (callbacks == null) {
-            callbacks = new HashMap<>();
-        }
-        final int N = cbs.size();
-        for (int i=0; i<N; i++) {
-            OnOpModeChangedListener cb = cbs.valueAt(i);
-            ArrayList<ChangeRec> reports = callbacks.get(cb);
-            ArrayList<ChangeRec> changed = addChange(reports, op, uid, packageName, previousMode);
-            if (changed != reports) {
-                callbacks.put(cb, changed);
-            }
-        }
-        return callbacks;
-    }
-
-    static final class ChangeRec {
-        final int op;
-        final int uid;
-        final String pkg;
-        final int previous_mode;
-
-        ChangeRec(int _op, int _uid, String _pkg, int _previous_mode) {
-            op = _op;
-            uid = _uid;
-            pkg = _pkg;
-            previous_mode = _previous_mode;
-        }
+        mAppOpsService.setMode(code, uid, packageName, mode, null);
     }
 
     @Override
     public void resetAllModes(int reqUserId, String reqPackageName) {
-        final int callingPid = Binder.getCallingPid();
-        final int callingUid = Binder.getCallingUid();
-        reqUserId = ActivityManager.handleIncomingUser(callingPid, callingUid, reqUserId,
-                true, true, "resetAllModes", null);
-
-        int reqUid = -1;
-        if (reqPackageName != null) {
-            try {
-                reqUid = AppGlobals.getPackageManager().getPackageUid(
-                        reqPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, reqUserId);
-            } catch (RemoteException e) {
-                /* ignore - local call */
-            }
-        }
-
-        enforceManageAppOpsModes(callingPid, callingUid, reqUid);
-
-        HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks = null;
-        ArrayList<ChangeRec> allChanges = new ArrayList<>();
-        synchronized (this) {
-            boolean changed = false;
-            for (int i = mUidStates.size() - 1; i >= 0; i--) {
-                UidState uidState = mUidStates.valueAt(i);
-
-                SparseIntArray opModes = uidState.getNonDefaultUidModes();
-                if (opModes != null && (uidState.uid == reqUid || reqUid == -1)) {
-                    final int uidOpCount = opModes.size();
-                    for (int j = uidOpCount - 1; j >= 0; j--) {
-                        final int code = opModes.keyAt(j);
-                        if (AppOpsManager.opAllowsReset(code)) {
-                            int previousMode = opModes.valueAt(j);
-                            uidState.setUidMode(code, AppOpsManager.opToDefaultMode(code));
-                            for (String packageName : getPackagesForUid(uidState.uid)) {
-                                callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
-                                        previousMode,
-                                        mAppOpsServiceInterface.getOpModeChangedListeners(code));
-                                callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
-                                        previousMode, mAppOpsServiceInterface
-                                                .getPackageModeChangedListeners(packageName));
-
-                                allChanges = addChange(allChanges, code, uidState.uid,
-                                        packageName, previousMode);
-                            }
-                        }
-                    }
-                }
-
-                if (uidState.pkgOps == null) {
-                    continue;
-                }
-
-                if (reqUserId != UserHandle.USER_ALL
-                        && reqUserId != UserHandle.getUserId(uidState.uid)) {
-                    // Skip any ops for a different user
-                    continue;
-                }
-
-                Map<String, Ops> packages = uidState.pkgOps;
-                Iterator<Map.Entry<String, Ops>> it = packages.entrySet().iterator();
-                boolean uidChanged = false;
-                while (it.hasNext()) {
-                    Map.Entry<String, Ops> ent = it.next();
-                    String packageName = ent.getKey();
-                    if (reqPackageName != null && !reqPackageName.equals(packageName)) {
-                        // Skip any ops for a different package
-                        continue;
-                    }
-                    Ops pkgOps = ent.getValue();
-                    for (int j=pkgOps.size()-1; j>=0; j--) {
-                        Op curOp = pkgOps.valueAt(j);
-                        if (shouldDeferResetOpToDpm(curOp.op)) {
-                            deferResetOpToDpm(curOp.op, reqPackageName, reqUserId);
-                            continue;
-                        }
-                        if (AppOpsManager.opAllowsReset(curOp.op)
-                                && curOp.getMode() != AppOpsManager.opToDefaultMode(curOp.op)) {
-                            int previousMode = curOp.getMode();
-                            curOp.setMode(AppOpsManager.opToDefaultMode(curOp.op));
-                            changed = true;
-                            uidChanged = true;
-                            final int uid = curOp.uidState.uid;
-                            callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
-                                    previousMode,
-                                    mAppOpsServiceInterface.getOpModeChangedListeners(curOp.op));
-                            callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
-                                    previousMode, mAppOpsServiceInterface
-                                            .getPackageModeChangedListeners(packageName));
-
-                            allChanges = addChange(allChanges, curOp.op, uid, packageName,
-                                    previousMode);
-                            curOp.removeAttributionsWithNoTime();
-                            if (curOp.mAttributions.isEmpty()) {
-                                pkgOps.removeAt(j);
-                            }
-                        }
-                    }
-                    if (pkgOps.size() == 0) {
-                        it.remove();
-                        mAppOpsServiceInterface.removePackage(packageName,
-                                UserHandle.getUserId(uidState.uid));
-                    }
-                }
-                if (uidState.isDefault()) {
-                    uidState.clear();
-                    mUidStates.remove(uidState.uid);
-                }
-                if (uidChanged) {
-                    uidState.evalForegroundOps();
-                }
-            }
-
-            if (changed) {
-                scheduleFastWriteLocked();
-            }
-        }
-        if (callbacks != null) {
-            for (Map.Entry<OnOpModeChangedListener, ArrayList<ChangeRec>> ent
-                    : callbacks.entrySet()) {
-                OnOpModeChangedListener cb = ent.getKey();
-                ArrayList<ChangeRec> reports = ent.getValue();
-                for (int i=0; i<reports.size(); i++) {
-                    ChangeRec rep = reports.get(i);
-                    mHandler.sendMessage(PooledLambda.obtainMessage(
-                            AppOpsService::notifyOpChanged,
-                            this, cb, rep.op, rep.uid, rep.pkg));
-                }
-            }
-        }
-
-        int numChanges = allChanges.size();
-        for (int i = 0; i < numChanges; i++) {
-            ChangeRec change = allChanges.get(i);
-            notifyOpChangedSync(change.op, change.uid, change.pkg,
-                    AppOpsManager.opToDefaultMode(change.op), change.previous_mode);
-        }
-    }
-
-    private boolean shouldDeferResetOpToDpm(int op) {
-        // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
-        //  pre-grants to a role-based mechanism or another general-purpose mechanism.
-        return dpmi != null && dpmi.supportsResetOp(op);
-    }
-
-    /** Assumes {@link #shouldDeferResetOpToDpm(int)} is true. */
-    private void deferResetOpToDpm(int op, String packageName, @UserIdInt int userId) {
-        // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
-        //  pre-grants to a role-based mechanism or another general-purpose mechanism.
-        dpmi.resetOp(op, packageName, userId);
-    }
-
-    private void evalAllForegroundOpsLocked() {
-        for (int uidi = mUidStates.size() - 1; uidi >= 0; uidi--) {
-            final UidState uidState = mUidStates.valueAt(uidi);
-            if (uidState.foregroundOps != null) {
-                uidState.evalForegroundOps();
-            }
-        }
+        mAppOpsService.resetAllModes(reqUserId, reqPackageName);
     }
 
     @Override
@@ -2179,66 +423,17 @@
     @Override
     public void startWatchingModeWithFlags(int op, String packageName, int flags,
             IAppOpsCallback callback) {
-        int watchedUid = -1;
-        final int callingUid = Binder.getCallingUid();
-        final int callingPid = Binder.getCallingPid();
-        // TODO: should have a privileged permission to protect this.
-        // Also, if the caller has requested WATCH_FOREGROUND_CHANGES, should we require
-        // the USAGE_STATS permission since this can provide information about when an
-        // app is in the foreground?
-        Preconditions.checkArgumentInRange(op, AppOpsManager.OP_NONE,
-                AppOpsManager._NUM_OP - 1, "Invalid op code: " + op);
-        if (callback == null) {
-            return;
-        }
-        final boolean mayWatchPackageName = packageName != null
-                && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(callingUid));
-        synchronized (this) {
-            int switchOp = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op;
-
-            int notifiedOps;
-            if ((flags & CALL_BACK_ON_SWITCHED_OP) == 0) {
-                if (op == OP_NONE) {
-                    notifiedOps = ALL_OPS;
-                } else {
-                    notifiedOps = op;
-                }
-            } else {
-                notifiedOps = switchOp;
-            }
-
-            ModeCallback cb = mModeWatchers.get(callback.asBinder());
-            if (cb == null) {
-                cb = new ModeCallback(callback, watchedUid, flags, notifiedOps, callingUid,
-                        callingPid);
-                mModeWatchers.put(callback.asBinder(), cb);
-            }
-            if (switchOp != AppOpsManager.OP_NONE) {
-                mAppOpsServiceInterface.startWatchingOpModeChanged(cb, switchOp);
-            }
-            if (mayWatchPackageName) {
-                mAppOpsServiceInterface.startWatchingPackageModeChanged(cb, packageName);
-            }
-            evalAllForegroundOpsLocked();
-        }
+        mAppOpsService.startWatchingModeWithFlags(op, packageName, flags, callback);
     }
 
     @Override
     public void stopWatchingMode(IAppOpsCallback callback) {
-        if (callback == null) {
-            return;
-        }
-        synchronized (this) {
-            ModeCallback cb = mModeWatchers.remove(callback.asBinder());
-            if (cb != null) {
-                cb.unlinkToDeath();
-                mAppOpsServiceInterface.removeListener(cb);
-            }
-
-            evalAllForegroundOpsLocked();
-        }
+        mAppOpsService.stopWatchingMode(callback);
     }
 
+    /**
+     * @return the current {@link CheckOpsDelegate}.
+     */
     public CheckOpsDelegate getAppOpsServiceDelegate() {
         synchronized (AppOpsService.this) {
             final CheckOpsDelegateDispatcher dispatcher = mCheckOpsDelegateDispatcher;
@@ -2246,6 +441,9 @@
         }
     }
 
+    /**
+     * Sets the appops {@link CheckOpsDelegate}
+     */
     public void setAppOpsServiceDelegate(CheckOpsDelegate delegate) {
         synchronized (AppOpsService.this) {
             final CheckOpsDelegateDispatcher oldDispatcher = mCheckOpsDelegateDispatcher;
@@ -2269,58 +467,7 @@
 
     private int checkOperationImpl(int code, int uid, String packageName,
             @Nullable String attributionTag, boolean raw) {
-        verifyIncomingOp(code);
-        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
-            return AppOpsManager.opToDefaultMode(code);
-        }
-
-        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
-        if (resolvedPackageName == null) {
-            return AppOpsManager.MODE_IGNORED;
-        }
-        return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag, raw);
-    }
-
-    /**
-     * Get the mode of an app-op.
-     *
-     * @param code The code of the op
-     * @param uid The uid of the package the op belongs to
-     * @param packageName The package the op belongs to
-     * @param raw If the raw state of eval-ed state should be checked.
-     *
-     * @return The mode of the op
-     */
-    private @Mode int checkOperationUnchecked(int code, int uid, @NonNull String packageName,
-            @Nullable String attributionTag, boolean raw) {
-        PackageVerificationResult pvr;
-        try {
-            pvr = verifyAndGetBypass(uid, packageName, null);
-        } catch (SecurityException e) {
-            Slog.e(TAG, "checkOperation", e);
-            return AppOpsManager.opToDefaultMode(code);
-        }
-
-        if (isOpRestrictedDueToSuspend(code, packageName, uid)) {
-            return AppOpsManager.MODE_IGNORED;
-        }
-        synchronized (this) {
-            if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, true)) {
-                return AppOpsManager.MODE_IGNORED;
-            }
-            code = AppOpsManager.opToSwitch(code);
-            UidState uidState = getUidStateLocked(uid, false);
-            if (uidState != null
-                    && uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
-                final int rawMode = uidState.getUidMode(code);
-                return raw ? rawMode : uidState.evalMode(code, rawMode);
-            }
-            Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ false);
-            if (op == null) {
-                return AppOpsManager.opToDefaultMode(code);
-            }
-            return raw ? op.getMode() : op.uidState.evalMode(op.op, op.getMode());
-        }
+        return mAppOpsService.checkOperation(code, uid, packageName, attributionTag, raw);
     }
 
     @Override
@@ -2340,7 +487,8 @@
     @Override
     public void setAudioRestriction(int code, int usage, int uid, int mode,
             String[] exceptionPackages) {
-        enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
+        mAppOpsService.enforceManageAppOpsModes(Binder.getCallingPid(),
+                Binder.getCallingUid(), uid);
         verifyIncomingUid(uid);
         verifyIncomingOp(code);
 
@@ -2348,58 +496,35 @@
                 code, usage, uid, mode, exceptionPackages);
 
         mHandler.sendMessage(PooledLambda.obtainMessage(
-                AppOpsService::notifyWatchersOfChange, this, code, UID_ANY));
+                AppOpsServiceInterface::notifyWatchersOfChange, mAppOpsService, code,
+                UID_ANY));
     }
 
 
     @Override
     public void setCameraAudioRestriction(@CAMERA_AUDIO_RESTRICTION int mode) {
-        enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), -1);
+        mAppOpsService.enforceManageAppOpsModes(Binder.getCallingPid(),
+                Binder.getCallingUid(), -1);
 
         mAudioRestrictionManager.setCameraAudioRestriction(mode);
 
         mHandler.sendMessage(PooledLambda.obtainMessage(
-                AppOpsService::notifyWatchersOfChange, this,
+                AppOpsServiceInterface::notifyWatchersOfChange, mAppOpsService,
                 AppOpsManager.OP_PLAY_AUDIO, UID_ANY));
         mHandler.sendMessage(PooledLambda.obtainMessage(
-                AppOpsService::notifyWatchersOfChange, this,
+                AppOpsServiceInterface::notifyWatchersOfChange, mAppOpsService,
                 AppOpsManager.OP_VIBRATE, UID_ANY));
     }
 
     @Override
     public int checkPackage(int uid, String packageName) {
-        Objects.requireNonNull(packageName);
-        try {
-            verifyAndGetBypass(uid, packageName, null);
-            // When the caller is the system, it's possible that the packageName is the special
-            // one (e.g., "root") which isn't actually existed.
-            if (resolveUid(packageName) == uid
-                    || (isPackageExisted(packageName)
-                            && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(uid)))) {
-                return AppOpsManager.MODE_ALLOWED;
-            }
-            return AppOpsManager.MODE_ERRORED;
-        } catch (SecurityException ignored) {
-            return AppOpsManager.MODE_ERRORED;
-        }
+        return mAppOpsService.checkPackage(uid, packageName);
     }
 
     private boolean isPackageExisted(String packageName) {
         return getPackageManagerInternal().getPackageStateInternal(packageName) != null;
     }
 
-    /**
-     * This method will check with PackageManager to determine if the package provided should
-     * be visible to the {@link Binder#getCallingUid()}.
-     *
-     * NOTE: This must not be called while synchronized on {@code this} to avoid dead locks
-     */
-    private boolean filterAppAccessUnlocked(String packageName, int userId) {
-        final int callingUid = Binder.getCallingUid();
-        return LocalServices.getService(PackageManagerInternal.class)
-                .filterAppAccess(packageName, callingUid, userId);
-    }
-
     @Override
     public SyncNotedAppOp noteProxyOperation(int code, AttributionSource attributionSource,
             boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
@@ -2445,13 +570,20 @@
             final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
                     : AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
 
-            final SyncNotedAppOp proxyReturn = noteOperationUnchecked(code, proxyUid,
+            final int proxyReturn = mAppOpsService.noteOperationUnchecked(code, proxyUid,
                     resolveProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
-                    proxyFlags, !isProxyTrusted, "proxy " + message, shouldCollectMessage);
-            if (proxyReturn.getOpMode() != AppOpsManager.MODE_ALLOWED) {
-                return new SyncNotedAppOp(proxyReturn.getOpMode(), code, proxiedAttributionTag,
+                    proxyFlags);
+            if (proxyReturn != AppOpsManager.MODE_ALLOWED) {
+                return new SyncNotedAppOp(proxyReturn, code, proxiedAttributionTag,
                         proxiedPackageName);
             }
+            if (shouldCollectAsyncNotedOp) {
+                boolean isProxyAttributionTagValid = mAppOpsService.isAttributionTagValid(proxyUid,
+                        resolveProxyPackageName, proxyAttributionTag, null);
+                collectAsyncNotedOp(proxyUid, resolveProxyPackageName, code,
+                        isProxyAttributionTagValid ? proxyAttributionTag : null, proxyFlags,
+                        message, shouldCollectMessage);
+            }
         }
 
         String resolveProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid,
@@ -2463,9 +595,32 @@
 
         final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED
                 : AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED;
-        return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName,
-                proxiedAttributionTag, proxyUid, resolveProxyPackageName, proxyAttributionTag,
-                proxiedFlags, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+        final int result = mAppOpsService.noteOperationUnchecked(code, proxiedUid,
+                resolveProxiedPackageName, proxiedAttributionTag, proxyUid, resolveProxyPackageName,
+                proxyAttributionTag, proxiedFlags);
+
+        boolean isProxiedAttributionTagValid = mAppOpsService.isAttributionTagValid(proxiedUid,
+                resolveProxiedPackageName, proxiedAttributionTag, resolveProxyPackageName);
+        if (shouldCollectAsyncNotedOp && result == AppOpsManager.MODE_ALLOWED) {
+            collectAsyncNotedOp(proxiedUid, resolveProxiedPackageName, code,
+                    isProxiedAttributionTagValid ? proxiedAttributionTag : null, proxiedFlags,
+                    message, shouldCollectMessage);
+        }
+
+
+        return new SyncNotedAppOp(result, code,
+                isProxiedAttributionTagValid ? proxiedAttributionTag : null,
+                resolveProxiedPackageName);
+    }
+
+    private boolean isCallerAndAttributionTrusted(@NonNull AttributionSource attributionSource) {
+        if (attributionSource.getUid() != Binder.getCallingUid()
+                && attributionSource.isTrusted(mContext)) {
+            return true;
+        }
+        return mContext.checkPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
+                Binder.getCallingPid(), Binder.getCallingUid(), null)
+                == PackageManager.PERMISSION_GRANTED;
     }
 
     @Override
@@ -2479,258 +634,58 @@
     private SyncNotedAppOp noteOperationImpl(int code, int uid, @Nullable String packageName,
             @Nullable String attributionTag, boolean shouldCollectAsyncNotedOp,
             @Nullable String message, boolean shouldCollectMessage) {
-        verifyIncomingUid(uid);
-        verifyIncomingOp(code);
         if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
             return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                     packageName);
         }
 
+        int result = mAppOpsService.noteOperation(code, uid, packageName,
+                attributionTag, message);
+
         String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
-        if (resolvedPackageName == null) {
-            return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
-                    packageName);
-        }
-        return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
-                Process.INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF,
-                shouldCollectAsyncNotedOp, message, shouldCollectMessage);
-    }
 
-    private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName,
-            @Nullable String attributionTag, int proxyUid, String proxyPackageName,
-            @Nullable String proxyAttributionTag, @OpFlags int flags,
-            boolean shouldCollectAsyncNotedOp, @Nullable String message,
-            boolean shouldCollectMessage) {
-        PackageVerificationResult pvr;
-        try {
-            pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
-            boolean wasNull = attributionTag == null;
-            if (!pvr.isAttributionTagValid) {
-                attributionTag = null;
-            }
-        } catch (SecurityException e) {
-            Slog.e(TAG, "noteOperation", e);
-            return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
-                    packageName);
+        boolean isAttributionTagValid = mAppOpsService.isAttributionTagValid(uid,
+                    resolvedPackageName, attributionTag, null);
+
+        if (shouldCollectAsyncNotedOp && result == MODE_ALLOWED) {
+            collectAsyncNotedOp(uid, resolvedPackageName, code,
+                    isAttributionTagValid ? attributionTag : null, AppOpsManager.OP_FLAG_SELF,
+                    message, shouldCollectMessage);
         }
 
-        synchronized (this) {
-            final Ops ops = getOpsLocked(uid, packageName, attributionTag,
-                    pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
-            if (ops == null) {
-                scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
-                        AppOpsManager.MODE_IGNORED);
-                if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
-                        + " package " + packageName + "flags: " +
-                        AppOpsManager.flagsToString(flags));
-                return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
-                        packageName);
-            }
-            final Op op = getOpLocked(ops, code, uid, true);
-            final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
-            if (attributedOp.isRunning()) {
-                Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code "
-                        + code + " startTime of in progress event="
-                        + attributedOp.mInProgressEvents.valueAt(0).getStartTime());
-            }
-
-            final int switchCode = AppOpsManager.opToSwitch(code);
-            final UidState uidState = ops.uidState;
-            if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, false)) {
-                attributedOp.rejected(uidState.getState(), flags);
-                scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
-                        AppOpsManager.MODE_IGNORED);
-                return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
-                        packageName);
-            }
-            // If there is a non-default per UID policy (we set UID op mode only if
-            // non-default) it takes over, otherwise use the per package policy.
-            if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
-                final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
-                if (uidMode != AppOpsManager.MODE_ALLOWED) {
-                    if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
-                            + switchCode + " (" + code + ") uid " + uid + " package "
-                            + packageName + " flags: " + AppOpsManager.flagsToString(flags));
-                    attributedOp.rejected(uidState.getState(), flags);
-                    scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
-                            uidMode);
-                    return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
-                }
-            } else {
-                final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
-                        : op;
-                final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
-                if (mode != AppOpsManager.MODE_ALLOWED) {
-                    if (DEBUG) Slog.d(TAG, "noteOperation: reject #" + mode + " for code "
-                            + switchCode + " (" + code + ") uid " + uid + " package "
-                            + packageName + " flags: " + AppOpsManager.flagsToString(flags));
-                    attributedOp.rejected(uidState.getState(), flags);
-                    scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
-                            mode);
-                    return new SyncNotedAppOp(mode, code, attributionTag, packageName);
-                }
-            }
-            if (DEBUG) {
-                Slog.d(TAG,
-                        "noteOperation: allowing code " + code + " uid " + uid + " package "
-                                + packageName + (attributionTag == null ? ""
-                                : "." + attributionTag) + " flags: "
-                                + AppOpsManager.flagsToString(flags));
-            }
-            scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
-                    AppOpsManager.MODE_ALLOWED);
-            attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
-                    uidState.getState(),
-                    flags);
-
-            if (shouldCollectAsyncNotedOp) {
-                collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message,
-                        shouldCollectMessage);
-            }
-
-            return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag,
-                    packageName);
-        }
+        return new SyncNotedAppOp(result, code, isAttributionTagValid ? attributionTag : null,
+                resolvedPackageName);
     }
 
     // TODO moltmann: Allow watching for attribution ops
     @Override
     public void startWatchingActive(int[] ops, IAppOpsActiveCallback callback) {
-        int watchedUid = Process.INVALID_UID;
-        final int callingUid = Binder.getCallingUid();
-        final int callingPid = Binder.getCallingPid();
-        if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
-                != PackageManager.PERMISSION_GRANTED) {
-            watchedUid = callingUid;
-        }
-        if (ops != null) {
-            Preconditions.checkArrayElementsInRange(ops, 0,
-                    AppOpsManager._NUM_OP - 1, "Invalid op code in: " + Arrays.toString(ops));
-        }
-        if (callback == null) {
-            return;
-        }
-        synchronized (this) {
-            SparseArray<ActiveCallback> callbacks = mActiveWatchers.get(callback.asBinder());
-            if (callbacks == null) {
-                callbacks = new SparseArray<>();
-                mActiveWatchers.put(callback.asBinder(), callbacks);
-            }
-            final ActiveCallback activeCallback = new ActiveCallback(callback, watchedUid,
-                    callingUid, callingPid);
-            for (int op : ops) {
-                callbacks.put(op, activeCallback);
-            }
-        }
+        mAppOpsService.startWatchingActive(ops, callback);
     }
 
     @Override
     public void stopWatchingActive(IAppOpsActiveCallback callback) {
-        if (callback == null) {
-            return;
-        }
-        synchronized (this) {
-            final SparseArray<ActiveCallback> activeCallbacks =
-                    mActiveWatchers.remove(callback.asBinder());
-            if (activeCallbacks == null) {
-                return;
-            }
-            final int callbackCount = activeCallbacks.size();
-            for (int i = 0; i < callbackCount; i++) {
-                activeCallbacks.valueAt(i).destroy();
-            }
-        }
+        mAppOpsService.stopWatchingActive(callback);
     }
 
     @Override
     public void startWatchingStarted(int[] ops, @NonNull IAppOpsStartedCallback callback) {
-        int watchedUid = Process.INVALID_UID;
-        final int callingUid = Binder.getCallingUid();
-        final int callingPid = Binder.getCallingPid();
-        if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
-                != PackageManager.PERMISSION_GRANTED) {
-            watchedUid = callingUid;
-        }
-
-        Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
-        Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
-                "Invalid op code in: " + Arrays.toString(ops));
-        Objects.requireNonNull(callback, "Callback cannot be null");
-
-        synchronized (this) {
-            SparseArray<StartedCallback> callbacks = mStartedWatchers.get(callback.asBinder());
-            if (callbacks == null) {
-                callbacks = new SparseArray<>();
-                mStartedWatchers.put(callback.asBinder(), callbacks);
-            }
-
-            final StartedCallback startedCallback = new StartedCallback(callback, watchedUid,
-                    callingUid, callingPid);
-            for (int op : ops) {
-                callbacks.put(op, startedCallback);
-            }
-        }
+        mAppOpsService.startWatchingStarted(ops, callback);
     }
 
     @Override
     public void stopWatchingStarted(IAppOpsStartedCallback callback) {
-        Objects.requireNonNull(callback, "Callback cannot be null");
-
-        synchronized (this) {
-            final SparseArray<StartedCallback> startedCallbacks =
-                    mStartedWatchers.remove(callback.asBinder());
-            if (startedCallbacks == null) {
-                return;
-            }
-
-            final int callbackCount = startedCallbacks.size();
-            for (int i = 0; i < callbackCount; i++) {
-                startedCallbacks.valueAt(i).destroy();
-            }
-        }
+        mAppOpsService.stopWatchingStarted(callback);
     }
 
     @Override
     public void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback) {
-        int watchedUid = Process.INVALID_UID;
-        final int callingUid = Binder.getCallingUid();
-        final int callingPid = Binder.getCallingPid();
-        if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
-                != PackageManager.PERMISSION_GRANTED) {
-            watchedUid = callingUid;
-        }
-        Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
-        Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
-                "Invalid op code in: " + Arrays.toString(ops));
-        Objects.requireNonNull(callback, "Callback cannot be null");
-        synchronized (this) {
-            SparseArray<NotedCallback> callbacks = mNotedWatchers.get(callback.asBinder());
-            if (callbacks == null) {
-                callbacks = new SparseArray<>();
-                mNotedWatchers.put(callback.asBinder(), callbacks);
-            }
-            final NotedCallback notedCallback = new NotedCallback(callback, watchedUid,
-                    callingUid, callingPid);
-            for (int op : ops) {
-                callbacks.put(op, notedCallback);
-            }
-        }
+        mAppOpsService.startWatchingNoted(ops, callback);
     }
 
     @Override
     public void stopWatchingNoted(IAppOpsNotedCallback callback) {
-        Objects.requireNonNull(callback, "Callback cannot be null");
-        synchronized (this) {
-            final SparseArray<NotedCallback> notedCallbacks =
-                    mNotedWatchers.remove(callback.asBinder());
-            if (notedCallbacks == null) {
-                return;
-            }
-            final int callbackCount = notedCallbacks.size();
-            for (int i = 0; i < callbackCount; i++) {
-                notedCallbacks.valueAt(i).destroy();
-            }
-        }
+        mAppOpsService.stopWatchingNoted(callback);
     }
 
     /**
@@ -2817,7 +772,7 @@
         int uid = Binder.getCallingUid();
         Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
 
-        verifyAndGetBypass(uid, packageName, null);
+        mAppOpsService.verifyPackage(uid, packageName);
 
         synchronized (this) {
             RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
@@ -2847,7 +802,7 @@
         int uid = Binder.getCallingUid();
         Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
 
-        verifyAndGetBypass(uid, packageName, null);
+        mAppOpsService.verifyPackage(uid, packageName);
 
         synchronized (this) {
             RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
@@ -2866,7 +821,7 @@
 
         int uid = Binder.getCallingUid();
 
-        verifyAndGetBypass(uid, packageName, null);
+        mAppOpsService.verifyPackage(uid, packageName);
 
         synchronized (this) {
             return mUnforwardedAsyncNotedOps.remove(getAsyncNotedOpsKey(packageName, uid));
@@ -2889,54 +844,49 @@
             boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @NonNull String message,
             boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
             int attributionChainId) {
-        verifyIncomingUid(uid);
-        verifyIncomingOp(code);
         if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
             return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                     packageName);
         }
 
+        int result = mAppOpsService.startOperation(clientId, code, uid, packageName,
+                attributionTag, startIfModeDefault, message,
+                attributionFlags, attributionChainId);
+
         String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
-        if (resolvedPackageName == null) {
-            return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
-                    packageName);
+
+        boolean isAttributionTagValid = mAppOpsService.isAttributionTagValid(uid,
+                resolvedPackageName, attributionTag, null);
+
+        if (shouldCollectAsyncNotedOp && result == MODE_ALLOWED) {
+            collectAsyncNotedOp(uid, resolvedPackageName, code,
+                    isAttributionTagValid ? attributionTag : null, AppOpsManager.OP_FLAG_SELF,
+                    message, shouldCollectMessage);
         }
 
-        // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution
-        // purposes and not as a check, also make sure that the caller is allowed to access
-        // the data gated by OP_RECORD_AUDIO.
-        //
-        // TODO: Revert this change before Android 12.
-        if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) {
-            int result = checkOperation(OP_RECORD_AUDIO, uid, packageName);
-            if (result != AppOpsManager.MODE_ALLOWED) {
-                return new SyncNotedAppOp(result, code, attributionTag, packageName);
-            }
-        }
-        return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
-                Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
-                shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags,
-                attributionChainId, /*dryRun*/ false);
+        return new SyncNotedAppOp(result, code, isAttributionTagValid ? attributionTag : null,
+                resolvedPackageName);
     }
 
     @Override
-    public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
+    public SyncNotedAppOp startProxyOperation(IBinder clientId, int code,
             @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
             boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
             boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
             @AttributionFlags int proxiedAttributionFlags, int attributionChainId) {
-        return mCheckOpsDelegateDispatcher.startProxyOperation(clientId, code, attributionSource,
-                startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
-                skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlags,
-                attributionChainId);
+        return mCheckOpsDelegateDispatcher.startProxyOperation(clientId, code,
+                attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message,
+                shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
+                proxiedAttributionFlags, attributionChainId);
     }
 
-    private SyncNotedAppOp startProxyOperationImpl(@NonNull IBinder clientId, int code,
+    private SyncNotedAppOp startProxyOperationImpl(IBinder clientId, int code,
             @NonNull AttributionSource attributionSource,
             boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
             boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags
             int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags,
             int attributionChainId) {
+
         final int proxyUid = attributionSource.getUid();
         final String proxyPackageName = attributionSource.getPackageName();
         final String proxyAttributionTag = attributionSource.getAttributionTag();
@@ -2984,147 +934,68 @@
 
         if (!skipProxyOperation) {
             // Test if the proxied operation will succeed before starting the proxy operation
-            final SyncNotedAppOp testProxiedOp = startOperationUnchecked(clientId, code,
+            final int testProxiedOp = mAppOpsService.startOperationUnchecked(clientId, code,
                     proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag, proxyUid,
                     resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault,
-                    shouldCollectAsyncNotedOp, message, shouldCollectMessage,
                     proxiedAttributionFlags, attributionChainId, /*dryRun*/ true);
-            if (!shouldStartForMode(testProxiedOp.getOpMode(), startIfModeDefault)) {
-                return testProxiedOp;
+
+            boolean isTestProxiedAttributionTagValid =
+                    mAppOpsService.isAttributionTagValid(proxiedUid, resolvedProxiedPackageName,
+                            proxiedAttributionTag, resolvedProxyPackageName);
+
+            if (!shouldStartForMode(testProxiedOp, startIfModeDefault)) {
+                return new SyncNotedAppOp(testProxiedOp, code,
+                        isTestProxiedAttributionTagValid ? proxiedAttributionTag : null,
+                        resolvedProxiedPackageName);
             }
 
             final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
                     : AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
 
-            final SyncNotedAppOp proxyAppOp = startOperationUnchecked(clientId, code, proxyUid,
+            final int proxyAppOp = mAppOpsService.startOperationUnchecked(clientId, code, proxyUid,
                     resolvedProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
-                    proxyFlags, startIfModeDefault, !isProxyTrusted, "proxy " + message,
-                    shouldCollectMessage, proxyAttributionFlags, attributionChainId,
+                    proxyFlags, startIfModeDefault, proxyAttributionFlags, attributionChainId,
                     /*dryRun*/ false);
-            if (!shouldStartForMode(proxyAppOp.getOpMode(), startIfModeDefault)) {
-                return proxyAppOp;
+
+            boolean isProxyAttributionTagValid = mAppOpsService.isAttributionTagValid(proxyUid,
+                    resolvedProxyPackageName, proxyAttributionTag, null);
+
+            if (!shouldStartForMode(proxyAppOp, startIfModeDefault)) {
+                return new SyncNotedAppOp(proxyAppOp, code,
+                        isProxyAttributionTagValid ? proxyAttributionTag : null,
+                        resolvedProxyPackageName);
+            }
+
+            if (shouldCollectAsyncNotedOp) {
+                collectAsyncNotedOp(proxyUid, resolvedProxyPackageName, code,
+                        isProxyAttributionTagValid ? proxyAttributionTag : null, proxyFlags,
+                        message, shouldCollectMessage);
             }
         }
 
-        return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
-                proxiedAttributionTag, proxyUid, resolvedProxyPackageName, proxyAttributionTag,
-                proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp, message,
-                shouldCollectMessage, proxiedAttributionFlags, attributionChainId,
-                /*dryRun*/ false);
+        final int proxiedAppOp = mAppOpsService.startOperationUnchecked(clientId, code, proxiedUid,
+                resolvedProxiedPackageName, proxiedAttributionTag, proxyUid,
+                resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault,
+                proxiedAttributionFlags, attributionChainId,/*dryRun*/ false);
+
+        boolean isProxiedAttributionTagValid = mAppOpsService.isAttributionTagValid(proxiedUid,
+                resolvedProxiedPackageName, proxiedAttributionTag, resolvedProxyPackageName);
+
+        if (shouldCollectAsyncNotedOp && proxiedAppOp == MODE_ALLOWED) {
+            collectAsyncNotedOp(proxyUid, resolvedProxiedPackageName, code,
+                    isProxiedAttributionTagValid ? proxiedAttributionTag : null,
+                    proxiedAttributionFlags, message, shouldCollectMessage);
+        }
+
+        return new SyncNotedAppOp(proxiedAppOp, code,
+                isProxiedAttributionTagValid ? proxiedAttributionTag : null,
+                resolvedProxiedPackageName);
     }
 
     private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
         return (mode == MODE_ALLOWED || (mode == MODE_DEFAULT && startIfModeDefault));
     }
 
-    private SyncNotedAppOp startOperationUnchecked(IBinder clientId, int code, int uid,
-            @NonNull String packageName, @Nullable String attributionTag, int proxyUid,
-            String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
-            boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @Nullable String message,
-            boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
-            int attributionChainId, boolean dryRun) {
-        PackageVerificationResult pvr;
-        try {
-            pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
-            if (!pvr.isAttributionTagValid) {
-                attributionTag = null;
-            }
-        } catch (SecurityException e) {
-            Slog.e(TAG, "startOperation", e);
-            return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
-                    packageName);
-        }
-
-        boolean isRestricted = false;
-        int startType = START_TYPE_FAILED;
-        synchronized (this) {
-            final Ops ops = getOpsLocked(uid, packageName, attributionTag,
-                    pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
-            if (ops == null) {
-                if (!dryRun) {
-                    scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
-                            flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags,
-                            attributionChainId);
-                }
-                if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
-                        + " package " + packageName + " flags: "
-                        + AppOpsManager.flagsToString(flags));
-                return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
-                        packageName);
-            }
-            final Op op = getOpLocked(ops, code, uid, true);
-            final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
-            final UidState uidState = ops.uidState;
-            isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass,
-                    false);
-            final int switchCode = AppOpsManager.opToSwitch(code);
-            // If there is a non-default per UID policy (we set UID op mode only if
-            // non-default) it takes over, otherwise use the per package policy.
-            if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
-                final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
-                if (!shouldStartForMode(uidMode, startIfModeDefault)) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code "
-                                + switchCode + " (" + code + ") uid " + uid + " package "
-                                + packageName + " flags: " + AppOpsManager.flagsToString(flags));
-                    }
-                    if (!dryRun) {
-                        attributedOp.rejected(uidState.getState(), flags);
-                        scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
-                                flags, uidMode, startType, attributionFlags, attributionChainId);
-                    }
-                    return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
-                }
-            } else {
-                final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
-                        : op;
-                final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
-                if (mode != AppOpsManager.MODE_ALLOWED
-                        && (!startIfModeDefault || mode != MODE_DEFAULT)) {
-                    if (DEBUG) Slog.d(TAG, "startOperation: reject #" + mode + " for code "
-                            + switchCode + " (" + code + ") uid " + uid + " package "
-                            + packageName + " flags: " + AppOpsManager.flagsToString(flags));
-                    if (!dryRun) {
-                        attributedOp.rejected(uidState.getState(), flags);
-                        scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
-                                flags, mode, startType, attributionFlags, attributionChainId);
-                    }
-                    return new SyncNotedAppOp(mode, code, attributionTag, packageName);
-                }
-            }
-            if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
-                    + " package " + packageName + " restricted: " + isRestricted
-                    + " flags: " + AppOpsManager.flagsToString(flags));
-            if (!dryRun) {
-                try {
-                    if (isRestricted) {
-                        attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
-                                proxyAttributionTag, uidState.getState(), flags,
-                                attributionFlags, attributionChainId);
-                    } else {
-                        attributedOp.started(clientId, proxyUid, proxyPackageName,
-                                proxyAttributionTag, uidState.getState(), flags,
-                                attributionFlags, attributionChainId);
-                        startType = START_TYPE_STARTED;
-                    }
-                } catch (RemoteException e) {
-                    throw new RuntimeException(e);
-                }
-                scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
-                        isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
-                        attributionChainId);
-            }
-        }
-
-        if (shouldCollectAsyncNotedOp && !dryRun && !isRestricted) {
-            collectAsyncNotedOp(uid, packageName, code, attributionTag, AppOpsManager.OP_FLAG_SELF,
-                    message, shouldCollectMessage);
-        }
-
-        return new SyncNotedAppOp(isRestricted ? MODE_IGNORED : MODE_ALLOWED, code, attributionTag,
-                packageName);
-    }
-
     @Override
     public void finishOperation(IBinder clientId, int code, int uid, String packageName,
             String attributionTag) {
@@ -3134,22 +1005,11 @@
 
     private void finishOperationImpl(IBinder clientId, int code, int uid, String packageName,
             String attributionTag) {
-        verifyIncomingUid(uid);
-        verifyIncomingOp(code);
-        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
-            return;
-        }
-
-        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
-        if (resolvedPackageName == null) {
-            return;
-        }
-
-        finishOperationUnchecked(clientId, code, uid, resolvedPackageName, attributionTag);
+        mAppOpsService.finishOperation(clientId, code, uid, packageName, attributionTag);
     }
 
     @Override
-    public void finishProxyOperation(@NonNull IBinder clientId, int code,
+    public void finishProxyOperation(IBinder clientId, int code,
             @NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
         mCheckOpsDelegateDispatcher.finishProxyOperation(clientId, code, attributionSource,
                 skipProxyOperation);
@@ -3181,8 +1041,8 @@
         }
 
         if (!skipProxyOperation) {
-            finishOperationUnchecked(clientId, code, proxyUid, resolvedProxyPackageName,
-                    proxyAttributionTag);
+            mAppOpsService.finishOperationUnchecked(clientId, code, proxyUid,
+                    resolvedProxyPackageName, proxyAttributionTag);
         }
 
         String resolvedProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid,
@@ -3191,209 +1051,12 @@
             return null;
         }
 
-        finishOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
-                proxiedAttributionTag);
+        mAppOpsService.finishOperationUnchecked(clientId, code, proxiedUid,
+                resolvedProxiedPackageName, proxiedAttributionTag);
 
         return null;
     }
 
-    private void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName,
-            String attributionTag) {
-        PackageVerificationResult pvr;
-        try {
-            pvr = verifyAndGetBypass(uid, packageName, attributionTag);
-            if (!pvr.isAttributionTagValid) {
-                attributionTag = null;
-            }
-        } catch (SecurityException e) {
-            Slog.e(TAG, "Cannot finishOperation", e);
-            return;
-        }
-
-        synchronized (this) {
-            Op op = getOpLocked(code, uid, packageName, attributionTag, pvr.isAttributionTagValid,
-                    pvr.bypass, /* edit */ true);
-            if (op == null) {
-                Slog.e(TAG, "Operation not found: uid=" + uid + " pkg=" + packageName + "("
-                        + attributionTag + ") op=" + AppOpsManager.opToName(code));
-                return;
-            }
-            final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
-            if (attributedOp == null) {
-                Slog.e(TAG, "Attribution not found: uid=" + uid + " pkg=" + packageName + "("
-                        + attributionTag + ") op=" + AppOpsManager.opToName(code));
-                return;
-            }
-
-            if (attributedOp.isRunning() || attributedOp.isPaused()) {
-                attributedOp.finished(clientId);
-            } else {
-                Slog.e(TAG, "Operation not started: uid=" + uid + " pkg=" + packageName + "("
-                        + attributionTag + ") op=" + AppOpsManager.opToName(code));
-            }
-        }
-    }
-
-    void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull
-            String packageName, @Nullable String attributionTag, boolean active, @AttributionFlags
-            int attributionFlags, int attributionChainId) {
-        ArraySet<ActiveCallback> dispatchedCallbacks = null;
-        final int callbackListCount = mActiveWatchers.size();
-        for (int i = 0; i < callbackListCount; i++) {
-            final SparseArray<ActiveCallback> callbacks = mActiveWatchers.valueAt(i);
-            ActiveCallback callback = callbacks.get(code);
-            if (callback != null) {
-                if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
-                    continue;
-                }
-                if (dispatchedCallbacks == null) {
-                    dispatchedCallbacks = new ArraySet<>();
-                }
-                dispatchedCallbacks.add(callback);
-            }
-        }
-        if (dispatchedCallbacks == null) {
-            return;
-        }
-        mHandler.sendMessage(PooledLambda.obtainMessage(
-                AppOpsService::notifyOpActiveChanged,
-                this, dispatchedCallbacks, code, uid, packageName, attributionTag, active,
-                attributionFlags, attributionChainId));
-    }
-
-    private void notifyOpActiveChanged(ArraySet<ActiveCallback> callbacks,
-            int code, int uid, @NonNull String packageName, @Nullable String attributionTag,
-            boolean active, @AttributionFlags int attributionFlags, int attributionChainId) {
-        // There are features watching for mode changes such as window manager
-        // and location manager which are in our process. The callbacks in these
-        // features may require permissions our remote caller does not have.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            final int callbackCount = callbacks.size();
-            for (int i = 0; i < callbackCount; i++) {
-                final ActiveCallback callback = callbacks.valueAt(i);
-                try {
-                    if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
-                        continue;
-                    }
-                    callback.mCallback.opActiveChanged(code, uid, packageName, attributionTag,
-                            active, attributionFlags, attributionChainId);
-                } catch (RemoteException e) {
-                    /* do nothing */
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName,
-            String attributionTag, @OpFlags int flags, @Mode int result,
-            @AppOpsManager.OnOpStartedListener.StartedType int startedType,
-            @AttributionFlags int attributionFlags, int attributionChainId) {
-        ArraySet<StartedCallback> dispatchedCallbacks = null;
-        final int callbackListCount = mStartedWatchers.size();
-        for (int i = 0; i < callbackListCount; i++) {
-            final SparseArray<StartedCallback> callbacks = mStartedWatchers.valueAt(i);
-
-            StartedCallback callback = callbacks.get(code);
-            if (callback != null) {
-                if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
-                    continue;
-                }
-
-                if (dispatchedCallbacks == null) {
-                    dispatchedCallbacks = new ArraySet<>();
-                }
-                dispatchedCallbacks.add(callback);
-            }
-        }
-
-        if (dispatchedCallbacks == null) {
-            return;
-        }
-
-        mHandler.sendMessage(PooledLambda.obtainMessage(
-                AppOpsService::notifyOpStarted,
-                this, dispatchedCallbacks, code, uid, pkgName, attributionTag, flags,
-                result, startedType, attributionFlags, attributionChainId));
-    }
-
-    private void notifyOpStarted(ArraySet<StartedCallback> callbacks,
-            int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
-            @Mode int result, @AppOpsManager.OnOpStartedListener.StartedType int startedType,
-            @AttributionFlags int attributionFlags, int attributionChainId) {
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            final int callbackCount = callbacks.size();
-            for (int i = 0; i < callbackCount; i++) {
-                final StartedCallback callback = callbacks.valueAt(i);
-                try {
-                    if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
-                        continue;
-                    }
-                    callback.mCallback.opStarted(code, uid, packageName, attributionTag, flags,
-                            result, startedType, attributionFlags, attributionChainId);
-                } catch (RemoteException e) {
-                    /* do nothing */
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    private void scheduleOpNotedIfNeededLocked(int code, int uid, String packageName,
-            String attributionTag, @OpFlags int flags, @Mode int result) {
-        ArraySet<NotedCallback> dispatchedCallbacks = null;
-        final int callbackListCount = mNotedWatchers.size();
-        for (int i = 0; i < callbackListCount; i++) {
-            final SparseArray<NotedCallback> callbacks = mNotedWatchers.valueAt(i);
-            final NotedCallback callback = callbacks.get(code);
-            if (callback != null) {
-                if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
-                    continue;
-                }
-                if (dispatchedCallbacks == null) {
-                    dispatchedCallbacks = new ArraySet<>();
-                }
-                dispatchedCallbacks.add(callback);
-            }
-        }
-        if (dispatchedCallbacks == null) {
-            return;
-        }
-        mHandler.sendMessage(PooledLambda.obtainMessage(
-                AppOpsService::notifyOpChecked,
-                this, dispatchedCallbacks, code, uid, packageName, attributionTag, flags,
-                result));
-    }
-
-    private void notifyOpChecked(ArraySet<NotedCallback> callbacks,
-            int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
-            @Mode int result) {
-        // There are features watching for checks in our process. The callbacks in
-        // these features may require permissions our remote caller does not have.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            final int callbackCount = callbacks.size();
-            for (int i = 0; i < callbackCount; i++) {
-                final NotedCallback callback = callbacks.valueAt(i);
-                try {
-                    if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
-                        continue;
-                    }
-                    callback.mCallback.opNoted(code, uid, packageName, attributionTag, flags,
-                            result);
-                } catch (RemoteException e) {
-                    /* do nothing */
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
     @Override
     public int permissionToOpCode(String permission) {
         if (permission == null) {
@@ -3451,13 +1114,6 @@
                 Binder.getCallingPid(), Binder.getCallingUid(), null);
     }
 
-    private boolean shouldIgnoreCallback(int op, int watcherPid, int watcherUid) {
-        // If it's a restricted read op, ignore it if watcher doesn't have manage ops permission,
-        // as watcher should not use this to signal if the value is changed.
-        return opRestrictsRead(op) && mContext.checkPermission(Manifest.permission.MANAGE_APPOPS,
-                watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED;
-    }
-
     private void verifyIncomingOp(int op) {
         if (op >= 0 && op < AppOpsManager._NUM_OP) {
             // Enforce manage appops permission if it's a restricted read op.
@@ -3498,35 +1154,6 @@
                 || resolveUid(resolvedPackage) != Process.INVALID_UID;
     }
 
-    private boolean isCallerAndAttributionTrusted(@NonNull AttributionSource attributionSource) {
-        if (attributionSource.getUid() != Binder.getCallingUid()
-                && attributionSource.isTrusted(mContext)) {
-            return true;
-        }
-        return mContext.checkPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
-                Binder.getCallingPid(), Binder.getCallingUid(), null)
-                == PackageManager.PERMISSION_GRANTED;
-    }
-
-    private @Nullable UidState getUidStateLocked(int uid, boolean edit) {
-        UidState uidState = mUidStates.get(uid);
-        if (uidState == null) {
-            if (!edit) {
-                return null;
-            }
-            uidState = new UidState(uid);
-            mUidStates.put(uid, uidState);
-        }
-
-        return uidState;
-    }
-
-    private void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible) {
-        synchronized (this) {
-            getUidStateTracker().updateAppWidgetVisibility(uidPackageNames, visible);
-        }
-    }
-
     /**
      * @return {@link PackageManagerInternal}
      */
@@ -3538,764 +1165,6 @@
         return mPackageManagerInternal;
     }
 
-    /**
-     * Create a restriction description matching the properties of the package.
-     *
-     * @param pkg The package to create the restriction description for
-     *
-     * @return The restriction matching the package
-     */
-    private RestrictionBypass getBypassforPackage(@NonNull AndroidPackage pkg) {
-        return new RestrictionBypass(pkg.getUid() == Process.SYSTEM_UID, pkg.isPrivileged(),
-                mContext.checkPermission(android.Manifest.permission
-                        .EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS, -1, pkg.getUid())
-                == PackageManager.PERMISSION_GRANTED);
-    }
-
-    /**
-     * @see #verifyAndGetBypass(int, String, String, String)
-     */
-    private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
-            @Nullable String attributionTag) {
-        return verifyAndGetBypass(uid, packageName, attributionTag, null);
-    }
-
-    /**
-     * Verify that package belongs to uid and return the {@link RestrictionBypass bypass
-     * description} for the package, along with a boolean indicating whether the attribution tag is
-     * valid.
-     *
-     * @param uid The uid the package belongs to
-     * @param packageName The package the might belong to the uid
-     * @param attributionTag attribution tag or {@code null} if no need to verify
-     * @param proxyPackageName The proxy package, from which the attribution tag is to be pulled
-     *
-     * @return PackageVerificationResult containing {@link RestrictionBypass} and whether the
-     *         attribution tag is valid
-     */
-    private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
-            @Nullable String attributionTag, @Nullable String proxyPackageName) {
-        if (uid == Process.ROOT_UID) {
-            // For backwards compatibility, don't check package name for root UID.
-            return new PackageVerificationResult(null,
-                    /* isAttributionTagValid */ true);
-        }
-        if (Process.isSdkSandboxUid(uid)) {
-            // SDK sandbox processes run in their own UID range, but their associated
-            // UID for checks should always be the UID of the package implementing SDK sandbox
-            // service.
-            // TODO: We will need to modify the callers of this function instead, so
-            // modifications and checks against the app ops state are done with the
-            // correct UID.
-            try {
-                final PackageManager pm = mContext.getPackageManager();
-                final String supplementalPackageName = pm.getSdkSandboxPackageName();
-                if (Objects.equals(packageName, supplementalPackageName)) {
-                    uid = pm.getPackageUidAsUser(supplementalPackageName,
-                            PackageManager.PackageInfoFlags.of(0), UserHandle.getUserId(uid));
-                }
-            } catch (PackageManager.NameNotFoundException e) {
-                // Shouldn't happen for the supplemental package
-                e.printStackTrace();
-            }
-        }
-
-
-        // Do not check if uid/packageName/attributionTag is already known.
-        synchronized (this) {
-            UidState uidState = mUidStates.get(uid);
-            if (uidState != null && uidState.pkgOps != null) {
-                Ops ops = uidState.pkgOps.get(packageName);
-
-                if (ops != null && (attributionTag == null || ops.knownAttributionTags.contains(
-                        attributionTag)) && ops.bypass != null) {
-                    return new PackageVerificationResult(ops.bypass,
-                            ops.validAttributionTags.contains(attributionTag));
-                }
-            }
-        }
-
-        int callingUid = Binder.getCallingUid();
-
-        // Allow any attribution tag for resolvable uids
-        int pkgUid;
-        if (Objects.equals(packageName, "com.android.shell")) {
-            // Special case for the shell which is a package but should be able
-            // to bypass app attribution tag restrictions.
-            pkgUid = Process.SHELL_UID;
-        } else {
-            pkgUid = resolveUid(packageName);
-        }
-        if (pkgUid != Process.INVALID_UID) {
-            if (pkgUid != UserHandle.getAppId(uid)) {
-                Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
-                        + "Package \"" + packageName + "\" does not belong to uid " + uid + ".");
-                String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
-                throw new SecurityException("Specified package \"" + packageName + "\" under uid "
-                        +  UserHandle.getAppId(uid) + otherUidMessage);
-            }
-            return new PackageVerificationResult(RestrictionBypass.UNRESTRICTED,
-                    /* isAttributionTagValid */ true);
-        }
-
-        int userId = UserHandle.getUserId(uid);
-        RestrictionBypass bypass = null;
-        boolean isAttributionTagValid = false;
-
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
-            AndroidPackage pkg = pmInt.getPackage(packageName);
-            if (pkg != null) {
-                isAttributionTagValid = isAttributionInPackage(pkg, attributionTag);
-                pkgUid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid()));
-                bypass = getBypassforPackage(pkg);
-            }
-            if (!isAttributionTagValid) {
-                AndroidPackage proxyPkg = proxyPackageName != null
-                        ? pmInt.getPackage(proxyPackageName) : null;
-                // Re-check in proxy.
-                isAttributionTagValid = isAttributionInPackage(proxyPkg, attributionTag);
-                String msg;
-                if (pkg != null && isAttributionTagValid) {
-                    msg = "attributionTag " + attributionTag + " declared in manifest of the proxy"
-                            + " package " + proxyPackageName + ", this is not advised";
-                } else if (pkg != null) {
-                    msg = "attributionTag " + attributionTag + " not declared in manifest of "
-                            + packageName;
-                } else {
-                    msg = "package " + packageName + " not found, can't check for "
-                            + "attributionTag " + attributionTag;
-                }
-
-                try {
-                    if (!mPlatformCompat.isChangeEnabledByPackageName(
-                            SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, packageName,
-                            userId) || !mPlatformCompat.isChangeEnabledByUid(
-                                    SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE,
-                            callingUid)) {
-                        // Do not override tags if overriding is not enabled for this package
-                        isAttributionTagValid = true;
-                    }
-                    Slog.e(TAG, msg);
-                } catch (RemoteException neverHappens) {
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-
-        if (pkgUid != uid) {
-            Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
-                    + "Package \"" + packageName + "\" does not belong to uid " + uid + ".");
-            String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
-            throw new SecurityException("Specified package \"" + packageName + "\" under uid " + uid
-                    + otherUidMessage);
-        }
-
-        return new PackageVerificationResult(bypass, isAttributionTagValid);
-    }
-
-    private boolean isAttributionInPackage(@Nullable AndroidPackage pkg,
-            @Nullable String attributionTag) {
-        if (pkg == null) {
-            return false;
-        } else if (attributionTag == null) {
-            return true;
-        }
-        if (pkg.getAttributions() != null) {
-            int numAttributions = pkg.getAttributions().size();
-            for (int i = 0; i < numAttributions; i++) {
-                if (pkg.getAttributions().get(i).getTag().equals(attributionTag)) {
-                    return true;
-                }
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Get (and potentially create) ops.
-     *
-     * @param uid The uid the package belongs to
-     * @param packageName The name of the package
-     * @param attributionTag attribution tag
-     * @param isAttributionTagValid whether the given attribution tag is valid
-     * @param bypass When to bypass certain op restrictions (can be null if edit == false)
-     * @param edit If an ops does not exist, create the ops?
-
-     * @return The ops
-     */
-    private Ops getOpsLocked(int uid, String packageName, @Nullable String attributionTag,
-            boolean isAttributionTagValid, @Nullable RestrictionBypass bypass, boolean edit) {
-        UidState uidState = getUidStateLocked(uid, edit);
-        if (uidState == null) {
-            return null;
-        }
-
-        if (uidState.pkgOps == null) {
-            if (!edit) {
-                return null;
-            }
-            uidState.pkgOps = new ArrayMap<>();
-        }
-
-        Ops ops = uidState.pkgOps.get(packageName);
-        if (ops == null) {
-            if (!edit) {
-                return null;
-            }
-            ops = new Ops(packageName, uidState);
-            uidState.pkgOps.put(packageName, ops);
-        }
-
-        if (edit) {
-            if (bypass != null) {
-                ops.bypass = bypass;
-            }
-
-            if (attributionTag != null) {
-                ops.knownAttributionTags.add(attributionTag);
-                if (isAttributionTagValid) {
-                    ops.validAttributionTags.add(attributionTag);
-                } else {
-                    ops.validAttributionTags.remove(attributionTag);
-                }
-            }
-        }
-
-        return ops;
-    }
-
-    @Override
-    public void scheduleWriteLocked() {
-        if (!mWriteScheduled) {
-            mWriteScheduled = true;
-            mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
-        }
-    }
-
-    @Override
-    public void scheduleFastWriteLocked() {
-        if (!mFastWriteScheduled) {
-            mWriteScheduled = true;
-            mFastWriteScheduled = true;
-            mHandler.removeCallbacks(mWriteRunner);
-            mHandler.postDelayed(mWriteRunner, 10*1000);
-        }
-    }
-
-    /**
-     * Get the state of an op for a uid.
-     *
-     * @param code The code of the op
-     * @param uid The uid the of the package
-     * @param packageName The package name for which to get the state for
-     * @param attributionTag The attribution tag
-     * @param isAttributionTagValid Whether the given attribution tag is valid
-     * @param bypass When to bypass certain op restrictions (can be null if edit == false)
-     * @param edit Iff {@code true} create the {@link Op} object if not yet created
-     *
-     * @return The {@link Op state} of the op
-     */
-    private @Nullable Op getOpLocked(int code, int uid, @NonNull String packageName,
-            @Nullable String attributionTag, boolean isAttributionTagValid,
-            @Nullable RestrictionBypass bypass, boolean edit) {
-        Ops ops = getOpsLocked(uid, packageName, attributionTag, isAttributionTagValid, bypass,
-                edit);
-        if (ops == null) {
-            return null;
-        }
-        return getOpLocked(ops, code, uid, edit);
-    }
-
-    private Op getOpLocked(Ops ops, int code, int uid, boolean edit) {
-        Op op = ops.get(code);
-        if (op == null) {
-            if (!edit) {
-                return null;
-            }
-            op = new Op(ops.uidState, ops.packageName, code, uid);
-            ops.put(code, op);
-        }
-        if (edit) {
-            scheduleWriteLocked();
-        }
-        return op;
-    }
-
-    private boolean isOpRestrictedDueToSuspend(int code, String packageName, int uid) {
-        if (!ArrayUtils.contains(OPS_RESTRICTED_ON_SUSPEND, code)) {
-            return false;
-        }
-        final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
-        return pmi.isPackageSuspended(packageName, UserHandle.getUserId(uid));
-    }
-
-    private boolean isOpRestrictedLocked(int uid, int code, String packageName,
-            String attributionTag, @Nullable RestrictionBypass appBypass, boolean isCheckOp) {
-        int restrictionSetCount = mOpGlobalRestrictions.size();
-
-        for (int i = 0; i < restrictionSetCount; i++) {
-            ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.valueAt(i);
-            if (restrictionState.hasRestriction(code)) {
-                return true;
-            }
-        }
-
-        int userHandle = UserHandle.getUserId(uid);
-        restrictionSetCount = mOpUserRestrictions.size();
-
-        for (int i = 0; i < restrictionSetCount; i++) {
-            // For each client, check that the given op is not restricted, or that the given
-            // package is exempt from the restriction.
-            ClientUserRestrictionState restrictionState = mOpUserRestrictions.valueAt(i);
-            if (restrictionState.hasRestriction(code, packageName, attributionTag, userHandle,
-                    isCheckOp)) {
-                RestrictionBypass opBypass = opAllowSystemBypassRestriction(code);
-                if (opBypass != null) {
-                    // If we are the system, bypass user restrictions for certain codes
-                    synchronized (this) {
-                        if (opBypass.isSystemUid && appBypass != null && appBypass.isSystemUid) {
-                            return false;
-                        }
-                        if (opBypass.isPrivileged && appBypass != null && appBypass.isPrivileged) {
-                            return false;
-                        }
-                        if (opBypass.isRecordAudioRestrictionExcept && appBypass != null
-                                && appBypass.isRecordAudioRestrictionExcept) {
-                            return false;
-                        }
-                    }
-                }
-                return true;
-            }
-        }
-        return false;
-    }
-
-    void readState() {
-        int oldVersion = NO_VERSION;
-        synchronized (mFile) {
-            synchronized (this) {
-                FileInputStream stream;
-                try {
-                    stream = mFile.openRead();
-                } catch (FileNotFoundException e) {
-                    Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
-                    return;
-                }
-                boolean success = false;
-                mUidStates.clear();
-                mAppOpsServiceInterface.clearAllModes();
-                try {
-                    TypedXmlPullParser parser = Xml.resolvePullParser(stream);
-                    int type;
-                    while ((type = parser.next()) != XmlPullParser.START_TAG
-                            && type != XmlPullParser.END_DOCUMENT) {
-                        ;
-                    }
-
-                    if (type != XmlPullParser.START_TAG) {
-                        throw new IllegalStateException("no start tag found");
-                    }
-
-                    oldVersion = parser.getAttributeInt(null, "v", NO_VERSION);
-
-                    int outerDepth = parser.getDepth();
-                    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                            && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-                        if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                            continue;
-                        }
-
-                        String tagName = parser.getName();
-                        if (tagName.equals("pkg")) {
-                            readPackage(parser);
-                        } else if (tagName.equals("uid")) {
-                            readUidOps(parser);
-                        } else {
-                            Slog.w(TAG, "Unknown element under <app-ops>: "
-                                    + parser.getName());
-                            XmlUtils.skipCurrentTag(parser);
-                        }
-                    }
-                    success = true;
-                } catch (IllegalStateException e) {
-                    Slog.w(TAG, "Failed parsing " + e);
-                } catch (NullPointerException e) {
-                    Slog.w(TAG, "Failed parsing " + e);
-                } catch (NumberFormatException e) {
-                    Slog.w(TAG, "Failed parsing " + e);
-                } catch (XmlPullParserException e) {
-                    Slog.w(TAG, "Failed parsing " + e);
-                } catch (IOException e) {
-                    Slog.w(TAG, "Failed parsing " + e);
-                } catch (IndexOutOfBoundsException e) {
-                    Slog.w(TAG, "Failed parsing " + e);
-                } finally {
-                    if (!success) {
-                        mUidStates.clear();
-                        mAppOpsServiceInterface.clearAllModes();
-                    }
-                    try {
-                        stream.close();
-                    } catch (IOException e) {
-                    }
-                }
-            }
-        }
-        synchronized (this) {
-            upgradeLocked(oldVersion);
-        }
-    }
-
-    private void upgradeRunAnyInBackgroundLocked() {
-        for (int i = 0; i < mUidStates.size(); i++) {
-            final UidState uidState = mUidStates.valueAt(i);
-            if (uidState == null) {
-                continue;
-            }
-            SparseIntArray opModes = uidState.getNonDefaultUidModes();
-            if (opModes != null) {
-                final int idx = opModes.indexOfKey(AppOpsManager.OP_RUN_IN_BACKGROUND);
-                if (idx >= 0) {
-                    uidState.setUidMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
-                            opModes.valueAt(idx));
-                }
-            }
-            if (uidState.pkgOps == null) {
-                continue;
-            }
-            boolean changed = false;
-            for (int j = 0; j < uidState.pkgOps.size(); j++) {
-                Ops ops = uidState.pkgOps.valueAt(j);
-                if (ops != null) {
-                    final Op op = ops.get(AppOpsManager.OP_RUN_IN_BACKGROUND);
-                    if (op != null && op.getMode() != AppOpsManager.opToDefaultMode(op.op)) {
-                        final Op copy = new Op(op.uidState, op.packageName,
-                                AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uidState.uid);
-                        copy.setMode(op.getMode());
-                        ops.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, copy);
-                        changed = true;
-                    }
-                }
-            }
-            if (changed) {
-                uidState.evalForegroundOps();
-            }
-        }
-    }
-
-    private void upgradeLocked(int oldVersion) {
-        if (oldVersion >= CURRENT_VERSION) {
-            return;
-        }
-        Slog.d(TAG, "Upgrading app-ops xml from version " + oldVersion + " to " + CURRENT_VERSION);
-        switch (oldVersion) {
-            case NO_VERSION:
-                upgradeRunAnyInBackgroundLocked();
-                // fall through
-            case 1:
-                // for future upgrades
-        }
-        scheduleFastWriteLocked();
-    }
-
-    private void readUidOps(TypedXmlPullParser parser) throws NumberFormatException,
-            XmlPullParserException, IOException {
-        final int uid = parser.getAttributeInt(null, "n");
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-
-            String tagName = parser.getName();
-            if (tagName.equals("op")) {
-                final int code = parser.getAttributeInt(null, "n");
-                final int mode = parser.getAttributeInt(null, "m");
-                setUidMode(code, uid, mode);
-            } else {
-                Slog.w(TAG, "Unknown element under <uid-ops>: "
-                        + parser.getName());
-                XmlUtils.skipCurrentTag(parser);
-            }
-        }
-    }
-
-    private void readPackage(TypedXmlPullParser parser)
-            throws NumberFormatException, XmlPullParserException, IOException {
-        String pkgName = parser.getAttributeValue(null, "n");
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-
-            String tagName = parser.getName();
-            if (tagName.equals("uid")) {
-                readUid(parser, pkgName);
-            } else {
-                Slog.w(TAG, "Unknown element under <pkg>: "
-                        + parser.getName());
-                XmlUtils.skipCurrentTag(parser);
-            }
-        }
-    }
-
-    private void readUid(TypedXmlPullParser parser, String pkgName)
-            throws NumberFormatException, XmlPullParserException, IOException {
-        int uid = parser.getAttributeInt(null, "n");
-        final UidState uidState = getUidStateLocked(uid, true);
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-            String tagName = parser.getName();
-            if (tagName.equals("op")) {
-                readOp(parser, uidState, pkgName);
-            } else {
-                Slog.w(TAG, "Unknown element under <pkg>: "
-                        + parser.getName());
-                XmlUtils.skipCurrentTag(parser);
-            }
-        }
-        uidState.evalForegroundOps();
-    }
-
-    private void readAttributionOp(TypedXmlPullParser parser, @NonNull Op parent,
-            @Nullable String attribution)
-            throws NumberFormatException, IOException, XmlPullParserException {
-        final AttributedOp attributedOp = parent.getOrCreateAttribution(parent, attribution);
-
-        final long key = parser.getAttributeLong(null, "n");
-        final int uidState = extractUidStateFromKey(key);
-        final int opFlags = extractFlagsFromKey(key);
-
-        final long accessTime = parser.getAttributeLong(null, "t", 0);
-        final long rejectTime = parser.getAttributeLong(null, "r", 0);
-        final long accessDuration = parser.getAttributeLong(null, "d", -1);
-        final String proxyPkg = XmlUtils.readStringAttribute(parser, "pp");
-        final int proxyUid = parser.getAttributeInt(null, "pu", Process.INVALID_UID);
-        final String proxyAttributionTag = XmlUtils.readStringAttribute(parser, "pc");
-
-        if (accessTime > 0) {
-            attributedOp.accessed(accessTime, accessDuration, proxyUid, proxyPkg,
-                    proxyAttributionTag, uidState, opFlags);
-        }
-        if (rejectTime > 0) {
-            attributedOp.rejected(rejectTime, uidState, opFlags);
-        }
-    }
-
-    private void readOp(TypedXmlPullParser parser,
-            @NonNull UidState uidState, @NonNull String pkgName)
-            throws NumberFormatException, XmlPullParserException, IOException {
-        int opCode = parser.getAttributeInt(null, "n");
-        Op op = new Op(uidState, pkgName, opCode, uidState.uid);
-
-        final int mode = parser.getAttributeInt(null, "m", AppOpsManager.opToDefaultMode(op.op));
-        op.setMode(mode);
-
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-            String tagName = parser.getName();
-            if (tagName.equals("st")) {
-                readAttributionOp(parser, op, XmlUtils.readStringAttribute(parser, "id"));
-            } else {
-                Slog.w(TAG, "Unknown element under <op>: "
-                        + parser.getName());
-                XmlUtils.skipCurrentTag(parser);
-            }
-        }
-
-        if (uidState.pkgOps == null) {
-            uidState.pkgOps = new ArrayMap<>();
-        }
-        Ops ops = uidState.pkgOps.get(pkgName);
-        if (ops == null) {
-            ops = new Ops(pkgName, uidState);
-            uidState.pkgOps.put(pkgName, ops);
-        }
-        ops.put(op.op, op);
-    }
-
-    void writeState() {
-        synchronized (mFile) {
-            FileOutputStream stream;
-            try {
-                stream = mFile.startWrite();
-            } catch (IOException e) {
-                Slog.w(TAG, "Failed to write state: " + e);
-                return;
-            }
-
-            List<AppOpsManager.PackageOps> allOps = getPackagesForOps(null);
-
-            try {
-                TypedXmlSerializer out = Xml.resolveSerializer(stream);
-                out.startDocument(null, true);
-                out.startTag(null, "app-ops");
-                out.attributeInt(null, "v", CURRENT_VERSION);
-
-                SparseArray<SparseIntArray> uidStatesClone;
-                synchronized (this) {
-                    uidStatesClone = new SparseArray<>(mUidStates.size());
-
-                    final int uidStateCount = mUidStates.size();
-                    for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
-                        UidState uidState = mUidStates.valueAt(uidStateNum);
-                        int uid = mUidStates.keyAt(uidStateNum);
-
-                        SparseIntArray opModes = uidState.getNonDefaultUidModes();
-                        if (opModes != null && opModes.size() > 0) {
-                            uidStatesClone.put(uid, opModes);
-                        }
-                    }
-                }
-
-                final int uidStateCount = uidStatesClone.size();
-                for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
-                    SparseIntArray opModes = uidStatesClone.valueAt(uidStateNum);
-                    if (opModes != null && opModes.size() > 0) {
-                        out.startTag(null, "uid");
-                        out.attributeInt(null, "n", uidStatesClone.keyAt(uidStateNum));
-                        final int opCount = opModes.size();
-                        for (int opCountNum = 0; opCountNum < opCount; opCountNum++) {
-                            final int op = opModes.keyAt(opCountNum);
-                            final int mode = opModes.valueAt(opCountNum);
-                            out.startTag(null, "op");
-                            out.attributeInt(null, "n", op);
-                            out.attributeInt(null, "m", mode);
-                            out.endTag(null, "op");
-                        }
-                        out.endTag(null, "uid");
-                    }
-                }
-
-                if (allOps != null) {
-                    String lastPkg = null;
-                    for (int i=0; i<allOps.size(); i++) {
-                        AppOpsManager.PackageOps pkg = allOps.get(i);
-                        if (!Objects.equals(pkg.getPackageName(), lastPkg)) {
-                            if (lastPkg != null) {
-                                out.endTag(null, "pkg");
-                            }
-                            lastPkg = pkg.getPackageName();
-                            if (lastPkg != null) {
-                                out.startTag(null, "pkg");
-                                out.attribute(null, "n", lastPkg);
-                            }
-                        }
-                        out.startTag(null, "uid");
-                        out.attributeInt(null, "n", pkg.getUid());
-                        List<AppOpsManager.OpEntry> ops = pkg.getOps();
-                        for (int j=0; j<ops.size(); j++) {
-                            AppOpsManager.OpEntry op = ops.get(j);
-                            out.startTag(null, "op");
-                            out.attributeInt(null, "n", op.getOp());
-                            if (op.getMode() != AppOpsManager.opToDefaultMode(op.getOp())) {
-                                out.attributeInt(null, "m", op.getMode());
-                            }
-
-                            for (String attributionTag : op.getAttributedOpEntries().keySet()) {
-                                final AttributedOpEntry attribution =
-                                        op.getAttributedOpEntries().get(attributionTag);
-
-                                final ArraySet<Long> keys = attribution.collectKeys();
-
-                                final int keyCount = keys.size();
-                                for (int k = 0; k < keyCount; k++) {
-                                    final long key = keys.valueAt(k);
-
-                                    final int uidState = AppOpsManager.extractUidStateFromKey(key);
-                                    final int flags = AppOpsManager.extractFlagsFromKey(key);
-
-                                    final long accessTime = attribution.getLastAccessTime(uidState,
-                                            uidState, flags);
-                                    final long rejectTime = attribution.getLastRejectTime(uidState,
-                                            uidState, flags);
-                                    final long accessDuration = attribution.getLastDuration(
-                                            uidState, uidState, flags);
-                                    // Proxy information for rejections is not backed up
-                                    final OpEventProxyInfo proxy = attribution.getLastProxyInfo(
-                                            uidState, uidState, flags);
-
-                                    if (accessTime <= 0 && rejectTime <= 0 && accessDuration <= 0
-                                            && proxy == null) {
-                                        continue;
-                                    }
-
-                                    String proxyPkg = null;
-                                    String proxyAttributionTag = null;
-                                    int proxyUid = Process.INVALID_UID;
-                                    if (proxy != null) {
-                                        proxyPkg = proxy.getPackageName();
-                                        proxyAttributionTag = proxy.getAttributionTag();
-                                        proxyUid = proxy.getUid();
-                                    }
-
-                                    out.startTag(null, "st");
-                                    if (attributionTag != null) {
-                                        out.attribute(null, "id", attributionTag);
-                                    }
-                                    out.attributeLong(null, "n", key);
-                                    if (accessTime > 0) {
-                                        out.attributeLong(null, "t", accessTime);
-                                    }
-                                    if (rejectTime > 0) {
-                                        out.attributeLong(null, "r", rejectTime);
-                                    }
-                                    if (accessDuration > 0) {
-                                        out.attributeLong(null, "d", accessDuration);
-                                    }
-                                    if (proxyPkg != null) {
-                                        out.attribute(null, "pp", proxyPkg);
-                                    }
-                                    if (proxyAttributionTag != null) {
-                                        out.attribute(null, "pc", proxyAttributionTag);
-                                    }
-                                    if (proxyUid >= 0) {
-                                        out.attributeInt(null, "pu", proxyUid);
-                                    }
-                                    out.endTag(null, "st");
-                                }
-                            }
-
-                            out.endTag(null, "op");
-                        }
-                        out.endTag(null, "uid");
-                    }
-                    if (lastPkg != null) {
-                        out.endTag(null, "pkg");
-                    }
-                }
-
-                out.endTag(null, "app-ops");
-                out.endDocument();
-                mFile.finishWrite(stream);
-            } catch (IOException e) {
-                Slog.w(TAG, "Failed to write state, restoring backup.", e);
-                mFile.failWrite(stream);
-            }
-        }
-        mHistoricalRegistry.writeAndClearDiscreteHistory();
-    }
-
     static class Shell extends ShellCommand {
         final IAppOpsService mInterface;
         final AppOpsService mInternal;
@@ -4309,7 +1178,6 @@
         int mode;
         int packageUid;
         int nonpackageUid;
-        final static Binder sBinder = new Binder();
         IBinder mToken;
         boolean targetsUid;
 
@@ -4330,7 +1198,7 @@
             dumpCommandHelp(pw);
         }
 
-        static private int strOpToOp(String op, PrintWriter err) {
+        static int strOpToOp(String op, PrintWriter err) {
             try {
                 return AppOpsManager.strOpToOp(op);
             } catch (IllegalArgumentException e) {
@@ -4527,6 +1395,24 @@
         pw.println("              not specified, the current user is assumed.");
     }
 
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        mAppOpsService.dump(fd, pw, args);
+
+        pw.println();
+        if (mCheckOpsDelegateDispatcher.mPolicy != null
+                && mCheckOpsDelegateDispatcher.mPolicy instanceof AppOpsPolicy) {
+            AppOpsPolicy policy = (AppOpsPolicy) mCheckOpsDelegateDispatcher.mPolicy;
+            policy.dumpTags(pw);
+        } else {
+            pw.println("  AppOps policy not set.");
+        }
+
+        if (mAudioRestrictionManager.hasActiveRestrictions()) {
+            pw.println();
+            mAudioRestrictionManager.dump(pw);
+        }
+    }
     static int onShellCommand(Shell shell, String cmd) {
         if (cmd == null) {
             return shell.handleDefaultCommands(cmd);
@@ -4730,14 +1616,12 @@
                     return 0;
                 }
                 case "write-settings": {
-                    shell.mInternal.enforceManageAppOpsModes(Binder.getCallingPid(),
+                    shell.mInternal.mAppOpsService
+                            .enforceManageAppOpsModes(Binder.getCallingPid(),
                             Binder.getCallingUid(), -1);
                     final long token = Binder.clearCallingIdentity();
                     try {
-                        synchronized (shell.mInternal) {
-                            shell.mInternal.mHandler.removeCallbacks(shell.mInternal.mWriteRunner);
-                        }
-                        shell.mInternal.writeState();
+                        shell.mInternal.mAppOpsService.writeState();
                         pw.println("Current settings written.");
                     } finally {
                         Binder.restoreCallingIdentity(token);
@@ -4745,11 +1629,12 @@
                     return 0;
                 }
                 case "read-settings": {
-                    shell.mInternal.enforceManageAppOpsModes(Binder.getCallingPid(),
-                            Binder.getCallingUid(), -1);
+                    shell.mInternal.mAppOpsService
+                            .enforceManageAppOpsModes(Binder.getCallingPid(),
+                                    Binder.getCallingUid(), -1);
                     final long token = Binder.clearCallingIdentity();
                     try {
-                        shell.mInternal.readState();
+                        shell.mInternal.mAppOpsService.readState();
                         pw.println("Last settings read.");
                     } finally {
                         Binder.restoreCallingIdentity(token);
@@ -4795,877 +1680,70 @@
         return -1;
     }
 
-    private void dumpHelp(PrintWriter pw) {
-        pw.println("AppOps service (appops) dump options:");
-        pw.println("  -h");
-        pw.println("    Print this help text.");
-        pw.println("  --op [OP]");
-        pw.println("    Limit output to data associated with the given app op code.");
-        pw.println("  --mode [MODE]");
-        pw.println("    Limit output to data associated with the given app op mode.");
-        pw.println("  --package [PACKAGE]");
-        pw.println("    Limit output to data associated with the given package name.");
-        pw.println("  --attributionTag [attributionTag]");
-        pw.println("    Limit output to data associated with the given attribution tag.");
-        pw.println("  --include-discrete [n]");
-        pw.println("    Include discrete ops limited to n per dimension. Use zero for no limit.");
-        pw.println("  --watchers");
-        pw.println("    Only output the watcher sections.");
-        pw.println("  --history");
-        pw.println("    Only output history.");
-        pw.println("  --uid-state-changes");
-        pw.println("    Include logs about uid state changes.");
-    }
-
-    private void dumpStatesLocked(@NonNull PrintWriter pw, @Nullable String filterAttributionTag,
-            @HistoricalOpsRequestFilter int filter, long nowElapsed, @NonNull Op op, long now,
-            @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix) {
-        final int numAttributions = op.mAttributions.size();
-        for (int i = 0; i < numAttributions; i++) {
-            if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !Objects.equals(
-                    op.mAttributions.keyAt(i), filterAttributionTag)) {
-                continue;
-            }
-
-            pw.print(prefix + op.mAttributions.keyAt(i) + "=[\n");
-            dumpStatesLocked(pw, nowElapsed, op, op.mAttributions.keyAt(i), now, sdf, date,
-                    prefix + "  ");
-            pw.print(prefix + "]\n");
-        }
-    }
-
-    private void dumpStatesLocked(@NonNull PrintWriter pw, long nowElapsed, @NonNull Op op,
-            @Nullable String attributionTag, long now, @NonNull SimpleDateFormat sdf,
-            @NonNull Date date, @NonNull String prefix) {
-
-        final AttributedOpEntry entry = op.createSingleAttributionEntryLocked(
-                attributionTag).getAttributedOpEntries().get(attributionTag);
-
-        final ArraySet<Long> keys = entry.collectKeys();
-
-        final int keyCount = keys.size();
-        for (int k = 0; k < keyCount; k++) {
-            final long key = keys.valueAt(k);
-
-            final int uidState = AppOpsManager.extractUidStateFromKey(key);
-            final int flags = AppOpsManager.extractFlagsFromKey(key);
-
-            final long accessTime = entry.getLastAccessTime(uidState, uidState, flags);
-            final long rejectTime = entry.getLastRejectTime(uidState, uidState, flags);
-            final long accessDuration = entry.getLastDuration(uidState, uidState, flags);
-            final OpEventProxyInfo proxy = entry.getLastProxyInfo(uidState, uidState, flags);
-
-            String proxyPkg = null;
-            String proxyAttributionTag = null;
-            int proxyUid = Process.INVALID_UID;
-            if (proxy != null) {
-                proxyPkg = proxy.getPackageName();
-                proxyAttributionTag = proxy.getAttributionTag();
-                proxyUid = proxy.getUid();
-            }
-
-            if (accessTime > 0) {
-                pw.print(prefix);
-                pw.print("Access: ");
-                pw.print(AppOpsManager.keyToString(key));
-                pw.print(" ");
-                date.setTime(accessTime);
-                pw.print(sdf.format(date));
-                pw.print(" (");
-                TimeUtils.formatDuration(accessTime - now, pw);
-                pw.print(")");
-                if (accessDuration > 0) {
-                    pw.print(" duration=");
-                    TimeUtils.formatDuration(accessDuration, pw);
-                }
-                if (proxyUid >= 0) {
-                    pw.print(" proxy[");
-                    pw.print("uid=");
-                    pw.print(proxyUid);
-                    pw.print(", pkg=");
-                    pw.print(proxyPkg);
-                    pw.print(", attributionTag=");
-                    pw.print(proxyAttributionTag);
-                    pw.print("]");
-                }
-                pw.println();
-            }
-
-            if (rejectTime > 0) {
-                pw.print(prefix);
-                pw.print("Reject: ");
-                pw.print(AppOpsManager.keyToString(key));
-                date.setTime(rejectTime);
-                pw.print(sdf.format(date));
-                pw.print(" (");
-                TimeUtils.formatDuration(rejectTime - now, pw);
-                pw.print(")");
-                if (proxyUid >= 0) {
-                    pw.print(" proxy[");
-                    pw.print("uid=");
-                    pw.print(proxyUid);
-                    pw.print(", pkg=");
-                    pw.print(proxyPkg);
-                    pw.print(", attributionTag=");
-                    pw.print(proxyAttributionTag);
-                    pw.print("]");
-                }
-                pw.println();
-            }
-        }
-
-        final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
-        if (attributedOp.isRunning()) {
-            long earliestElapsedTime = Long.MAX_VALUE;
-            long maxNumStarts = 0;
-            int numInProgressEvents = attributedOp.mInProgressEvents.size();
-            for (int i = 0; i < numInProgressEvents; i++) {
-                AttributedOp.InProgressStartOpEvent event =
-                        attributedOp.mInProgressEvents.valueAt(i);
-
-                earliestElapsedTime = Math.min(earliestElapsedTime, event.getStartElapsedTime());
-                maxNumStarts = Math.max(maxNumStarts, event.mNumUnfinishedStarts);
-            }
-
-            pw.print(prefix + "Running start at: ");
-            TimeUtils.formatDuration(nowElapsed - earliestElapsedTime, pw);
-            pw.println();
-
-            if (maxNumStarts > 1) {
-                pw.print(prefix + "startNesting=");
-                pw.println(maxNumStarts);
-            }
-        }
-    }
-
-    @NeverCompile // Avoid size overhead of debugging code.
-    @Override
-    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
-
-        int dumpOp = OP_NONE;
-        String dumpPackage = null;
-        String dumpAttributionTag = null;
-        int dumpUid = Process.INVALID_UID;
-        int dumpMode = -1;
-        boolean dumpWatchers = false;
-        // TODO ntmyren: Remove the dumpHistory and dumpFilter
-        boolean dumpHistory = false;
-        boolean includeDiscreteOps = false;
-        boolean dumpUidStateChangeLogs = false;
-        int nDiscreteOps = 10;
-        @HistoricalOpsRequestFilter int dumpFilter = 0;
-        boolean dumpAll = false;
-
-        if (args != null) {
-            for (int i = 0; i < args.length; i++) {
-                String arg = args[i];
-                if ("-h".equals(arg)) {
-                    dumpHelp(pw);
-                    return;
-                } else if ("-a".equals(arg)) {
-                    // dump all data
-                    dumpAll = true;
-                } else if ("--op".equals(arg)) {
-                    i++;
-                    if (i >= args.length) {
-                        pw.println("No argument for --op option");
-                        return;
-                    }
-                    dumpOp = Shell.strOpToOp(args[i], pw);
-                    dumpFilter |= FILTER_BY_OP_NAMES;
-                    if (dumpOp < 0) {
-                        return;
-                    }
-                } else if ("--package".equals(arg)) {
-                    i++;
-                    if (i >= args.length) {
-                        pw.println("No argument for --package option");
-                        return;
-                    }
-                    dumpPackage = args[i];
-                    dumpFilter |= FILTER_BY_PACKAGE_NAME;
-                    try {
-                        dumpUid = AppGlobals.getPackageManager().getPackageUid(dumpPackage,
-                                PackageManager.MATCH_KNOWN_PACKAGES | PackageManager.MATCH_INSTANT,
-                                0);
-                    } catch (RemoteException e) {
-                    }
-                    if (dumpUid < 0) {
-                        pw.println("Unknown package: " + dumpPackage);
-                        return;
-                    }
-                    dumpUid = UserHandle.getAppId(dumpUid);
-                    dumpFilter |= FILTER_BY_UID;
-                } else if ("--attributionTag".equals(arg)) {
-                    i++;
-                    if (i >= args.length) {
-                        pw.println("No argument for --attributionTag option");
-                        return;
-                    }
-                    dumpAttributionTag = args[i];
-                    dumpFilter |= FILTER_BY_ATTRIBUTION_TAG;
-                } else if ("--mode".equals(arg)) {
-                    i++;
-                    if (i >= args.length) {
-                        pw.println("No argument for --mode option");
-                        return;
-                    }
-                    dumpMode = Shell.strModeToMode(args[i], pw);
-                    if (dumpMode < 0) {
-                        return;
-                    }
-                } else if ("--watchers".equals(arg)) {
-                    dumpWatchers = true;
-                } else if ("--include-discrete".equals(arg)) {
-                    i++;
-                    if (i >= args.length) {
-                        pw.println("No argument for --include-discrete option");
-                        return;
-                    }
-                    try {
-                        nDiscreteOps = Integer.valueOf(args[i]);
-                    } catch (NumberFormatException e) {
-                        pw.println("Wrong parameter: " + args[i]);
-                        return;
-                    }
-                    includeDiscreteOps = true;
-                } else if ("--history".equals(arg)) {
-                    dumpHistory = true;
-                } else if (arg.length() > 0 && arg.charAt(0) == '-') {
-                    pw.println("Unknown option: " + arg);
-                    return;
-                } else if ("--uid-state-changes".equals(arg)) {
-                    dumpUidStateChangeLogs = true;
-                } else {
-                    pw.println("Unknown command: " + arg);
-                    return;
-                }
-            }
-        }
-
-        final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
-        final Date date = new Date();
-        synchronized (this) {
-            pw.println("Current AppOps Service state:");
-            if (!dumpHistory && !dumpWatchers) {
-                mConstants.dump(pw);
-            }
-            pw.println();
-            final long now = System.currentTimeMillis();
-            final long nowElapsed = SystemClock.elapsedRealtime();
-            final long nowUptime = SystemClock.uptimeMillis();
-            boolean needSep = false;
-            if (dumpFilter == 0 && dumpMode < 0 && mProfileOwners != null && !dumpWatchers
-                    && !dumpHistory) {
-                pw.println("  Profile owners:");
-                for (int poi = 0; poi < mProfileOwners.size(); poi++) {
-                    pw.print("    User #");
-                    pw.print(mProfileOwners.keyAt(poi));
-                    pw.print(": ");
-                    UserHandle.formatUid(pw, mProfileOwners.valueAt(poi));
-                    pw.println();
-                }
-                pw.println();
-            }
-
-            if (!dumpHistory) {
-                needSep |= mAppOpsServiceInterface.dumpListeners(dumpOp, dumpUid, dumpPackage, pw);
-            }
-
-            if (mModeWatchers.size() > 0 && dumpOp < 0 && !dumpHistory) {
-                boolean printedHeader = false;
-                for (int i = 0; i < mModeWatchers.size(); i++) {
-                    final ModeCallback cb = mModeWatchers.valueAt(i);
-                    if (dumpPackage != null
-                            && dumpUid != UserHandle.getAppId(cb.getWatchingUid())) {
-                        continue;
-                    }
-                    needSep = true;
-                    if (!printedHeader) {
-                        pw.println("  All op mode watchers:");
-                        printedHeader = true;
-                    }
-                    pw.print("    ");
-                    pw.print(Integer.toHexString(System.identityHashCode(mModeWatchers.keyAt(i))));
-                    pw.print(": "); pw.println(cb);
-                }
-            }
-            if (mActiveWatchers.size() > 0 && dumpMode < 0) {
-                needSep = true;
-                boolean printedHeader = false;
-                for (int watcherNum = 0; watcherNum < mActiveWatchers.size(); watcherNum++) {
-                    final SparseArray<ActiveCallback> activeWatchers =
-                            mActiveWatchers.valueAt(watcherNum);
-                    if (activeWatchers.size() <= 0) {
-                        continue;
-                    }
-                    final ActiveCallback cb = activeWatchers.valueAt(0);
-                    if (dumpOp >= 0 && activeWatchers.indexOfKey(dumpOp) < 0) {
-                        continue;
-                    }
-                    if (dumpPackage != null
-                            && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
-                        continue;
-                    }
-                    if (!printedHeader) {
-                        pw.println("  All op active watchers:");
-                        printedHeader = true;
-                    }
-                    pw.print("    ");
-                    pw.print(Integer.toHexString(System.identityHashCode(
-                            mActiveWatchers.keyAt(watcherNum))));
-                    pw.println(" ->");
-                    pw.print("        [");
-                    final int opCount = activeWatchers.size();
-                    for (int opNum = 0; opNum < opCount; opNum++) {
-                        if (opNum > 0) {
-                            pw.print(' ');
-                        }
-                        pw.print(AppOpsManager.opToName(activeWatchers.keyAt(opNum)));
-                        if (opNum < opCount - 1) {
-                            pw.print(',');
-                        }
-                    }
-                    pw.println("]");
-                    pw.print("        ");
-                    pw.println(cb);
-                }
-            }
-            if (mStartedWatchers.size() > 0 && dumpMode < 0) {
-                needSep = true;
-                boolean printedHeader = false;
-
-                final int watchersSize = mStartedWatchers.size();
-                for (int watcherNum = 0; watcherNum < watchersSize; watcherNum++) {
-                    final SparseArray<StartedCallback> startedWatchers =
-                            mStartedWatchers.valueAt(watcherNum);
-                    if (startedWatchers.size() <= 0) {
-                        continue;
-                    }
-
-                    final StartedCallback cb = startedWatchers.valueAt(0);
-                    if (dumpOp >= 0 && startedWatchers.indexOfKey(dumpOp) < 0) {
-                        continue;
-                    }
-
-                    if (dumpPackage != null
-                            && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
-                        continue;
-                    }
-
-                    if (!printedHeader) {
-                        pw.println("  All op started watchers:");
-                        printedHeader = true;
-                    }
-
-                    pw.print("    ");
-                    pw.print(Integer.toHexString(System.identityHashCode(
-                            mStartedWatchers.keyAt(watcherNum))));
-                    pw.println(" ->");
-
-                    pw.print("        [");
-                    final int opCount = startedWatchers.size();
-                    for (int opNum = 0; opNum < opCount; opNum++) {
-                        if (opNum > 0) {
-                            pw.print(' ');
-                        }
-
-                        pw.print(AppOpsManager.opToName(startedWatchers.keyAt(opNum)));
-                        if (opNum < opCount - 1) {
-                            pw.print(',');
-                        }
-                    }
-                    pw.println("]");
-
-                    pw.print("        ");
-                    pw.println(cb);
-                }
-            }
-            if (mNotedWatchers.size() > 0 && dumpMode < 0) {
-                needSep = true;
-                boolean printedHeader = false;
-                for (int watcherNum = 0; watcherNum < mNotedWatchers.size(); watcherNum++) {
-                    final SparseArray<NotedCallback> notedWatchers =
-                            mNotedWatchers.valueAt(watcherNum);
-                    if (notedWatchers.size() <= 0) {
-                        continue;
-                    }
-                    final NotedCallback cb = notedWatchers.valueAt(0);
-                    if (dumpOp >= 0 && notedWatchers.indexOfKey(dumpOp) < 0) {
-                        continue;
-                    }
-                    if (dumpPackage != null
-                            && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
-                        continue;
-                    }
-                    if (!printedHeader) {
-                        pw.println("  All op noted watchers:");
-                        printedHeader = true;
-                    }
-                    pw.print("    ");
-                    pw.print(Integer.toHexString(System.identityHashCode(
-                            mNotedWatchers.keyAt(watcherNum))));
-                    pw.println(" ->");
-                    pw.print("        [");
-                    final int opCount = notedWatchers.size();
-                    for (int opNum = 0; opNum < opCount; opNum++) {
-                        if (opNum > 0) {
-                            pw.print(' ');
-                        }
-                        pw.print(AppOpsManager.opToName(notedWatchers.keyAt(opNum)));
-                        if (opNum < opCount - 1) {
-                            pw.print(',');
-                        }
-                    }
-                    pw.println("]");
-                    pw.print("        ");
-                    pw.println(cb);
-                }
-            }
-            if (mAudioRestrictionManager.hasActiveRestrictions() && dumpOp < 0
-                    && dumpPackage != null && dumpMode < 0 && !dumpWatchers) {
-                needSep = mAudioRestrictionManager.dump(pw) || needSep;
-            }
-            if (needSep) {
-                pw.println();
-            }
-            for (int i=0; i<mUidStates.size(); i++) {
-                UidState uidState = mUidStates.valueAt(i);
-                final SparseIntArray opModes = uidState.getNonDefaultUidModes();
-                final ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
-
-                if (dumpWatchers || dumpHistory) {
-                    continue;
-                }
-                if (dumpOp >= 0 || dumpPackage != null || dumpMode >= 0) {
-                    boolean hasOp = dumpOp < 0 || (opModes != null
-                            && opModes.indexOfKey(dumpOp) >= 0);
-                    boolean hasPackage = dumpPackage == null || dumpUid == mUidStates.keyAt(i);
-                    boolean hasMode = dumpMode < 0;
-                    if (!hasMode && opModes != null) {
-                        for (int opi = 0; !hasMode && opi < opModes.size(); opi++) {
-                            if (opModes.valueAt(opi) == dumpMode) {
-                                hasMode = true;
-                            }
-                        }
-                    }
-                    if (pkgOps != null) {
-                        for (int pkgi = 0;
-                                 (!hasOp || !hasPackage || !hasMode) && pkgi < pkgOps.size();
-                                 pkgi++) {
-                            Ops ops = pkgOps.valueAt(pkgi);
-                            if (!hasOp && ops != null && ops.indexOfKey(dumpOp) >= 0) {
-                                hasOp = true;
-                            }
-                            if (!hasMode) {
-                                for (int opi = 0; !hasMode && opi < ops.size(); opi++) {
-                                    if (ops.valueAt(opi).getMode() == dumpMode) {
-                                        hasMode = true;
-                                    }
-                                }
-                            }
-                            if (!hasPackage && dumpPackage.equals(ops.packageName)) {
-                                hasPackage = true;
-                            }
-                        }
-                    }
-                    if (uidState.foregroundOps != null && !hasOp) {
-                        if (uidState.foregroundOps.indexOfKey(dumpOp) > 0) {
-                            hasOp = true;
-                        }
-                    }
-                    if (!hasOp || !hasPackage || !hasMode) {
-                        continue;
-                    }
-                }
-
-                pw.print("  Uid "); UserHandle.formatUid(pw, uidState.uid); pw.println(":");
-                uidState.dump(pw, nowElapsed);
-                if (uidState.foregroundOps != null && (dumpMode < 0
-                        || dumpMode == AppOpsManager.MODE_FOREGROUND)) {
-                    pw.println("    foregroundOps:");
-                    for (int j = 0; j < uidState.foregroundOps.size(); j++) {
-                        if (dumpOp >= 0 && dumpOp != uidState.foregroundOps.keyAt(j)) {
-                            continue;
-                        }
-                        pw.print("      ");
-                        pw.print(AppOpsManager.opToName(uidState.foregroundOps.keyAt(j)));
-                        pw.print(": ");
-                        pw.println(uidState.foregroundOps.valueAt(j) ? "WATCHER" : "SILENT");
-                    }
-                    pw.print("    hasForegroundWatchers=");
-                    pw.println(uidState.hasForegroundWatchers);
-                }
-                needSep = true;
-
-                if (opModes != null) {
-                    final int opModeCount = opModes.size();
-                    for (int j = 0; j < opModeCount; j++) {
-                        final int code = opModes.keyAt(j);
-                        final int mode = opModes.valueAt(j);
-                        if (dumpOp >= 0 && dumpOp != code) {
-                            continue;
-                        }
-                        if (dumpMode >= 0 && dumpMode != mode) {
-                            continue;
-                        }
-                        pw.print("      "); pw.print(AppOpsManager.opToName(code));
-                        pw.print(": mode="); pw.println(AppOpsManager.modeToName(mode));
-                    }
-                }
-
-                if (pkgOps == null) {
-                    continue;
-                }
-
-                for (int pkgi = 0; pkgi < pkgOps.size(); pkgi++) {
-                    final Ops ops = pkgOps.valueAt(pkgi);
-                    if (dumpPackage != null && !dumpPackage.equals(ops.packageName)) {
-                        continue;
-                    }
-                    boolean printedPackage = false;
-                    for (int j=0; j<ops.size(); j++) {
-                        final Op op = ops.valueAt(j);
-                        final int opCode = op.op;
-                        if (dumpOp >= 0 && dumpOp != opCode) {
-                            continue;
-                        }
-                        if (dumpMode >= 0 && dumpMode != op.getMode()) {
-                            continue;
-                        }
-                        if (!printedPackage) {
-                            pw.print("    Package "); pw.print(ops.packageName); pw.println(":");
-                            printedPackage = true;
-                        }
-                        pw.print("      "); pw.print(AppOpsManager.opToName(opCode));
-                        pw.print(" ("); pw.print(AppOpsManager.modeToName(op.getMode()));
-                        final int switchOp = AppOpsManager.opToSwitch(opCode);
-                        if (switchOp != opCode) {
-                            pw.print(" / switch ");
-                            pw.print(AppOpsManager.opToName(switchOp));
-                            final Op switchObj = ops.get(switchOp);
-                            int mode = switchObj == null
-                                    ? AppOpsManager.opToDefaultMode(switchOp) : switchObj.getMode();
-                            pw.print("="); pw.print(AppOpsManager.modeToName(mode));
-                        }
-                        pw.println("): ");
-                        dumpStatesLocked(pw, dumpAttributionTag, dumpFilter, nowElapsed, op, now,
-                                sdf, date, "        ");
-                    }
-                }
-            }
-            if (needSep) {
-                pw.println();
-            }
-
-            boolean showUserRestrictions = !(dumpMode < 0 && !dumpWatchers && !dumpHistory);
-            mAppOpsRestrictions.dumpRestrictions(pw, dumpOp, dumpPackage, showUserRestrictions);
-
-            if (!dumpHistory && !dumpWatchers) {
-                pw.println();
-                if (mCheckOpsDelegateDispatcher.mPolicy != null
-                        && mCheckOpsDelegateDispatcher.mPolicy instanceof AppOpsPolicy) {
-                    AppOpsPolicy policy = (AppOpsPolicy) mCheckOpsDelegateDispatcher.mPolicy;
-                    policy.dumpTags(pw);
-                } else {
-                    pw.println("  AppOps policy not set.");
-                }
-            }
-
-            if (dumpAll || dumpUidStateChangeLogs) {
-                pw.println();
-                pw.println("Uid State Changes Event Log:");
-                getUidStateTracker().dumpEvents(pw);
-            }
-        }
-
-        // Must not hold the appops lock
-        if (dumpHistory && !dumpWatchers) {
-            mHistoricalRegistry.dump("  ", pw, dumpUid, dumpPackage, dumpAttributionTag, dumpOp,
-                    dumpFilter);
-        }
-        if (includeDiscreteOps) {
-            pw.println("Discrete accesses: ");
-            mHistoricalRegistry.dumpDiscreteData(pw, dumpUid, dumpPackage, dumpAttributionTag,
-                    dumpFilter, dumpOp, sdf, date, "  ", nDiscreteOps);
-        }
-    }
-
     @Override
     public void setUserRestrictions(Bundle restrictions, IBinder token, int userHandle) {
-        checkSystemUid("setUserRestrictions");
-        Objects.requireNonNull(restrictions);
-        Objects.requireNonNull(token);
-        for (int i = 0; i < AppOpsManager._NUM_OP; i++) {
-            String restriction = AppOpsManager.opToRestriction(i);
-            if (restriction != null) {
-                setUserRestrictionNoCheck(i, restrictions.getBoolean(restriction, false), token,
-                        userHandle, null);
-            }
-        }
+        mAppOpsService.setUserRestrictions(restrictions, token, userHandle);
     }
 
     @Override
     public void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle,
             PackageTagsList excludedPackageTags) {
-        if (Binder.getCallingPid() != Process.myPid()) {
-            mContext.enforcePermission(Manifest.permission.MANAGE_APP_OPS_RESTRICTIONS,
-                    Binder.getCallingPid(), Binder.getCallingUid(), null);
-        }
-        if (userHandle != UserHandle.getCallingUserId()) {
-            if (mContext.checkCallingOrSelfPermission(Manifest.permission
-                    .INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED
-                && mContext.checkCallingOrSelfPermission(Manifest.permission
-                    .INTERACT_ACROSS_USERS) != PackageManager.PERMISSION_GRANTED) {
-                throw new SecurityException("Need INTERACT_ACROSS_USERS_FULL or"
-                        + " INTERACT_ACROSS_USERS to interact cross user ");
-            }
-        }
-        verifyIncomingOp(code);
-        Objects.requireNonNull(token);
-        setUserRestrictionNoCheck(code, restricted, token, userHandle, excludedPackageTags);
-    }
-
-    private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token,
-            int userHandle, PackageTagsList excludedPackageTags) {
-        synchronized (AppOpsService.this) {
-            ClientUserRestrictionState restrictionState = mOpUserRestrictions.get(token);
-
-            if (restrictionState == null) {
-                try {
-                    restrictionState = new ClientUserRestrictionState(token);
-                } catch (RemoteException e) {
-                    return;
-                }
-                mOpUserRestrictions.put(token, restrictionState);
-            }
-
-            if (restrictionState.setRestriction(code, restricted, excludedPackageTags,
-                    userHandle)) {
-                mHandler.sendMessage(PooledLambda.obtainMessage(
-                        AppOpsService::notifyWatchersOfChange, this, code, UID_ANY));
-                mHandler.sendMessage(PooledLambda.obtainMessage(
-                        AppOpsService::updateStartedOpModeForUser, this, code, restricted,
-                        userHandle));
-            }
-
-            if (restrictionState.isDefault()) {
-                mOpUserRestrictions.remove(token);
-                restrictionState.destroy();
-            }
-        }
-    }
-
-    private void updateStartedOpModeForUser(int code, boolean restricted, int userId) {
-        synchronized (AppOpsService.this) {
-            int numUids = mUidStates.size();
-            for (int uidNum = 0; uidNum < numUids; uidNum++) {
-                int uid = mUidStates.keyAt(uidNum);
-                if (userId != UserHandle.USER_ALL && UserHandle.getUserId(uid) != userId) {
-                    continue;
-                }
-                updateStartedOpModeForUidLocked(code, restricted, uid);
-            }
-        }
-    }
-
-    private void updateStartedOpModeForUidLocked(int code, boolean restricted, int uid) {
-        UidState uidState = mUidStates.get(uid);
-        if (uidState == null || uidState.pkgOps == null) {
-            return;
-        }
-
-        int numPkgOps = uidState.pkgOps.size();
-        for (int pkgNum = 0; pkgNum < numPkgOps; pkgNum++) {
-            Ops ops = uidState.pkgOps.valueAt(pkgNum);
-            Op op = ops != null ? ops.get(code) : null;
-            if (op == null || (op.getMode() != MODE_ALLOWED && op.getMode() != MODE_FOREGROUND)) {
-                continue;
-            }
-            int numAttrTags = op.mAttributions.size();
-            for (int attrNum = 0; attrNum < numAttrTags; attrNum++) {
-                AttributedOp attrOp = op.mAttributions.valueAt(attrNum);
-                if (restricted && attrOp.isRunning()) {
-                    attrOp.pause();
-                } else if (attrOp.isPaused()) {
-                    attrOp.resume();
-                }
-            }
-        }
-    }
-
-    private void notifyWatchersOfChange(int code, int uid) {
-        final ArraySet<OnOpModeChangedListener> modeChangedListenerSet;
-        synchronized (this) {
-            modeChangedListenerSet = mAppOpsServiceInterface.getOpModeChangedListeners(code);
-            if (modeChangedListenerSet == null) {
-                return;
-            }
-        }
-
-        notifyOpChanged(modeChangedListenerSet,  code, uid, null);
+        mAppOpsService.setUserRestriction(code, restricted, token, userHandle,
+                excludedPackageTags);
     }
 
     @Override
     public void removeUser(int userHandle) throws RemoteException {
-        checkSystemUid("removeUser");
-        synchronized (AppOpsService.this) {
-            final int tokenCount = mOpUserRestrictions.size();
-            for (int i = tokenCount - 1; i >= 0; i--) {
-                ClientUserRestrictionState opRestrictions = mOpUserRestrictions.valueAt(i);
-                opRestrictions.removeUser(userHandle);
-            }
-            removeUidsForUserLocked(userHandle);
-        }
+        mAppOpsService.removeUser(userHandle);
     }
 
     @Override
     public boolean isOperationActive(int code, int uid, String packageName) {
-        if (Binder.getCallingUid() != uid) {
-            if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
-                    != PackageManager.PERMISSION_GRANTED) {
-                return false;
-            }
-        }
-        verifyIncomingOp(code);
-        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
-            return false;
-        }
-
-        final String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
-        if (resolvedPackageName == null) {
-            return false;
-        }
-        // TODO moltmann: Allow to check for attribution op activeness
-        synchronized (AppOpsService.this) {
-            Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null, false);
-            if (pkgOps == null) {
-                return false;
-            }
-
-            Op op = pkgOps.get(code);
-            if (op == null) {
-                return false;
-            }
-
-            return op.isRunning();
-        }
+        return mAppOpsService.isOperationActive(code, uid, packageName);
     }
 
     @Override
     public boolean isProxying(int op, @NonNull String proxyPackageName,
             @NonNull String proxyAttributionTag, int proxiedUid,
             @NonNull String proxiedPackageName) {
-        Objects.requireNonNull(proxyPackageName);
-        Objects.requireNonNull(proxiedPackageName);
-        final long callingUid = Binder.getCallingUid();
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            final List<AppOpsManager.PackageOps> packageOps = getOpsForPackage(proxiedUid,
-                    proxiedPackageName, new int[] {op});
-            if (packageOps == null || packageOps.isEmpty()) {
-                return false;
-            }
-            final List<OpEntry> opEntries = packageOps.get(0).getOps();
-            if (opEntries.isEmpty()) {
-                return false;
-            }
-            final OpEntry opEntry = opEntries.get(0);
-            if (!opEntry.isRunning()) {
-                return false;
-            }
-            final OpEventProxyInfo proxyInfo = opEntry.getLastProxyInfo(
-                    OP_FLAG_TRUSTED_PROXIED | AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED);
-            return proxyInfo != null && callingUid == proxyInfo.getUid()
-                    && proxyPackageName.equals(proxyInfo.getPackageName())
-                    && Objects.equals(proxyAttributionTag, proxyInfo.getAttributionTag());
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
+        return mAppOpsService.isProxying(op, proxyPackageName, proxyAttributionTag,
+                proxiedUid, proxiedPackageName);
     }
 
     @Override
     public void resetPackageOpsNoHistory(@NonNull String packageName) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "resetPackageOpsNoHistory");
-        synchronized (AppOpsService.this) {
-            final int uid = mPackageManagerInternal.getPackageUid(packageName, 0,
-                    UserHandle.getCallingUserId());
-            if (uid == Process.INVALID_UID) {
-                return;
-            }
-            UidState uidState = mUidStates.get(uid);
-            if (uidState == null || uidState.pkgOps == null) {
-                return;
-            }
-            Ops removedOps = uidState.pkgOps.remove(packageName);
-            mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
-            if (removedOps != null) {
-                scheduleFastWriteLocked();
-            }
-        }
+        mAppOpsService.resetPackageOpsNoHistory(packageName);
     }
 
     @Override
     public void setHistoryParameters(@AppOpsManager.HistoricalMode int mode,
             long baseSnapshotInterval, int compressionStep) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "setHistoryParameters");
-        // Must not hold the appops lock
-        mHistoricalRegistry.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
+        mAppOpsService.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
     }
 
     @Override
     public void offsetHistory(long offsetMillis) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "offsetHistory");
-        // Must not hold the appops lock
-        mHistoricalRegistry.offsetHistory(offsetMillis);
-        mHistoricalRegistry.offsetDiscreteHistory(offsetMillis);
+        mAppOpsService.offsetHistory(offsetMillis);
     }
 
     @Override
     public void addHistoricalOps(HistoricalOps ops) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "addHistoricalOps");
-        // Must not hold the appops lock
-        mHistoricalRegistry.addHistoricalOps(ops);
+        mAppOpsService.addHistoricalOps(ops);
     }
 
     @Override
     public void resetHistoryParameters() {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "resetHistoryParameters");
-        // Must not hold the appops lock
-        mHistoricalRegistry.resetHistoryParameters();
+        mAppOpsService.resetHistoryParameters();
     }
 
     @Override
     public void clearHistory() {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "clearHistory");
-        // Must not hold the appops lock
-        mHistoricalRegistry.clearAllHistory();
+        mAppOpsService.clearHistory();
     }
 
     @Override
     public void rebootHistory(long offlineDurationMillis) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "rebootHistory");
-
-        Preconditions.checkArgument(offlineDurationMillis >= 0);
-
-        // Must not hold the appops lock
-        mHistoricalRegistry.shutdown();
-
-        if (offlineDurationMillis > 0) {
-            SystemClock.sleep(offlineDurationMillis);
-        }
-
-        mHistoricalRegistry = new HistoricalRegistry(mHistoricalRegistry);
-        mHistoricalRegistry.systemReady(mContext.getContentResolver());
-        mHistoricalRegistry.persistPendingHistory();
+        mAppOpsService.rebootHistory(offlineDurationMillis);
     }
 
     /**
@@ -5920,24 +1998,6 @@
         return false;
     }
 
-    @GuardedBy("this")
-    private void removeUidsForUserLocked(int userHandle) {
-        for (int i = mUidStates.size() - 1; i >= 0; --i) {
-            final int uid = mUidStates.keyAt(i);
-            if (UserHandle.getUserId(uid) == userHandle) {
-                mUidStates.valueAt(i).clear();
-                mUidStates.removeAt(i);
-            }
-        }
-    }
-
-    private void checkSystemUid(String function) {
-        int uid = Binder.getCallingUid();
-        if (uid != Process.SYSTEM_UID) {
-            throw new SecurityException(function + " must by called by the system");
-        }
-    }
-
     private static int resolveUid(String packageName)  {
         if (packageName == null) {
             return Process.INVALID_UID;
@@ -5958,184 +2018,43 @@
         return Process.INVALID_UID;
     }
 
-    private static String[] getPackagesForUid(int uid) {
-        String[] packageNames = null;
-
-        // Very early during boot the package manager is not yet or not yet fully started. At this
-        // time there are no packages yet.
-        if (AppGlobals.getPackageManager() != null) {
-            try {
-                packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid);
-            } catch (RemoteException e) {
-                /* ignore - local call */
-            }
-        }
-        if (packageNames == null) {
-            return EmptyArray.STRING;
-        }
-        return packageNames;
-    }
-
-    private final class ClientUserRestrictionState implements DeathRecipient {
-        private final IBinder token;
-
-        ClientUserRestrictionState(IBinder token)
-                throws RemoteException {
-            token.linkToDeath(this, 0);
-            this.token = token;
-        }
-
-        public boolean setRestriction(int code, boolean restricted,
-                PackageTagsList excludedPackageTags, int userId) {
-            return mAppOpsRestrictions.setUserRestriction(token, userId, code,
-                    restricted, excludedPackageTags);
-        }
-
-        public boolean hasRestriction(int code, String packageName, String attributionTag,
-                int userId, boolean isCheckOp) {
-            return mAppOpsRestrictions.getUserRestriction(token, userId, code, packageName,
-                    attributionTag, isCheckOp);
-        }
-
-        public void removeUser(int userId) {
-            mAppOpsRestrictions.clearUserRestrictions(token, userId);
-        }
-
-        public boolean isDefault() {
-            return !mAppOpsRestrictions.hasUserRestrictions(token);
-        }
-
-        @Override
-        public void binderDied() {
-            synchronized (AppOpsService.this) {
-                mAppOpsRestrictions.clearUserRestrictions(token);
-                mOpUserRestrictions.remove(token);
-                destroy();
-            }
-        }
-
-        public void destroy() {
-            token.unlinkToDeath(this, 0);
-        }
-    }
-
-    private final class ClientGlobalRestrictionState implements DeathRecipient {
-        final IBinder mToken;
-
-        ClientGlobalRestrictionState(IBinder token)
-                throws RemoteException {
-            token.linkToDeath(this, 0);
-            this.mToken = token;
-        }
-
-        boolean setRestriction(int code, boolean restricted) {
-            return mAppOpsRestrictions.setGlobalRestriction(mToken, code, restricted);
-        }
-
-        boolean hasRestriction(int code) {
-            return mAppOpsRestrictions.getGlobalRestriction(mToken, code);
-        }
-
-        boolean isDefault() {
-            return !mAppOpsRestrictions.hasGlobalRestrictions(mToken);
-        }
-
-        @Override
-        public void binderDied() {
-            mAppOpsRestrictions.clearGlobalRestrictions(mToken);
-            mOpGlobalRestrictions.remove(mToken);
-            destroy();
-        }
-
-        void destroy() {
-            mToken.unlinkToDeath(this, 0);
-        }
-    }
-
     private final class AppOpsManagerInternalImpl extends AppOpsManagerInternal {
         @Override public void setDeviceAndProfileOwners(SparseIntArray owners) {
-            synchronized (AppOpsService.this) {
-                mProfileOwners = owners;
-            }
+            AppOpsService.this.mAppOpsService.setDeviceAndProfileOwners(owners);
         }
 
         @Override
         public void updateAppWidgetVisibility(SparseArray<String> uidPackageNames,
                 boolean visible) {
-            AppOpsService.this.updateAppWidgetVisibility(uidPackageNames, visible);
+            AppOpsService.this.mAppOpsService
+                    .updateAppWidgetVisibility(uidPackageNames, visible);
         }
 
         @Override
         public void setUidModeFromPermissionPolicy(int code, int uid, int mode,
                 @Nullable IAppOpsCallback callback) {
-            setUidMode(code, uid, mode, callback);
+            AppOpsService.this.mAppOpsService.setUidMode(code, uid, mode, callback);
         }
 
         @Override
         public void setModeFromPermissionPolicy(int code, int uid, @NonNull String packageName,
                 int mode, @Nullable IAppOpsCallback callback) {
-            setMode(code, uid, packageName, mode, callback);
+            AppOpsService.this.mAppOpsService
+                    .setMode(code, uid, packageName, mode, callback);
         }
 
 
         @Override
         public void setGlobalRestriction(int code, boolean restricted, IBinder token) {
-            if (Binder.getCallingPid() != Process.myPid()) {
-                // TODO instead of this enforcement put in AppOpsManagerInternal
-                throw new SecurityException("Only the system can set global restrictions");
-            }
-
-            synchronized (AppOpsService.this) {
-                ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.get(token);
-
-                if (restrictionState == null) {
-                    try {
-                        restrictionState = new ClientGlobalRestrictionState(token);
-                    } catch (RemoteException  e) {
-                        return;
-                    }
-                    mOpGlobalRestrictions.put(token, restrictionState);
-                }
-
-                if (restrictionState.setRestriction(code, restricted)) {
-                    mHandler.sendMessage(PooledLambda.obtainMessage(
-                            AppOpsService::notifyWatchersOfChange, AppOpsService.this, code,
-                            UID_ANY));
-                    mHandler.sendMessage(PooledLambda.obtainMessage(
-                            AppOpsService::updateStartedOpModeForUser, AppOpsService.this,
-                            code, restricted, UserHandle.USER_ALL));
-                }
-
-                if (restrictionState.isDefault()) {
-                    mOpGlobalRestrictions.remove(token);
-                    restrictionState.destroy();
-                }
-            }
+            AppOpsService.this.mAppOpsService
+                    .setGlobalRestriction(code, restricted, token);
         }
 
         @Override
         public int getOpRestrictionCount(int code, UserHandle user, String pkg,
                 String attributionTag) {
-            int number = 0;
-            synchronized (AppOpsService.this) {
-                int numRestrictions = mOpUserRestrictions.size();
-                for (int i = 0; i < numRestrictions; i++) {
-                    if (mOpUserRestrictions.valueAt(i)
-                            .hasRestriction(code, pkg, attributionTag, user.getIdentifier(),
-                                    false)) {
-                        number++;
-                    }
-                }
-
-                numRestrictions = mOpGlobalRestrictions.size();
-                for (int i = 0; i < numRestrictions; i++) {
-                    if (mOpGlobalRestrictions.valueAt(i).hasRestriction(code)) {
-                        number++;
-                    }
-                }
-            }
-
-            return number;
+            return AppOpsService.this.mAppOpsService
+                    .getOpRestrictionCount(code, user, pkg, attributionTag);
         }
     }
 
@@ -6431,7 +2350,7 @@
                     attributionFlags, attributionChainId, AppOpsService.this::startOperationImpl);
         }
 
-        public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
+        public SyncNotedAppOp startProxyOperation(IBinder clientId, int code,
                 @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
                 boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
                 boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
@@ -6461,7 +2380,7 @@
                     proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
         }
 
-        private SyncNotedAppOp startDelegateProxyOperationImpl(@NonNull IBinder clientId, int code,
+        private SyncNotedAppOp startDelegateProxyOperationImpl(IBinder clientId, int code,
                 @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
                 boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
                 boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
@@ -6495,7 +2414,7 @@
                     AppOpsService.this::finishOperationImpl);
         }
 
-        public void finishProxyOperation(@NonNull IBinder clientId, int code,
+        public void finishProxyOperation(IBinder clientId, int code,
                 @NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
             if (mPolicy != null) {
                 if (mCheckOpsDelegate != null) {
@@ -6513,7 +2432,7 @@
             }
         }
 
-        private Void finishDelegateProxyOperationImpl(@NonNull IBinder clientId, int code,
+        private Void finishDelegateProxyOperationImpl(IBinder clientId, int code,
                 @NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
             mCheckOpsDelegate.finishProxyOperation(clientId, code, attributionSource,
                     skipProxyOperation, AppOpsService.this::finishProxyOperationImpl);
diff --git a/services/core/java/com/android/server/appop/AppOpsServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsServiceImpl.java
new file mode 100644
index 0000000..70f3bcc
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpsServiceImpl.java
@@ -0,0 +1,4679 @@
+/*
+ * 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.server.appop;
+
+import static android.app.AppOpsManager.AttributedOpEntry;
+import static android.app.AppOpsManager.AttributionFlags;
+import static android.app.AppOpsManager.CALL_BACK_ON_SWITCHED_OP;
+import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG;
+import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
+import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
+import static android.app.AppOpsManager.FILTER_BY_UID;
+import static android.app.AppOpsManager.HISTORY_FLAG_GET_ATTRIBUTION_CHAINS;
+import static android.app.AppOpsManager.HistoricalOps;
+import static android.app.AppOpsManager.HistoricalOpsRequestFilter;
+import static android.app.AppOpsManager.KEY_BG_STATE_SETTLE_TIME;
+import static android.app.AppOpsManager.KEY_FG_SERVICE_STATE_SETTLE_TIME;
+import static android.app.AppOpsManager.KEY_TOP_STATE_SETTLE_TIME;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.app.AppOpsManager.MODE_ERRORED;
+import static android.app.AppOpsManager.MODE_FOREGROUND;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.Mode;
+import static android.app.AppOpsManager.OP_CAMERA;
+import static android.app.AppOpsManager.OP_FLAGS_ALL;
+import static android.app.AppOpsManager.OP_FLAG_SELF;
+import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
+import static android.app.AppOpsManager.OP_NONE;
+import static android.app.AppOpsManager.OP_PLAY_AUDIO;
+import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
+import static android.app.AppOpsManager.OP_VIBRATE;
+import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
+import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
+import static android.app.AppOpsManager.OpEntry;
+import static android.app.AppOpsManager.OpEventProxyInfo;
+import static android.app.AppOpsManager.OpFlags;
+import static android.app.AppOpsManager.RestrictionBypass;
+import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE;
+import static android.app.AppOpsManager._NUM_OP;
+import static android.app.AppOpsManager.extractFlagsFromKey;
+import static android.app.AppOpsManager.extractUidStateFromKey;
+import static android.app.AppOpsManager.modeToName;
+import static android.app.AppOpsManager.opAllowSystemBypassRestriction;
+import static android.app.AppOpsManager.opRestrictsRead;
+import static android.app.AppOpsManager.opToName;
+import static android.content.Intent.ACTION_PACKAGE_REMOVED;
+import static android.content.Intent.EXTRA_REPLACING;
+
+import static com.android.server.appop.AppOpsServiceImpl.ModeCallback.ALL_OPS;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.PermissionInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.PackageTagsList;
+import android.os.Process;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.storage.StorageManagerInternal;
+import android.permission.PermissionManager;
+import android.provider.Settings;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.AtomicFile;
+import android.util.KeyValueListParser;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+import android.util.TimeUtils;
+import android.util.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IAppOpsActiveCallback;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsNotedCallback;
+import com.android.internal.app.IAppOpsStartedCallback;
+import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.os.Clock;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.XmlUtils;
+import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.LocalServices;
+import com.android.server.LockGuard;
+import com.android.server.SystemServerInitThreadPool;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.component.ParsedAttribution;
+
+import dalvik.annotation.optimization.NeverCompile;
+
+import libcore.util.EmptyArray;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+class AppOpsServiceImpl implements AppOpsServiceInterface {
+    static final String TAG = "AppOps";
+    static final boolean DEBUG = false;
+
+    private static final int NO_VERSION = -1;
+    /**
+     * Increment by one every time and add the corresponding upgrade logic in
+     * {@link #upgradeLocked(int)} below. The first version was 1
+     */
+    private static final int CURRENT_VERSION = 1;
+
+    // Write at most every 30 minutes.
+    static final long WRITE_DELAY = DEBUG ? 1000 : 30 * 60 * 1000;
+
+    // Constant meaning that any UID should be matched when dispatching callbacks
+    private static final int UID_ANY = -2;
+
+    private static final int[] OPS_RESTRICTED_ON_SUSPEND = {
+            OP_PLAY_AUDIO,
+            OP_RECORD_AUDIO,
+            OP_CAMERA,
+            OP_VIBRATE,
+    };
+    private static final int MAX_UNUSED_POOLED_OBJECTS = 3;
+
+    final Context mContext;
+    final AtomicFile mFile;
+    final Handler mHandler;
+
+    /**
+     * Pool for {@link AttributedOp.OpEventProxyInfoPool} to avoid to constantly reallocate new
+     * objects
+     */
+    @GuardedBy("this")
+    final AttributedOp.OpEventProxyInfoPool mOpEventProxyInfoPool =
+            new AttributedOp.OpEventProxyInfoPool(MAX_UNUSED_POOLED_OBJECTS);
+
+    /**
+     * Pool for {@link AttributedOp.InProgressStartOpEventPool} to avoid to constantly reallocate
+     * new objects
+     */
+    @GuardedBy("this")
+    final AttributedOp.InProgressStartOpEventPool mInProgressStartOpEventPool =
+            new AttributedOp.InProgressStartOpEventPool(mOpEventProxyInfoPool,
+                    MAX_UNUSED_POOLED_OBJECTS);
+    @Nullable
+    private final DevicePolicyManagerInternal dpmi =
+            LocalServices.getService(DevicePolicyManagerInternal.class);
+
+    private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface(
+            ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+
+    boolean mWriteScheduled;
+    boolean mFastWriteScheduled;
+    final Runnable mWriteRunner = new Runnable() {
+        public void run() {
+            synchronized (AppOpsServiceImpl.this) {
+                mWriteScheduled = false;
+                mFastWriteScheduled = false;
+                AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+                    @Override
+                    protected Void doInBackground(Void... params) {
+                        writeState();
+                        return null;
+                    }
+                };
+                task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+            }
+        }
+    };
+
+    @GuardedBy("this")
+    @VisibleForTesting
+    final SparseArray<UidState> mUidStates = new SparseArray<>();
+
+    volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this);
+
+    /*
+     * These are app op restrictions imposed per user from various parties.
+     */
+    private final ArrayMap<IBinder, ClientUserRestrictionState> mOpUserRestrictions =
+            new ArrayMap<>();
+
+    /*
+     * These are app op restrictions imposed globally from various parties within the system.
+     */
+    private final ArrayMap<IBinder, ClientGlobalRestrictionState> mOpGlobalRestrictions =
+            new ArrayMap<>();
+
+    SparseIntArray mProfileOwners;
+
+    /**
+     * Reverse lookup for {@link AppOpsManager#opToSwitch(int)}. Initialized once and never
+     * changed
+     */
+    private final SparseArray<int[]> mSwitchedOps = new SparseArray<>();
+
+    /**
+     * Package Manager internal. Access via {@link #getPackageManagerInternal()}
+     */
+    private @Nullable PackageManagerInternal mPackageManagerInternal;
+
+    /**
+     * Interface for app-op modes.
+     */
+    @VisibleForTesting
+    AppOpsCheckingServiceInterface mAppOpsServiceInterface;
+
+    /**
+     * Interface for app-op restrictions.
+     */
+    @VisibleForTesting
+    AppOpsRestrictions mAppOpsRestrictions;
+
+    private AppOpsUidStateTracker mUidStateTracker;
+
+    /**
+     * Hands the definition of foreground and uid states
+     */
+    @GuardedBy("this")
+    public AppOpsUidStateTracker getUidStateTracker() {
+        if (mUidStateTracker == null) {
+            mUidStateTracker = new AppOpsUidStateTrackerImpl(
+                    LocalServices.getService(ActivityManagerInternal.class),
+                    mHandler,
+                    r -> {
+                        synchronized (AppOpsServiceImpl.this) {
+                            r.run();
+                        }
+                    },
+                    Clock.SYSTEM_CLOCK, mConstants);
+
+            mUidStateTracker.addUidStateChangedCallback(new HandlerExecutor(mHandler),
+                    this::onUidStateChanged);
+        }
+        return mUidStateTracker;
+    }
+
+    /**
+     * All times are in milliseconds. These constants are kept synchronized with the system
+     * global Settings. Any access to this class or its fields should be done while
+     * holding the AppOpsService lock.
+     */
+    final class Constants extends ContentObserver {
+
+        /**
+         * How long we want for a drop in uid state from top to settle before applying it.
+         *
+         * @see Settings.Global#APP_OPS_CONSTANTS
+         * @see AppOpsManager#KEY_TOP_STATE_SETTLE_TIME
+         */
+        public long TOP_STATE_SETTLE_TIME;
+
+        /**
+         * How long we want for a drop in uid state from foreground to settle before applying it.
+         *
+         * @see Settings.Global#APP_OPS_CONSTANTS
+         * @see AppOpsManager#KEY_FG_SERVICE_STATE_SETTLE_TIME
+         */
+        public long FG_SERVICE_STATE_SETTLE_TIME;
+
+        /**
+         * How long we want for a drop in uid state from background to settle before applying it.
+         *
+         * @see Settings.Global#APP_OPS_CONSTANTS
+         * @see AppOpsManager#KEY_BG_STATE_SETTLE_TIME
+         */
+        public long BG_STATE_SETTLE_TIME;
+
+        private final KeyValueListParser mParser = new KeyValueListParser(',');
+        private ContentResolver mResolver;
+
+        Constants(Handler handler) {
+            super(handler);
+            updateConstants();
+        }
+
+        public void startMonitoring(ContentResolver resolver) {
+            mResolver = resolver;
+            mResolver.registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.APP_OPS_CONSTANTS),
+                    false, this);
+            updateConstants();
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            updateConstants();
+        }
+
+        private void updateConstants() {
+            String value = mResolver != null ? Settings.Global.getString(mResolver,
+                    Settings.Global.APP_OPS_CONSTANTS) : "";
+
+            synchronized (AppOpsServiceImpl.this) {
+                try {
+                    mParser.setString(value);
+                } catch (IllegalArgumentException e) {
+                    // Failed to parse the settings string, log this and move on
+                    // with defaults.
+                    Slog.e(TAG, "Bad app ops settings", e);
+                }
+                TOP_STATE_SETTLE_TIME = mParser.getDurationMillis(
+                        KEY_TOP_STATE_SETTLE_TIME, 5 * 1000L);
+                FG_SERVICE_STATE_SETTLE_TIME = mParser.getDurationMillis(
+                        KEY_FG_SERVICE_STATE_SETTLE_TIME, 5 * 1000L);
+                BG_STATE_SETTLE_TIME = mParser.getDurationMillis(
+                        KEY_BG_STATE_SETTLE_TIME, 1 * 1000L);
+            }
+        }
+
+        void dump(PrintWriter pw) {
+            pw.println("  Settings:");
+
+            pw.print("    ");
+            pw.print(KEY_TOP_STATE_SETTLE_TIME);
+            pw.print("=");
+            TimeUtils.formatDuration(TOP_STATE_SETTLE_TIME, pw);
+            pw.println();
+            pw.print("    ");
+            pw.print(KEY_FG_SERVICE_STATE_SETTLE_TIME);
+            pw.print("=");
+            TimeUtils.formatDuration(FG_SERVICE_STATE_SETTLE_TIME, pw);
+            pw.println();
+            pw.print("    ");
+            pw.print(KEY_BG_STATE_SETTLE_TIME);
+            pw.print("=");
+            TimeUtils.formatDuration(BG_STATE_SETTLE_TIME, pw);
+            pw.println();
+        }
+    }
+
+    @VisibleForTesting
+    final Constants mConstants;
+
+    @VisibleForTesting
+    final class UidState {
+        public final int uid;
+
+        public ArrayMap<String, Ops> pkgOps;
+
+        // true indicates there is an interested observer, false there isn't but it has such an op
+        //TODO: Move foregroundOps and hasForegroundWatchers into the AppOpsServiceInterface.
+        public SparseBooleanArray foregroundOps;
+        public boolean hasForegroundWatchers;
+
+        public UidState(int uid) {
+            this.uid = uid;
+        }
+
+        public void clear() {
+            mAppOpsServiceInterface.removeUid(uid);
+            if (pkgOps != null) {
+                for (String packageName : pkgOps.keySet()) {
+                    mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
+                }
+            }
+            pkgOps = null;
+        }
+
+        public boolean isDefault() {
+            boolean areAllPackageModesDefault = true;
+            if (pkgOps != null) {
+                for (String packageName : pkgOps.keySet()) {
+                    if (!mAppOpsServiceInterface.arePackageModesDefault(packageName,
+                            UserHandle.getUserId(uid))) {
+                        areAllPackageModesDefault = false;
+                        break;
+                    }
+                }
+            }
+            return (pkgOps == null || pkgOps.isEmpty())
+                    && mAppOpsServiceInterface.areUidModesDefault(uid)
+                    && areAllPackageModesDefault;
+        }
+
+        // Functions for uid mode access and manipulation.
+        public SparseIntArray getNonDefaultUidModes() {
+            return mAppOpsServiceInterface.getNonDefaultUidModes(uid);
+        }
+
+        public int getUidMode(int op) {
+            return mAppOpsServiceInterface.getUidMode(uid, op);
+        }
+
+        public boolean setUidMode(int op, int mode) {
+            return mAppOpsServiceInterface.setUidMode(uid, op, mode);
+        }
+
+        @SuppressWarnings("GuardedBy")
+        int evalMode(int op, int mode) {
+            return getUidStateTracker().evalMode(uid, op, mode);
+        }
+
+        public void evalForegroundOps() {
+            foregroundOps = null;
+            foregroundOps = mAppOpsServiceInterface.evalForegroundUidOps(uid, foregroundOps);
+            if (pkgOps != null) {
+                for (int i = pkgOps.size() - 1; i >= 0; i--) {
+                    foregroundOps = mAppOpsServiceInterface
+                            .evalForegroundPackageOps(pkgOps.valueAt(i).packageName,
+                                    foregroundOps,
+                                    UserHandle.getUserId(uid));
+                }
+            }
+            hasForegroundWatchers = false;
+            if (foregroundOps != null) {
+                for (int i = 0; i < foregroundOps.size(); i++) {
+                    if (foregroundOps.valueAt(i)) {
+                        hasForegroundWatchers = true;
+                        break;
+                    }
+                }
+            }
+        }
+
+        @SuppressWarnings("GuardedBy")
+        public int getState() {
+            return getUidStateTracker().getUidState(uid);
+        }
+
+        @SuppressWarnings("GuardedBy")
+        public void dump(PrintWriter pw, long nowElapsed) {
+            getUidStateTracker().dumpUidState(pw, uid, nowElapsed);
+        }
+    }
+
+    static final class Ops extends SparseArray<Op> {
+        final String packageName;
+        final UidState uidState;
+
+        /**
+         * The restriction properties of the package. If {@code null} it could not have been read
+         * yet and has to be refreshed.
+         */
+        @Nullable RestrictionBypass bypass;
+
+        /** Lazily populated cache of attributionTags of this package */
+        final @NonNull ArraySet<String> knownAttributionTags = new ArraySet<>();
+
+        /**
+         * Lazily populated cache of <b>valid</b> attributionTags of this package, a set smaller
+         * than or equal to {@link #knownAttributionTags}.
+         */
+        final @NonNull ArraySet<String> validAttributionTags = new ArraySet<>();
+
+        Ops(String _packageName, UidState _uidState) {
+            packageName = _packageName;
+            uidState = _uidState;
+        }
+    }
+
+    /** Returned from {@link #verifyAndGetBypass(int, String, String, String)}. */
+    private static final class PackageVerificationResult {
+
+        final RestrictionBypass bypass;
+        final boolean isAttributionTagValid;
+
+        PackageVerificationResult(RestrictionBypass bypass, boolean isAttributionTagValid) {
+            this.bypass = bypass;
+            this.isAttributionTagValid = isAttributionTagValid;
+        }
+    }
+
+    final class Op {
+        int op;
+        int uid;
+        final UidState uidState;
+        final @NonNull String packageName;
+
+        /** attributionTag -> AttributedOp */
+        final ArrayMap<String, AttributedOp> mAttributions = new ArrayMap<>(1);
+
+        Op(UidState uidState, String packageName, int op, int uid) {
+            this.op = op;
+            this.uid = uid;
+            this.uidState = uidState;
+            this.packageName = packageName;
+        }
+
+        @Mode int getMode() {
+            return mAppOpsServiceInterface.getPackageMode(packageName, this.op,
+                    UserHandle.getUserId(this.uid));
+        }
+
+        void setMode(@Mode int mode) {
+            mAppOpsServiceInterface.setPackageMode(packageName, this.op, mode,
+                    UserHandle.getUserId(this.uid));
+        }
+
+        void removeAttributionsWithNoTime() {
+            for (int i = mAttributions.size() - 1; i >= 0; i--) {
+                if (!mAttributions.valueAt(i).hasAnyTime()) {
+                    mAttributions.removeAt(i);
+                }
+            }
+        }
+
+        private @NonNull AttributedOp getOrCreateAttribution(@NonNull Op parent,
+                @Nullable String attributionTag) {
+            AttributedOp attributedOp;
+
+            attributedOp = mAttributions.get(attributionTag);
+            if (attributedOp == null) {
+                attributedOp = new AttributedOp(AppOpsServiceImpl.this, attributionTag,
+                        parent);
+                mAttributions.put(attributionTag, attributedOp);
+            }
+
+            return attributedOp;
+        }
+
+        @NonNull
+        OpEntry createEntryLocked() {
+            final int numAttributions = mAttributions.size();
+
+            final ArrayMap<String, AppOpsManager.AttributedOpEntry> attributionEntries =
+                    new ArrayMap<>(numAttributions);
+            for (int i = 0; i < numAttributions; i++) {
+                attributionEntries.put(mAttributions.keyAt(i),
+                        mAttributions.valueAt(i).createAttributedOpEntryLocked());
+            }
+
+            return new OpEntry(op, getMode(), attributionEntries);
+        }
+
+        @NonNull
+        OpEntry createSingleAttributionEntryLocked(@Nullable String attributionTag) {
+            final int numAttributions = mAttributions.size();
+
+            final ArrayMap<String, AttributedOpEntry> attributionEntries = new ArrayMap<>(1);
+            for (int i = 0; i < numAttributions; i++) {
+                if (Objects.equals(mAttributions.keyAt(i), attributionTag)) {
+                    attributionEntries.put(mAttributions.keyAt(i),
+                            mAttributions.valueAt(i).createAttributedOpEntryLocked());
+                    break;
+                }
+            }
+
+            return new OpEntry(op, getMode(), attributionEntries);
+        }
+
+        boolean isRunning() {
+            final int numAttributions = mAttributions.size();
+            for (int i = 0; i < numAttributions; i++) {
+                if (mAttributions.valueAt(i).isRunning()) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+    }
+
+    final ArrayMap<IBinder, ModeCallback> mModeWatchers = new ArrayMap<>();
+    final ArrayMap<IBinder, SparseArray<ActiveCallback>> mActiveWatchers = new ArrayMap<>();
+    final ArrayMap<IBinder, SparseArray<StartedCallback>> mStartedWatchers = new ArrayMap<>();
+    final ArrayMap<IBinder, SparseArray<NotedCallback>> mNotedWatchers = new ArrayMap<>();
+
+    final class ModeCallback extends OnOpModeChangedListener implements DeathRecipient  {
+        /** If mWatchedOpCode==ALL_OPS notify for ops affected by the switch-op */
+        public static final int ALL_OPS = -2;
+
+        // Need to keep this only because stopWatchingMode needs an IAppOpsCallback.
+        // Otherwise we can just use the IBinder object.
+        private final IAppOpsCallback mCallback;
+
+        ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOpCode,
+                int callingUid, int callingPid) {
+            super(watchingUid, flags, watchedOpCode, callingUid, callingPid);
+            this.mCallback = callback;
+            try {
+                mCallback.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                /*ignored*/
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("ModeCallback{");
+            sb.append(Integer.toHexString(System.identityHashCode(this)));
+            sb.append(" watchinguid=");
+            UserHandle.formatUid(sb, getWatchingUid());
+            sb.append(" flags=0x");
+            sb.append(Integer.toHexString(getFlags()));
+            switch (getWatchedOpCode()) {
+                case OP_NONE:
+                    break;
+                case ALL_OPS:
+                    sb.append(" op=(all)");
+                    break;
+                default:
+                    sb.append(" op=");
+                    sb.append(opToName(getWatchedOpCode()));
+                    break;
+            }
+            sb.append(" from uid=");
+            UserHandle.formatUid(sb, getCallingUid());
+            sb.append(" pid=");
+            sb.append(getCallingPid());
+            sb.append('}');
+            return sb.toString();
+        }
+
+        void unlinkToDeath() {
+            mCallback.asBinder().unlinkToDeath(this, 0);
+        }
+
+        @Override
+        public void binderDied() {
+            stopWatchingMode(mCallback);
+        }
+
+        @Override
+        public void onOpModeChanged(int op, int uid, String packageName) throws RemoteException {
+            mCallback.opChanged(op, uid, packageName);
+        }
+    }
+
+    final class ActiveCallback implements DeathRecipient {
+        final IAppOpsActiveCallback mCallback;
+        final int mWatchingUid;
+        final int mCallingUid;
+        final int mCallingPid;
+
+        ActiveCallback(IAppOpsActiveCallback callback, int watchingUid, int callingUid,
+                int callingPid) {
+            mCallback = callback;
+            mWatchingUid = watchingUid;
+            mCallingUid = callingUid;
+            mCallingPid = callingPid;
+            try {
+                mCallback.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                /*ignored*/
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("ActiveCallback{");
+            sb.append(Integer.toHexString(System.identityHashCode(this)));
+            sb.append(" watchinguid=");
+            UserHandle.formatUid(sb, mWatchingUid);
+            sb.append(" from uid=");
+            UserHandle.formatUid(sb, mCallingUid);
+            sb.append(" pid=");
+            sb.append(mCallingPid);
+            sb.append('}');
+            return sb.toString();
+        }
+
+        void destroy() {
+            mCallback.asBinder().unlinkToDeath(this, 0);
+        }
+
+        @Override
+        public void binderDied() {
+            stopWatchingActive(mCallback);
+        }
+    }
+
+    final class StartedCallback implements DeathRecipient {
+        final IAppOpsStartedCallback mCallback;
+        final int mWatchingUid;
+        final int mCallingUid;
+        final int mCallingPid;
+
+        StartedCallback(IAppOpsStartedCallback callback, int watchingUid, int callingUid,
+                int callingPid) {
+            mCallback = callback;
+            mWatchingUid = watchingUid;
+            mCallingUid = callingUid;
+            mCallingPid = callingPid;
+            try {
+                mCallback.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                /*ignored*/
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("StartedCallback{");
+            sb.append(Integer.toHexString(System.identityHashCode(this)));
+            sb.append(" watchinguid=");
+            UserHandle.formatUid(sb, mWatchingUid);
+            sb.append(" from uid=");
+            UserHandle.formatUid(sb, mCallingUid);
+            sb.append(" pid=");
+            sb.append(mCallingPid);
+            sb.append('}');
+            return sb.toString();
+        }
+
+        void destroy() {
+            mCallback.asBinder().unlinkToDeath(this, 0);
+        }
+
+        @Override
+        public void binderDied() {
+            stopWatchingStarted(mCallback);
+        }
+    }
+
+    final class NotedCallback implements DeathRecipient {
+        final IAppOpsNotedCallback mCallback;
+        final int mWatchingUid;
+        final int mCallingUid;
+        final int mCallingPid;
+
+        NotedCallback(IAppOpsNotedCallback callback, int watchingUid, int callingUid,
+                int callingPid) {
+            mCallback = callback;
+            mWatchingUid = watchingUid;
+            mCallingUid = callingUid;
+            mCallingPid = callingPid;
+            try {
+                mCallback.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                /*ignored*/
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("NotedCallback{");
+            sb.append(Integer.toHexString(System.identityHashCode(this)));
+            sb.append(" watchinguid=");
+            UserHandle.formatUid(sb, mWatchingUid);
+            sb.append(" from uid=");
+            UserHandle.formatUid(sb, mCallingUid);
+            sb.append(" pid=");
+            sb.append(mCallingPid);
+            sb.append('}');
+            return sb.toString();
+        }
+
+        void destroy() {
+            mCallback.asBinder().unlinkToDeath(this, 0);
+        }
+
+        @Override
+        public void binderDied() {
+            stopWatchingNoted(mCallback);
+        }
+    }
+
+    /**
+     * Call {@link AttributedOp#onClientDeath attributedOp.onClientDeath(clientId)}.
+     */
+    static void onClientDeath(@NonNull AttributedOp attributedOp,
+            @NonNull IBinder clientId) {
+        attributedOp.onClientDeath(clientId);
+    }
+
+    AppOpsServiceImpl(File storagePath, Handler handler, Context context) {
+        mContext = context;
+
+        for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) {
+            int switchCode = AppOpsManager.opToSwitch(switchedCode);
+            mSwitchedOps.put(switchCode,
+                    ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode));
+        }
+        mAppOpsServiceInterface =
+                new AppOpsCheckingServiceImpl(this, this, handler, context, mSwitchedOps);
+        mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler,
+                mAppOpsServiceInterface);
+
+        LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
+        mFile = new AtomicFile(storagePath, "appops");
+
+        mHandler = handler;
+        mConstants = new Constants(mHandler);
+        readState();
+    }
+
+    /**
+     * Handler for work when packages are removed or updated
+     */
+    private BroadcastReceiver mOnPackageUpdatedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            String pkgName = intent.getData().getEncodedSchemeSpecificPart();
+            int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
+
+            if (action.equals(ACTION_PACKAGE_REMOVED) && !intent.hasExtra(EXTRA_REPLACING)) {
+                synchronized (AppOpsServiceImpl.this) {
+                    UidState uidState = mUidStates.get(uid);
+                    if (uidState == null || uidState.pkgOps == null) {
+                        return;
+                    }
+                    mAppOpsServiceInterface.removePackage(pkgName, UserHandle.getUserId(uid));
+                    Ops removedOps = uidState.pkgOps.remove(pkgName);
+                    if (removedOps != null) {
+                        scheduleFastWriteLocked();
+                    }
+                }
+            } else if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) {
+                AndroidPackage pkg = getPackageManagerInternal().getPackage(pkgName);
+                if (pkg == null) {
+                    return;
+                }
+
+                ArrayMap<String, String> dstAttributionTags = new ArrayMap<>();
+                ArraySet<String> attributionTags = new ArraySet<>();
+                attributionTags.add(null);
+                if (pkg.getAttributions() != null) {
+                    int numAttributions = pkg.getAttributions().size();
+                    for (int attributionNum = 0; attributionNum < numAttributions;
+                            attributionNum++) {
+                        ParsedAttribution attribution = pkg.getAttributions().get(attributionNum);
+                        attributionTags.add(attribution.getTag());
+
+                        int numInheritFrom = attribution.getInheritFrom().size();
+                        for (int inheritFromNum = 0; inheritFromNum < numInheritFrom;
+                                inheritFromNum++) {
+                            dstAttributionTags.put(attribution.getInheritFrom().get(inheritFromNum),
+                                    attribution.getTag());
+                        }
+                    }
+                }
+
+                synchronized (AppOpsServiceImpl.this) {
+                    UidState uidState = mUidStates.get(uid);
+                    if (uidState == null || uidState.pkgOps == null) {
+                        return;
+                    }
+
+                    Ops ops = uidState.pkgOps.get(pkgName);
+                    if (ops == null) {
+                        return;
+                    }
+
+                    // Reset cached package properties to re-initialize when needed
+                    ops.bypass = null;
+                    ops.knownAttributionTags.clear();
+
+                    // Merge data collected for removed attributions into their successor
+                    // attributions
+                    int numOps = ops.size();
+                    for (int opNum = 0; opNum < numOps; opNum++) {
+                        Op op = ops.valueAt(opNum);
+
+                        int numAttributions = op.mAttributions.size();
+                        for (int attributionNum = numAttributions - 1; attributionNum >= 0;
+                                attributionNum--) {
+                            String attributionTag = op.mAttributions.keyAt(attributionNum);
+
+                            if (attributionTags.contains(attributionTag)) {
+                                // attribution still exist after upgrade
+                                continue;
+                            }
+
+                            String newAttributionTag = dstAttributionTags.get(attributionTag);
+
+                            AttributedOp newAttributedOp = op.getOrCreateAttribution(op,
+                                    newAttributionTag);
+                            newAttributedOp.add(op.mAttributions.valueAt(attributionNum));
+                            op.mAttributions.removeAt(attributionNum);
+
+                            scheduleFastWriteLocked();
+                        }
+                    }
+                }
+            }
+        }
+    };
+
+    @Override
+    public void systemReady() {
+        mConstants.startMonitoring(mContext.getContentResolver());
+        mHistoricalRegistry.systemReady(mContext.getContentResolver());
+
+        IntentFilter packageUpdateFilter = new IntentFilter();
+        packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+        packageUpdateFilter.addDataScheme("package");
+
+        mContext.registerReceiverAsUser(mOnPackageUpdatedReceiver, UserHandle.ALL,
+                packageUpdateFilter, null, null);
+
+        synchronized (this) {
+            for (int uidNum = mUidStates.size() - 1; uidNum >= 0; uidNum--) {
+                int uid = mUidStates.keyAt(uidNum);
+                UidState uidState = mUidStates.valueAt(uidNum);
+
+                String[] pkgsInUid = getPackagesForUid(uidState.uid);
+                if (ArrayUtils.isEmpty(pkgsInUid)) {
+                    uidState.clear();
+                    mUidStates.removeAt(uidNum);
+                    scheduleFastWriteLocked();
+                    continue;
+                }
+
+                ArrayMap<String, Ops> pkgs = uidState.pkgOps;
+                if (pkgs == null) {
+                    continue;
+                }
+
+                int numPkgs = pkgs.size();
+                for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
+                    String pkg = pkgs.keyAt(pkgNum);
+
+                    String action;
+                    if (!ArrayUtils.contains(pkgsInUid, pkg)) {
+                        action = Intent.ACTION_PACKAGE_REMOVED;
+                    } else {
+                        action = Intent.ACTION_PACKAGE_REPLACED;
+                    }
+
+                    SystemServerInitThreadPool.submit(
+                            () -> mOnPackageUpdatedReceiver.onReceive(mContext, new Intent(action)
+                                    .setData(Uri.fromParts("package", pkg, null))
+                                    .putExtra(Intent.EXTRA_UID, uid)),
+                            "Update app-ops uidState in case package " + pkg + " changed");
+                }
+            }
+        }
+
+        final IntentFilter packageSuspendFilter = new IntentFilter();
+        packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
+        packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
+        mContext.registerReceiverAsUser(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final int[] changedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
+                final String[] changedPkgs = intent.getStringArrayExtra(
+                        Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                for (int code : OPS_RESTRICTED_ON_SUSPEND) {
+                    ArraySet<OnOpModeChangedListener> onModeChangedListeners;
+                    synchronized (AppOpsServiceImpl.this) {
+                        onModeChangedListeners =
+                                mAppOpsServiceInterface.getOpModeChangedListeners(code);
+                        if (onModeChangedListeners == null) {
+                            continue;
+                        }
+                    }
+                    for (int i = 0; i < changedUids.length; i++) {
+                        final int changedUid = changedUids[i];
+                        final String changedPkg = changedPkgs[i];
+                        // We trust packagemanager to insert matching uid and packageNames in the
+                        // extras
+                        notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg);
+                    }
+                }
+            }
+        }, UserHandle.ALL, packageSuspendFilter, null, null);
+    }
+
+    @Override
+    public void packageRemoved(int uid, String packageName) {
+        synchronized (this) {
+            UidState uidState = mUidStates.get(uid);
+            if (uidState == null) {
+                return;
+            }
+
+            Ops removedOps = null;
+
+            // Remove any package state if such.
+            if (uidState.pkgOps != null) {
+                removedOps = uidState.pkgOps.remove(packageName);
+                mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
+            }
+
+            // If we just nuked the last package state check if the UID is valid.
+            if (removedOps != null && uidState.pkgOps.isEmpty()
+                    && getPackagesForUid(uid).length <= 0) {
+                uidState.clear();
+                mUidStates.remove(uid);
+            }
+
+            if (removedOps != null) {
+                scheduleFastWriteLocked();
+
+                final int numOps = removedOps.size();
+                for (int opNum = 0; opNum < numOps; opNum++) {
+                    final Op op = removedOps.valueAt(opNum);
+
+                    final int numAttributions = op.mAttributions.size();
+                    for (int attributionNum = 0; attributionNum < numAttributions;
+                            attributionNum++) {
+                        AttributedOp attributedOp = op.mAttributions.valueAt(attributionNum);
+
+                        while (attributedOp.isRunning()) {
+                            attributedOp.finished(attributedOp.mInProgressEvents.keyAt(0));
+                        }
+                        while (attributedOp.isPaused()) {
+                            attributedOp.finished(attributedOp.mPausedInProgressEvents.keyAt(0));
+                        }
+                    }
+                }
+            }
+        }
+
+        mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::clearHistory,
+                mHistoricalRegistry, uid, packageName));
+    }
+
+    @Override
+    public void uidRemoved(int uid) {
+        synchronized (this) {
+            if (mUidStates.indexOfKey(uid) >= 0) {
+                mUidStates.get(uid).clear();
+                mUidStates.remove(uid);
+                scheduleFastWriteLocked();
+            }
+        }
+    }
+
+    // The callback method from ForegroundPolicyInterface
+    private void onUidStateChanged(int uid, int state, boolean foregroundModeMayChange) {
+        synchronized (this) {
+            UidState uidState = getUidStateLocked(uid, true);
+
+            if (uidState != null && foregroundModeMayChange && uidState.hasForegroundWatchers) {
+                for (int fgi = uidState.foregroundOps.size() - 1; fgi >= 0; fgi--) {
+                    if (!uidState.foregroundOps.valueAt(fgi)) {
+                        continue;
+                    }
+                    final int code = uidState.foregroundOps.keyAt(fgi);
+
+                    if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)
+                            && uidState.getUidMode(code) == AppOpsManager.MODE_FOREGROUND) {
+                        mHandler.sendMessage(PooledLambda.obtainMessage(
+                                AppOpsServiceImpl::notifyOpChangedForAllPkgsInUid,
+                                this, code, uidState.uid, true, null));
+                    } else if (uidState.pkgOps != null) {
+                        final ArraySet<OnOpModeChangedListener> listenerSet =
+                                mAppOpsServiceInterface.getOpModeChangedListeners(code);
+                        if (listenerSet != null) {
+                            for (int cbi = listenerSet.size() - 1; cbi >= 0; cbi--) {
+                                final OnOpModeChangedListener listener = listenerSet.valueAt(cbi);
+                                if ((listener.getFlags()
+                                        & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0
+                                        || !listener.isWatchingUid(uidState.uid)) {
+                                    continue;
+                                }
+                                for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) {
+                                    final Op op = uidState.pkgOps.valueAt(pkgi).get(code);
+                                    if (op == null) {
+                                        continue;
+                                    }
+                                    if (op.getMode() == AppOpsManager.MODE_FOREGROUND) {
+                                        mHandler.sendMessage(PooledLambda.obtainMessage(
+                                                AppOpsServiceImpl::notifyOpChanged,
+                                                this, listenerSet.valueAt(cbi), code, uidState.uid,
+                                                uidState.pkgOps.keyAt(pkgi)));
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (uidState != null && uidState.pkgOps != null) {
+                int numPkgs = uidState.pkgOps.size();
+                for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
+                    Ops ops = uidState.pkgOps.valueAt(pkgNum);
+
+                    int numOps = ops.size();
+                    for (int opNum = 0; opNum < numOps; opNum++) {
+                        Op op = ops.valueAt(opNum);
+
+                        int numAttributions = op.mAttributions.size();
+                        for (int attributionNum = 0; attributionNum < numAttributions;
+                                attributionNum++) {
+                            AttributedOp attributedOp = op.mAttributions.valueAt(
+                                    attributionNum);
+
+                            attributedOp.onUidStateChanged(state);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Notify the proc state or capability has changed for a certain UID.
+     */
+    @Override
+    public void updateUidProcState(int uid, int procState,
+            @ActivityManager.ProcessCapability int capability) {
+        synchronized (this) {
+            getUidStateTracker().updateUidProcState(uid, procState, capability);
+            if (!mUidStates.contains(uid)) {
+                UidState uidState = new UidState(uid);
+                mUidStates.put(uid, uidState);
+                onUidStateChanged(uid,
+                        AppOpsUidStateTracker.processStateToUidState(procState), false);
+            }
+        }
+    }
+
+    @Override
+    public void shutdown() {
+        Slog.w(TAG, "Writing app ops before shutdown...");
+        boolean doWrite = false;
+        synchronized (this) {
+            if (mWriteScheduled) {
+                mWriteScheduled = false;
+                mFastWriteScheduled = false;
+                mHandler.removeCallbacks(mWriteRunner);
+                doWrite = true;
+            }
+        }
+        if (doWrite) {
+            writeState();
+        }
+
+        mHistoricalRegistry.shutdown();
+    }
+
+    private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops) {
+        ArrayList<AppOpsManager.OpEntry> resOps = null;
+        if (ops == null) {
+            resOps = new ArrayList<>();
+            for (int j = 0; j < pkgOps.size(); j++) {
+                Op curOp = pkgOps.valueAt(j);
+                resOps.add(getOpEntryForResult(curOp));
+            }
+        } else {
+            for (int j = 0; j < ops.length; j++) {
+                Op curOp = pkgOps.get(ops[j]);
+                if (curOp != null) {
+                    if (resOps == null) {
+                        resOps = new ArrayList<>();
+                    }
+                    resOps.add(getOpEntryForResult(curOp));
+                }
+            }
+        }
+        return resOps;
+    }
+
+    @Nullable
+    private ArrayList<AppOpsManager.OpEntry> collectUidOps(@NonNull UidState uidState,
+            @Nullable int[] ops) {
+        final SparseIntArray opModes = uidState.getNonDefaultUidModes();
+        if (opModes == null) {
+            return null;
+        }
+
+        int opModeCount = opModes.size();
+        if (opModeCount == 0) {
+            return null;
+        }
+        ArrayList<AppOpsManager.OpEntry> resOps = null;
+        if (ops == null) {
+            resOps = new ArrayList<>();
+            for (int i = 0; i < opModeCount; i++) {
+                int code = opModes.keyAt(i);
+                resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
+            }
+        } else {
+            for (int j = 0; j < ops.length; j++) {
+                int code = ops[j];
+                if (opModes.indexOfKey(code) >= 0) {
+                    if (resOps == null) {
+                        resOps = new ArrayList<>();
+                    }
+                    resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
+                }
+            }
+        }
+        return resOps;
+    }
+
+    private static @NonNull OpEntry getOpEntryForResult(@NonNull Op op) {
+        return op.createEntryLocked();
+    }
+
+    @Override
+    public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
+        final int callingUid = Binder.getCallingUid();
+        final boolean hasAllPackageAccess = mContext.checkPermission(
+                Manifest.permission.GET_APP_OPS_STATS, Binder.getCallingPid(),
+                Binder.getCallingUid(), null) == PackageManager.PERMISSION_GRANTED;
+        ArrayList<AppOpsManager.PackageOps> res = null;
+        synchronized (this) {
+            final int uidStateCount = mUidStates.size();
+            for (int i = 0; i < uidStateCount; i++) {
+                UidState uidState = mUidStates.valueAt(i);
+                if (uidState.pkgOps == null || uidState.pkgOps.isEmpty()) {
+                    continue;
+                }
+                ArrayMap<String, Ops> packages = uidState.pkgOps;
+                final int packageCount = packages.size();
+                for (int j = 0; j < packageCount; j++) {
+                    Ops pkgOps = packages.valueAt(j);
+                    ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
+                    if (resOps != null) {
+                        if (res == null) {
+                            res = new ArrayList<>();
+                        }
+                        AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
+                                pkgOps.packageName, pkgOps.uidState.uid, resOps);
+                        // Caller can always see their packages and with a permission all.
+                        if (hasAllPackageAccess || callingUid == pkgOps.uidState.uid) {
+                            res.add(resPackage);
+                        }
+                    }
+                }
+            }
+        }
+        return res;
+    }
+
+    @Override
+    public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName,
+            int[] ops) {
+        enforceGetAppOpsStatsPermissionIfNeeded(uid, packageName);
+        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+        if (resolvedPackageName == null) {
+            return Collections.emptyList();
+        }
+        synchronized (this) {
+            Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null,
+                    /* edit */ false);
+            if (pkgOps == null) {
+                return null;
+            }
+            ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
+            if (resOps == null) {
+                return null;
+            }
+            ArrayList<AppOpsManager.PackageOps> res = new ArrayList<>();
+            AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
+                    pkgOps.packageName, pkgOps.uidState.uid, resOps);
+            res.add(resPackage);
+            return res;
+        }
+    }
+
+    private void enforceGetAppOpsStatsPermissionIfNeeded(int uid, String packageName) {
+        final int callingUid = Binder.getCallingUid();
+        // We get to access everything
+        if (callingUid == Process.myPid()) {
+            return;
+        }
+        // Apps can access their own data
+        if (uid == callingUid && packageName != null
+                && checkPackage(uid, packageName) == MODE_ALLOWED) {
+            return;
+        }
+        // Otherwise, you need a permission...
+        mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+                Binder.getCallingPid(), callingUid, null);
+    }
+
+    /**
+     * Verify that historical appop request arguments are valid.
+     */
+    private void ensureHistoricalOpRequestIsValid(int uid, String packageName,
+            String attributionTag, List<String> opNames, int filter, long beginTimeMillis,
+            long endTimeMillis, int flags) {
+        if ((filter & FILTER_BY_UID) != 0) {
+            Preconditions.checkArgument(uid != Process.INVALID_UID);
+        } else {
+            Preconditions.checkArgument(uid == Process.INVALID_UID);
+        }
+
+        if ((filter & FILTER_BY_PACKAGE_NAME) != 0) {
+            Objects.requireNonNull(packageName);
+        } else {
+            Preconditions.checkArgument(packageName == null);
+        }
+
+        if ((filter & FILTER_BY_ATTRIBUTION_TAG) == 0) {
+            Preconditions.checkArgument(attributionTag == null);
+        }
+
+        if ((filter & FILTER_BY_OP_NAMES) != 0) {
+            Objects.requireNonNull(opNames);
+        } else {
+            Preconditions.checkArgument(opNames == null);
+        }
+
+        Preconditions.checkFlagsArgument(filter,
+                FILTER_BY_UID | FILTER_BY_PACKAGE_NAME | FILTER_BY_ATTRIBUTION_TAG
+                        | FILTER_BY_OP_NAMES);
+        Preconditions.checkArgumentNonnegative(beginTimeMillis);
+        Preconditions.checkArgument(endTimeMillis > beginTimeMillis);
+        Preconditions.checkFlagsArgument(flags, OP_FLAGS_ALL);
+    }
+
+    @Override
+    public void getHistoricalOps(int uid, String packageName, String attributionTag,
+            List<String> opNames, int dataType, int filter, long beginTimeMillis,
+            long endTimeMillis, int flags, RemoteCallback callback) {
+        PackageManager pm = mContext.getPackageManager();
+
+        ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
+                beginTimeMillis, endTimeMillis, flags);
+        Objects.requireNonNull(callback, "callback cannot be null");
+        ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
+        boolean isSelfRequest = (filter & FILTER_BY_UID) != 0 && uid == Binder.getCallingUid();
+        if (!isSelfRequest) {
+            boolean isCallerInstrumented =
+                    ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
+            boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
+            boolean isCallerPermissionController;
+            try {
+                isCallerPermissionController = pm.getPackageUidAsUser(
+                        mContext.getPackageManager().getPermissionControllerPackageName(), 0,
+                        UserHandle.getUserId(Binder.getCallingUid()))
+                        == Binder.getCallingUid();
+            } catch (PackageManager.NameNotFoundException doesNotHappen) {
+                return;
+            }
+
+            boolean doesCallerHavePermission = mContext.checkPermission(
+                    android.Manifest.permission.GET_HISTORICAL_APP_OPS_STATS,
+                    Binder.getCallingPid(), Binder.getCallingUid())
+                    == PackageManager.PERMISSION_GRANTED;
+
+            if (!isCallerSystem && !isCallerInstrumented && !isCallerPermissionController
+                    && !doesCallerHavePermission) {
+                mHandler.post(() -> callback.sendResult(new Bundle()));
+                return;
+            }
+
+            mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+                    Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
+        }
+
+        final String[] opNamesArray = (opNames != null)
+                ? opNames.toArray(new String[opNames.size()]) : null;
+
+        Set<String> attributionChainExemptPackages = null;
+        if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
+            attributionChainExemptPackages =
+                    PermissionManager.getIndicatorExemptedPackages(mContext);
+        }
+
+        final String[] chainExemptPkgArray = attributionChainExemptPackages != null
+                ? attributionChainExemptPackages.toArray(
+                new String[attributionChainExemptPackages.size()]) : null;
+
+        // Must not hold the appops lock
+        mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps,
+                mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
+                filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
+                callback).recycleOnUse());
+    }
+
+    @Override
+    public void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag,
+            List<String> opNames, int dataType, int filter, long beginTimeMillis,
+            long endTimeMillis, int flags, RemoteCallback callback) {
+        ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
+                beginTimeMillis, endTimeMillis, flags);
+        Objects.requireNonNull(callback, "callback cannot be null");
+
+        mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
+                Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
+
+        final String[] opNamesArray = (opNames != null)
+                ? opNames.toArray(new String[opNames.size()]) : null;
+
+        Set<String> attributionChainExemptPackages = null;
+        if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
+            attributionChainExemptPackages =
+                    PermissionManager.getIndicatorExemptedPackages(mContext);
+        }
+
+        final String[] chainExemptPkgArray = attributionChainExemptPackages != null
+                ? attributionChainExemptPackages.toArray(
+                new String[attributionChainExemptPackages.size()]) : null;
+
+        // Must not hold the appops lock
+        mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOpsFromDiskRaw,
+                mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
+                filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
+                callback).recycleOnUse());
+    }
+
+    @Override
+    public void reloadNonHistoricalState() {
+        mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
+                Binder.getCallingPid(), Binder.getCallingUid(), "reloadNonHistoricalState");
+        writeState();
+        readState();
+    }
+
+    @Override
+    public List<AppOpsManager.PackageOps> getUidOps(int uid, int[] ops) {
+        mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+                Binder.getCallingPid(), Binder.getCallingUid(), null);
+        synchronized (this) {
+            UidState uidState = getUidStateLocked(uid, false);
+            if (uidState == null) {
+                return null;
+            }
+            ArrayList<AppOpsManager.OpEntry> resOps = collectUidOps(uidState, ops);
+            if (resOps == null) {
+                return null;
+            }
+            ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
+            AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
+                    null, uidState.uid, resOps);
+            res.add(resPackage);
+            return res;
+        }
+    }
+
+    private void pruneOpLocked(Op op, int uid, String packageName) {
+        op.removeAttributionsWithNoTime();
+
+        if (op.mAttributions.isEmpty()) {
+            Ops ops = getOpsLocked(uid, packageName, null, false, null, /* edit */ false);
+            if (ops != null) {
+                ops.remove(op.op);
+                op.setMode(AppOpsManager.opToDefaultMode(op.op));
+                if (ops.size() <= 0) {
+                    UidState uidState = ops.uidState;
+                    ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
+                    if (pkgOps != null) {
+                        pkgOps.remove(ops.packageName);
+                        mAppOpsServiceInterface.removePackage(ops.packageName,
+                                UserHandle.getUserId(uidState.uid));
+                        if (pkgOps.isEmpty()) {
+                            uidState.pkgOps = null;
+                        }
+                        if (uidState.isDefault()) {
+                            uidState.clear();
+                            mUidStates.remove(uid);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void enforceManageAppOpsModes(int callingPid, int callingUid, int targetUid) {
+        if (callingPid == Process.myPid()) {
+            return;
+        }
+        final int callingUser = UserHandle.getUserId(callingUid);
+        synchronized (this) {
+            if (mProfileOwners != null && mProfileOwners.get(callingUser, -1) == callingUid) {
+                if (targetUid >= 0 && callingUser == UserHandle.getUserId(targetUid)) {
+                    // Profile owners are allowed to change modes but only for apps
+                    // within their user.
+                    return;
+                }
+            }
+        }
+        mContext.enforcePermission(android.Manifest.permission.MANAGE_APP_OPS_MODES,
+                Binder.getCallingPid(), Binder.getCallingUid(), null);
+    }
+
+    @Override
+    public void setUidMode(int code, int uid, int mode,
+            @Nullable IAppOpsCallback permissionPolicyCallback) {
+        if (DEBUG) {
+            Slog.i(TAG, "uid " + uid + " OP_" + opToName(code) + " := " + modeToName(mode)
+                    + " by uid " + Binder.getCallingUid());
+        }
+
+        enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
+        verifyIncomingOp(code);
+        code = AppOpsManager.opToSwitch(code);
+
+        if (permissionPolicyCallback == null) {
+            updatePermissionRevokedCompat(uid, code, mode);
+        }
+
+        int previousMode;
+        synchronized (this) {
+            final int defaultMode = AppOpsManager.opToDefaultMode(code);
+
+            UidState uidState = getUidStateLocked(uid, false);
+            if (uidState == null) {
+                if (mode == defaultMode) {
+                    return;
+                }
+                uidState = new UidState(uid);
+                mUidStates.put(uid, uidState);
+            }
+            if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
+                previousMode = uidState.getUidMode(code);
+            } else {
+                // doesn't look right but is legacy behavior.
+                previousMode = MODE_DEFAULT;
+            }
+
+            if (!uidState.setUidMode(code, mode)) {
+                return;
+            }
+            uidState.evalForegroundOps();
+            if (mode != MODE_ERRORED && mode != previousMode) {
+                updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
+            }
+        }
+
+        notifyOpChangedForAllPkgsInUid(code, uid, false, permissionPolicyCallback);
+        notifyOpChangedSync(code, uid, null, mode, previousMode);
+    }
+
+    /**
+     * Notify that an op changed for all packages in an uid.
+     *
+     * @param code           The op that changed
+     * @param uid            The uid the op was changed for
+     * @param onlyForeground Only notify watchers that watch for foreground changes
+     */
+    private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground,
+            @Nullable IAppOpsCallback callbackToIgnore) {
+        ModeCallback listenerToIgnore = callbackToIgnore != null
+                ? mModeWatchers.get(callbackToIgnore.asBinder()) : null;
+        mAppOpsServiceInterface.notifyOpChangedForAllPkgsInUid(code, uid, onlyForeground,
+                listenerToIgnore);
+    }
+
+    private void updatePermissionRevokedCompat(int uid, int switchCode, int mode) {
+        PackageManager packageManager = mContext.getPackageManager();
+        if (packageManager == null) {
+            // This can only happen during early boot. At this time the permission state and appop
+            // state are in sync
+            return;
+        }
+
+        String[] packageNames = packageManager.getPackagesForUid(uid);
+        if (ArrayUtils.isEmpty(packageNames)) {
+            return;
+        }
+        String packageName = packageNames[0];
+
+        int[] ops = mSwitchedOps.get(switchCode);
+        for (int code : ops) {
+            String permissionName = AppOpsManager.opToPermission(code);
+            if (permissionName == null) {
+                continue;
+            }
+
+            if (packageManager.checkPermission(permissionName, packageName)
+                    != PackageManager.PERMISSION_GRANTED) {
+                continue;
+            }
+
+            PermissionInfo permissionInfo;
+            try {
+                permissionInfo = packageManager.getPermissionInfo(permissionName, 0);
+            } catch (PackageManager.NameNotFoundException e) {
+                e.printStackTrace();
+                continue;
+            }
+
+            if (!permissionInfo.isRuntime()) {
+                continue;
+            }
+
+            boolean supportsRuntimePermissions = getPackageManagerInternal()
+                    .getUidTargetSdkVersion(uid) >= Build.VERSION_CODES.M;
+
+            UserHandle user = UserHandle.getUserHandleForUid(uid);
+            boolean isRevokedCompat;
+            if (permissionInfo.backgroundPermission != null) {
+                if (packageManager.checkPermission(permissionInfo.backgroundPermission, packageName)
+                        == PackageManager.PERMISSION_GRANTED) {
+                    boolean isBackgroundRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
+
+                    if (isBackgroundRevokedCompat && supportsRuntimePermissions) {
+                        Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
+                                + " permission state, this is discouraged and you should revoke the"
+                                + " runtime permission instead: uid=" + uid + ", switchCode="
+                                + switchCode + ", mode=" + mode + ", permission="
+                                + permissionInfo.backgroundPermission);
+                    }
+
+                    final long identity = Binder.clearCallingIdentity();
+                    try {
+                        packageManager.updatePermissionFlags(permissionInfo.backgroundPermission,
+                                packageName, PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
+                                isBackgroundRevokedCompat
+                                        ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
+                    } finally {
+                        Binder.restoreCallingIdentity(identity);
+                    }
+                }
+
+                isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED
+                        && mode != AppOpsManager.MODE_FOREGROUND;
+            } else {
+                isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
+            }
+
+            if (isRevokedCompat && supportsRuntimePermissions) {
+                Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
+                        + " permission state, this is discouraged and you should revoke the"
+                        + " runtime permission instead: uid=" + uid + ", switchCode="
+                        + switchCode + ", mode=" + mode + ", permission=" + permissionName);
+            }
+
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                packageManager.updatePermissionFlags(permissionName, packageName,
+                        PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, isRevokedCompat
+                                ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
+    private void notifyOpChangedSync(int code, int uid, @NonNull String packageName, int mode,
+            int previousMode) {
+        final StorageManagerInternal storageManagerInternal =
+                LocalServices.getService(StorageManagerInternal.class);
+        if (storageManagerInternal != null) {
+            storageManagerInternal.onAppOpsChanged(code, uid, packageName, mode, previousMode);
+        }
+    }
+
+    @Override
+    public void setMode(int code, int uid, @NonNull String packageName, int mode,
+            @Nullable IAppOpsCallback permissionPolicyCallback) {
+        enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
+        verifyIncomingOp(code);
+        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+            return;
+        }
+
+        ArraySet<OnOpModeChangedListener> repCbs = null;
+        code = AppOpsManager.opToSwitch(code);
+
+        PackageVerificationResult pvr;
+        try {
+            pvr = verifyAndGetBypass(uid, packageName, null);
+        } catch (SecurityException e) {
+            Slog.e(TAG, "Cannot setMode", e);
+            return;
+        }
+
+        int previousMode = MODE_DEFAULT;
+        synchronized (this) {
+            UidState uidState = getUidStateLocked(uid, false);
+            Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ true);
+            if (op != null) {
+                if (op.getMode() != mode) {
+                    previousMode = op.getMode();
+                    op.setMode(mode);
+
+                    if (uidState != null) {
+                        uidState.evalForegroundOps();
+                    }
+                    ArraySet<OnOpModeChangedListener> cbs =
+                            mAppOpsServiceInterface.getOpModeChangedListeners(code);
+                    if (cbs != null) {
+                        if (repCbs == null) {
+                            repCbs = new ArraySet<>();
+                        }
+                        repCbs.addAll(cbs);
+                    }
+                    cbs = mAppOpsServiceInterface.getPackageModeChangedListeners(packageName);
+                    if (cbs != null) {
+                        if (repCbs == null) {
+                            repCbs = new ArraySet<>();
+                        }
+                        repCbs.addAll(cbs);
+                    }
+                    if (repCbs != null && permissionPolicyCallback != null) {
+                        repCbs.remove(mModeWatchers.get(permissionPolicyCallback.asBinder()));
+                    }
+                    if (mode == AppOpsManager.opToDefaultMode(op.op)) {
+                        // If going into the default mode, prune this op
+                        // if there is nothing else interesting in it.
+                        pruneOpLocked(op, uid, packageName);
+                    }
+                    scheduleFastWriteLocked();
+                    if (mode != MODE_ERRORED) {
+                        updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
+                    }
+                }
+            }
+        }
+        if (repCbs != null) {
+            mHandler.sendMessage(PooledLambda.obtainMessage(
+                    AppOpsServiceImpl::notifyOpChanged,
+                    this, repCbs, code, uid, packageName));
+        }
+
+        notifyOpChangedSync(code, uid, packageName, mode, previousMode);
+    }
+
+    private void notifyOpChanged(ArraySet<OnOpModeChangedListener> callbacks, int code,
+            int uid, String packageName) {
+        for (int i = 0; i < callbacks.size(); i++) {
+            final OnOpModeChangedListener callback = callbacks.valueAt(i);
+            notifyOpChanged(callback, code, uid, packageName);
+        }
+    }
+
+    private void notifyOpChanged(OnOpModeChangedListener callback, int code,
+            int uid, String packageName) {
+        mAppOpsServiceInterface.notifyOpChanged(callback, code, uid, packageName);
+    }
+
+    private static ArrayList<ChangeRec> addChange(ArrayList<ChangeRec> reports,
+            int op, int uid, String packageName, int previousMode) {
+        boolean duplicate = false;
+        if (reports == null) {
+            reports = new ArrayList<>();
+        } else {
+            final int reportCount = reports.size();
+            for (int j = 0; j < reportCount; j++) {
+                ChangeRec report = reports.get(j);
+                if (report.op == op && report.pkg.equals(packageName)) {
+                    duplicate = true;
+                    break;
+                }
+            }
+        }
+        if (!duplicate) {
+            reports.add(new ChangeRec(op, uid, packageName, previousMode));
+        }
+
+        return reports;
+    }
+
+    private static HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> addCallbacks(
+            HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks,
+            int op, int uid, String packageName, int previousMode,
+            ArraySet<OnOpModeChangedListener> cbs) {
+        if (cbs == null) {
+            return callbacks;
+        }
+        if (callbacks == null) {
+            callbacks = new HashMap<>();
+        }
+        final int N = cbs.size();
+        for (int i=0; i<N; i++) {
+            OnOpModeChangedListener cb = cbs.valueAt(i);
+            ArrayList<ChangeRec> reports = callbacks.get(cb);
+            ArrayList<ChangeRec> changed = addChange(reports, op, uid, packageName, previousMode);
+            if (changed != reports) {
+                callbacks.put(cb, changed);
+            }
+        }
+        return callbacks;
+    }
+
+    static final class ChangeRec {
+        final int op;
+        final int uid;
+        final String pkg;
+        final int previous_mode;
+
+        ChangeRec(int _op, int _uid, String _pkg, int _previous_mode) {
+            op = _op;
+            uid = _uid;
+            pkg = _pkg;
+            previous_mode = _previous_mode;
+        }
+    }
+
+    @Override
+    public void resetAllModes(int reqUserId, String reqPackageName) {
+        final int callingPid = Binder.getCallingPid();
+        final int callingUid = Binder.getCallingUid();
+        reqUserId = ActivityManager.handleIncomingUser(callingPid, callingUid, reqUserId,
+                true, true, "resetAllModes", null);
+
+        int reqUid = -1;
+        if (reqPackageName != null) {
+            try {
+                reqUid = AppGlobals.getPackageManager().getPackageUid(
+                        reqPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, reqUserId);
+            } catch (RemoteException e) {
+                /* ignore - local call */
+            }
+        }
+
+        enforceManageAppOpsModes(callingPid, callingUid, reqUid);
+
+        HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks = null;
+        ArrayList<ChangeRec> allChanges = new ArrayList<>();
+        synchronized (this) {
+            boolean changed = false;
+            for (int i = mUidStates.size() - 1; i >= 0; i--) {
+                UidState uidState = mUidStates.valueAt(i);
+
+                SparseIntArray opModes = uidState.getNonDefaultUidModes();
+                if (opModes != null && (uidState.uid == reqUid || reqUid == -1)) {
+                    final int uidOpCount = opModes.size();
+                    for (int j = uidOpCount - 1; j >= 0; j--) {
+                        final int code = opModes.keyAt(j);
+                        if (AppOpsManager.opAllowsReset(code)) {
+                            int previousMode = opModes.valueAt(j);
+                            uidState.setUidMode(code, AppOpsManager.opToDefaultMode(code));
+                            for (String packageName : getPackagesForUid(uidState.uid)) {
+                                callbacks = addCallbacks(callbacks, code, uidState.uid,
+                                        packageName, previousMode,
+                                        mAppOpsServiceInterface.getOpModeChangedListeners(code));
+                                callbacks = addCallbacks(callbacks, code, uidState.uid,
+                                        packageName, previousMode, mAppOpsServiceInterface
+                                                .getPackageModeChangedListeners(packageName));
+
+                                allChanges = addChange(allChanges, code, uidState.uid,
+                                        packageName, previousMode);
+                            }
+                        }
+                    }
+                }
+
+                if (uidState.pkgOps == null) {
+                    continue;
+                }
+
+                if (reqUserId != UserHandle.USER_ALL
+                        && reqUserId != UserHandle.getUserId(uidState.uid)) {
+                    // Skip any ops for a different user
+                    continue;
+                }
+
+                Map<String, Ops> packages = uidState.pkgOps;
+                Iterator<Map.Entry<String, Ops>> it = packages.entrySet().iterator();
+                boolean uidChanged = false;
+                while (it.hasNext()) {
+                    Map.Entry<String, Ops> ent = it.next();
+                    String packageName = ent.getKey();
+                    if (reqPackageName != null && !reqPackageName.equals(packageName)) {
+                        // Skip any ops for a different package
+                        continue;
+                    }
+                    Ops pkgOps = ent.getValue();
+                    for (int j=pkgOps.size()-1; j>=0; j--) {
+                        Op curOp = pkgOps.valueAt(j);
+                        if (shouldDeferResetOpToDpm(curOp.op)) {
+                            deferResetOpToDpm(curOp.op, reqPackageName, reqUserId);
+                            continue;
+                        }
+                        if (AppOpsManager.opAllowsReset(curOp.op)
+                                && curOp.getMode() != AppOpsManager.opToDefaultMode(curOp.op)) {
+                            int previousMode = curOp.getMode();
+                            curOp.setMode(AppOpsManager.opToDefaultMode(curOp.op));
+                            changed = true;
+                            uidChanged = true;
+                            final int uid = curOp.uidState.uid;
+                            callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
+                                    previousMode,
+                                    mAppOpsServiceInterface.getOpModeChangedListeners(curOp.op));
+                            callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
+                                    previousMode, mAppOpsServiceInterface
+                                            .getPackageModeChangedListeners(packageName));
+
+                            allChanges = addChange(allChanges, curOp.op, uid, packageName,
+                                    previousMode);
+                            curOp.removeAttributionsWithNoTime();
+                            if (curOp.mAttributions.isEmpty()) {
+                                pkgOps.removeAt(j);
+                            }
+                        }
+                    }
+                    if (pkgOps.size() == 0) {
+                        it.remove();
+                        mAppOpsServiceInterface.removePackage(packageName,
+                                UserHandle.getUserId(uidState.uid));
+                    }
+                }
+                if (uidState.isDefault()) {
+                    uidState.clear();
+                    mUidStates.remove(uidState.uid);
+                }
+                if (uidChanged) {
+                    uidState.evalForegroundOps();
+                }
+            }
+
+            if (changed) {
+                scheduleFastWriteLocked();
+            }
+        }
+        if (callbacks != null) {
+            for (Map.Entry<OnOpModeChangedListener, ArrayList<ChangeRec>> ent
+                    : callbacks.entrySet()) {
+                OnOpModeChangedListener cb = ent.getKey();
+                ArrayList<ChangeRec> reports = ent.getValue();
+                for (int i=0; i<reports.size(); i++) {
+                    ChangeRec rep = reports.get(i);
+                    mHandler.sendMessage(PooledLambda.obtainMessage(
+                            AppOpsServiceImpl::notifyOpChanged,
+                            this, cb, rep.op, rep.uid, rep.pkg));
+                }
+            }
+        }
+
+        int numChanges = allChanges.size();
+        for (int i = 0; i < numChanges; i++) {
+            ChangeRec change = allChanges.get(i);
+            notifyOpChangedSync(change.op, change.uid, change.pkg,
+                    AppOpsManager.opToDefaultMode(change.op), change.previous_mode);
+        }
+    }
+
+    private boolean shouldDeferResetOpToDpm(int op) {
+        // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
+        //  pre-grants to a role-based mechanism or another general-purpose mechanism.
+        return dpmi != null && dpmi.supportsResetOp(op);
+    }
+
+    /** Assumes {@link #shouldDeferResetOpToDpm(int)} is true. */
+    private void deferResetOpToDpm(int op, String packageName, @UserIdInt int userId) {
+        // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
+        //  pre-grants to a role-based mechanism or another general-purpose mechanism.
+        dpmi.resetOp(op, packageName, userId);
+    }
+
+    private void evalAllForegroundOpsLocked() {
+        for (int uidi = mUidStates.size() - 1; uidi >= 0; uidi--) {
+            final UidState uidState = mUidStates.valueAt(uidi);
+            if (uidState.foregroundOps != null) {
+                uidState.evalForegroundOps();
+            }
+        }
+    }
+
+    @Override
+    public void startWatchingModeWithFlags(int op, String packageName, int flags,
+            IAppOpsCallback callback) {
+        int watchedUid = -1;
+        final int callingUid = Binder.getCallingUid();
+        final int callingPid = Binder.getCallingPid();
+        // TODO: should have a privileged permission to protect this.
+        // Also, if the caller has requested WATCH_FOREGROUND_CHANGES, should we require
+        // the USAGE_STATS permission since this can provide information about when an
+        // app is in the foreground?
+        Preconditions.checkArgumentInRange(op, AppOpsManager.OP_NONE,
+                AppOpsManager._NUM_OP - 1, "Invalid op code: " + op);
+        if (callback == null) {
+            return;
+        }
+        final boolean mayWatchPackageName = packageName != null
+                && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(callingUid));
+        synchronized (this) {
+            int switchOp = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op;
+
+            int notifiedOps;
+            if ((flags & CALL_BACK_ON_SWITCHED_OP) == 0) {
+                if (op == OP_NONE) {
+                    notifiedOps = ALL_OPS;
+                } else {
+                    notifiedOps = op;
+                }
+            } else {
+                notifiedOps = switchOp;
+            }
+
+            ModeCallback cb = mModeWatchers.get(callback.asBinder());
+            if (cb == null) {
+                cb = new ModeCallback(callback, watchedUid, flags, notifiedOps, callingUid,
+                        callingPid);
+                mModeWatchers.put(callback.asBinder(), cb);
+            }
+            if (switchOp != AppOpsManager.OP_NONE) {
+                mAppOpsServiceInterface.startWatchingOpModeChanged(cb, switchOp);
+            }
+            if (mayWatchPackageName) {
+                mAppOpsServiceInterface.startWatchingPackageModeChanged(cb, packageName);
+            }
+            evalAllForegroundOpsLocked();
+        }
+    }
+
+    @Override
+    public void stopWatchingMode(IAppOpsCallback callback) {
+        if (callback == null) {
+            return;
+        }
+        synchronized (this) {
+            ModeCallback cb = mModeWatchers.remove(callback.asBinder());
+            if (cb != null) {
+                cb.unlinkToDeath();
+                mAppOpsServiceInterface.removeListener(cb);
+            }
+
+            evalAllForegroundOpsLocked();
+        }
+    }
+
+    @Override
+    public int checkOperation(int code, int uid, String packageName,
+            @Nullable String attributionTag, boolean raw) {
+        verifyIncomingOp(code);
+        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+            return AppOpsManager.opToDefaultMode(code);
+        }
+
+        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+        if (resolvedPackageName == null) {
+            return AppOpsManager.MODE_IGNORED;
+        }
+        return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag, raw);
+    }
+
+    /**
+     * Get the mode of an app-op.
+     *
+     * @param code        The code of the op
+     * @param uid         The uid of the package the op belongs to
+     * @param packageName The package the op belongs to
+     * @param raw         If the raw state of eval-ed state should be checked.
+     * @return The mode of the op
+     */
+    private @Mode int checkOperationUnchecked(int code, int uid, @NonNull String packageName,
+            @Nullable String attributionTag, boolean raw) {
+        PackageVerificationResult pvr;
+        try {
+            pvr = verifyAndGetBypass(uid, packageName, null);
+        } catch (SecurityException e) {
+            Slog.e(TAG, "checkOperation", e);
+            return AppOpsManager.opToDefaultMode(code);
+        }
+
+        if (isOpRestrictedDueToSuspend(code, packageName, uid)) {
+            return AppOpsManager.MODE_IGNORED;
+        }
+        synchronized (this) {
+            if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, true)) {
+                return AppOpsManager.MODE_IGNORED;
+            }
+            code = AppOpsManager.opToSwitch(code);
+            UidState uidState = getUidStateLocked(uid, false);
+            if (uidState != null
+                    && uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
+                final int rawMode = uidState.getUidMode(code);
+                return raw ? rawMode : uidState.evalMode(code, rawMode);
+            }
+            Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ false);
+            if (op == null) {
+                return AppOpsManager.opToDefaultMode(code);
+            }
+            return raw ? op.getMode() : op.uidState.evalMode(op.op, op.getMode());
+        }
+    }
+
+    @Override
+    public int checkPackage(int uid, String packageName) {
+        Objects.requireNonNull(packageName);
+        try {
+            verifyAndGetBypass(uid, packageName, null);
+            // When the caller is the system, it's possible that the packageName is the special
+            // one (e.g., "root") which isn't actually existed.
+            if (resolveUid(packageName) == uid
+                    || (isPackageExisted(packageName)
+                            && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(uid)))) {
+                return AppOpsManager.MODE_ALLOWED;
+            }
+            return AppOpsManager.MODE_ERRORED;
+        } catch (SecurityException ignored) {
+            return AppOpsManager.MODE_ERRORED;
+        }
+    }
+
+    private boolean isPackageExisted(String packageName) {
+        return getPackageManagerInternal().getPackageStateInternal(packageName) != null;
+    }
+
+    /**
+     * This method will check with PackageManager to determine if the package provided should
+     * be visible to the {@link Binder#getCallingUid()}.
+     *
+     * NOTE: This must not be called while synchronized on {@code this} to avoid dead locks
+     */
+    private boolean filterAppAccessUnlocked(String packageName, int userId) {
+        final int callingUid = Binder.getCallingUid();
+        return LocalServices.getService(PackageManagerInternal.class)
+                .filterAppAccess(packageName, callingUid, userId);
+    }
+
+    @Override
+    public int noteOperation(int code, int uid, @Nullable String packageName,
+            @Nullable String attributionTag, @Nullable String message) {
+        verifyIncomingUid(uid);
+        verifyIncomingOp(code);
+        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+            return AppOpsManager.MODE_ERRORED;
+        }
+
+        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+        if (resolvedPackageName == null) {
+            return AppOpsManager.MODE_IGNORED;
+        }
+        return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
+                Process.INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
+    }
+
+    @Override
+    public int noteOperationUnchecked(int code, int uid, @NonNull String packageName,
+            @Nullable String attributionTag, int proxyUid, String proxyPackageName,
+            @Nullable String proxyAttributionTag, @OpFlags int flags) {
+        PackageVerificationResult pvr;
+        try {
+            pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
+            if (!pvr.isAttributionTagValid) {
+                attributionTag = null;
+            }
+        } catch (SecurityException e) {
+            Slog.e(TAG, "noteOperation", e);
+            return AppOpsManager.MODE_ERRORED;
+        }
+
+        synchronized (this) {
+            final Ops ops = getOpsLocked(uid, packageName, attributionTag,
+                    pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
+            if (ops == null) {
+                scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+                        AppOpsManager.MODE_IGNORED);
+                if (DEBUG) {
+                    Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
+                            + " package " + packageName + "flags: "
+                            + AppOpsManager.flagsToString(flags));
+                }
+                return AppOpsManager.MODE_ERRORED;
+            }
+            final Op op = getOpLocked(ops, code, uid, true);
+            final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
+            if (attributedOp.isRunning()) {
+                Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code "
+                        + code + " startTime of in progress event="
+                        + attributedOp.mInProgressEvents.valueAt(0).getStartTime());
+            }
+
+            final int switchCode = AppOpsManager.opToSwitch(code);
+            final UidState uidState = ops.uidState;
+            if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, false)) {
+                attributedOp.rejected(uidState.getState(), flags);
+                scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+                        AppOpsManager.MODE_IGNORED);
+                return AppOpsManager.MODE_IGNORED;
+            }
+            // If there is a non-default per UID policy (we set UID op mode only if
+            // non-default) it takes over, otherwise use the per package policy.
+            if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
+                final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
+                if (uidMode != AppOpsManager.MODE_ALLOWED) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
+                                + switchCode + " (" + code + ") uid " + uid + " package "
+                                + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+                    }
+                    attributedOp.rejected(uidState.getState(), flags);
+                    scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+                            uidMode);
+                    return uidMode;
+                }
+            } else {
+                final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
+                        : op;
+                final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
+                if (mode != AppOpsManager.MODE_ALLOWED) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "noteOperation: reject #" + mode + " for code "
+                                + switchCode + " (" + code + ") uid " + uid + " package "
+                                + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+                    }
+                    attributedOp.rejected(uidState.getState(), flags);
+                    scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+                            mode);
+                    return mode;
+                }
+            }
+            if (DEBUG) {
+                Slog.d(TAG,
+                        "noteOperation: allowing code " + code + " uid " + uid + " package "
+                                + packageName + (attributionTag == null ? ""
+                                : "." + attributionTag) + " flags: "
+                                + AppOpsManager.flagsToString(flags));
+            }
+            scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+                    AppOpsManager.MODE_ALLOWED);
+            attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
+                    uidState.getState(),
+                    flags);
+
+            return AppOpsManager.MODE_ALLOWED;
+        }
+    }
+
+    @Override
+    public boolean isAttributionTagValid(int uid, @NonNull String packageName,
+            @Nullable String attributionTag,
+            @Nullable String proxyPackageName) {
+        try {
+            return verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName)
+                    .isAttributionTagValid;
+        } catch (SecurityException ignored) {
+            // We don't want to throw, this exception will be handled in the (c/n/s)Operation calls
+            // when they need the bypass object.
+            return false;
+        }
+    }
+
+    // TODO moltmann: Allow watching for attribution ops
+    @Override
+    public void startWatchingActive(int[] ops, IAppOpsActiveCallback callback) {
+        int watchedUid = Process.INVALID_UID;
+        final int callingUid = Binder.getCallingUid();
+        final int callingPid = Binder.getCallingPid();
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
+                != PackageManager.PERMISSION_GRANTED) {
+            watchedUid = callingUid;
+        }
+        if (ops != null) {
+            Preconditions.checkArrayElementsInRange(ops, 0,
+                    AppOpsManager._NUM_OP - 1, "Invalid op code in: " + Arrays.toString(ops));
+        }
+        if (callback == null) {
+            return;
+        }
+        synchronized (this) {
+            SparseArray<ActiveCallback> callbacks = mActiveWatchers.get(callback.asBinder());
+            if (callbacks == null) {
+                callbacks = new SparseArray<>();
+                mActiveWatchers.put(callback.asBinder(), callbacks);
+            }
+            final ActiveCallback activeCallback = new ActiveCallback(callback, watchedUid,
+                    callingUid, callingPid);
+            for (int op : ops) {
+                callbacks.put(op, activeCallback);
+            }
+        }
+    }
+
+    @Override
+    public void stopWatchingActive(IAppOpsActiveCallback callback) {
+        if (callback == null) {
+            return;
+        }
+        synchronized (this) {
+            final SparseArray<ActiveCallback> activeCallbacks =
+                    mActiveWatchers.remove(callback.asBinder());
+            if (activeCallbacks == null) {
+                return;
+            }
+            final int callbackCount = activeCallbacks.size();
+            for (int i = 0; i < callbackCount; i++) {
+                activeCallbacks.valueAt(i).destroy();
+            }
+        }
+    }
+
+    @Override
+    public void startWatchingStarted(int[] ops, @NonNull IAppOpsStartedCallback callback) {
+        int watchedUid = Process.INVALID_UID;
+        final int callingUid = Binder.getCallingUid();
+        final int callingPid = Binder.getCallingPid();
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
+                != PackageManager.PERMISSION_GRANTED) {
+            watchedUid = callingUid;
+        }
+
+        Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
+        Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
+                "Invalid op code in: " + Arrays.toString(ops));
+        Objects.requireNonNull(callback, "Callback cannot be null");
+
+        synchronized (this) {
+            SparseArray<StartedCallback> callbacks = mStartedWatchers.get(callback.asBinder());
+            if (callbacks == null) {
+                callbacks = new SparseArray<>();
+                mStartedWatchers.put(callback.asBinder(), callbacks);
+            }
+
+            final StartedCallback startedCallback = new StartedCallback(callback, watchedUid,
+                    callingUid, callingPid);
+            for (int op : ops) {
+                callbacks.put(op, startedCallback);
+            }
+        }
+    }
+
+    @Override
+    public void stopWatchingStarted(IAppOpsStartedCallback callback) {
+        Objects.requireNonNull(callback, "Callback cannot be null");
+
+        synchronized (this) {
+            final SparseArray<StartedCallback> startedCallbacks =
+                    mStartedWatchers.remove(callback.asBinder());
+            if (startedCallbacks == null) {
+                return;
+            }
+
+            final int callbackCount = startedCallbacks.size();
+            for (int i = 0; i < callbackCount; i++) {
+                startedCallbacks.valueAt(i).destroy();
+            }
+        }
+    }
+
+    @Override
+    public void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback) {
+        int watchedUid = Process.INVALID_UID;
+        final int callingUid = Binder.getCallingUid();
+        final int callingPid = Binder.getCallingPid();
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
+                != PackageManager.PERMISSION_GRANTED) {
+            watchedUid = callingUid;
+        }
+        Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
+        Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
+                "Invalid op code in: " + Arrays.toString(ops));
+        Objects.requireNonNull(callback, "Callback cannot be null");
+        synchronized (this) {
+            SparseArray<NotedCallback> callbacks = mNotedWatchers.get(callback.asBinder());
+            if (callbacks == null) {
+                callbacks = new SparseArray<>();
+                mNotedWatchers.put(callback.asBinder(), callbacks);
+            }
+            final NotedCallback notedCallback = new NotedCallback(callback, watchedUid,
+                    callingUid, callingPid);
+            for (int op : ops) {
+                callbacks.put(op, notedCallback);
+            }
+        }
+    }
+
+    @Override
+    public void stopWatchingNoted(IAppOpsNotedCallback callback) {
+        Objects.requireNonNull(callback, "Callback cannot be null");
+        synchronized (this) {
+            final SparseArray<NotedCallback> notedCallbacks =
+                    mNotedWatchers.remove(callback.asBinder());
+            if (notedCallbacks == null) {
+                return;
+            }
+            final int callbackCount = notedCallbacks.size();
+            for (int i = 0; i < callbackCount; i++) {
+                notedCallbacks.valueAt(i).destroy();
+            }
+        }
+    }
+
+    @Override
+    public int startOperation(@NonNull IBinder clientId, int code, int uid,
+            @Nullable String packageName, @Nullable String attributionTag,
+            boolean startIfModeDefault, @NonNull String message,
+            @AttributionFlags int attributionFlags, int attributionChainId) {
+        verifyIncomingUid(uid);
+        verifyIncomingOp(code);
+        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+            return AppOpsManager.MODE_ERRORED;
+        }
+
+        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+        if (resolvedPackageName == null) {
+            return AppOpsManager.MODE_IGNORED;
+        }
+
+        // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution
+        // purposes and not as a check, also make sure that the caller is allowed to access
+        // the data gated by OP_RECORD_AUDIO.
+        //
+        // TODO: Revert this change before Android 12.
+        if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) {
+            int result = checkOperation(OP_RECORD_AUDIO, uid, packageName, null, false);
+            if (result != AppOpsManager.MODE_ALLOWED) {
+                return result;
+            }
+        }
+        return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
+                Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
+                attributionFlags, attributionChainId, /*dryRun*/ false);
+    }
+
+    private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
+        return (mode == MODE_ALLOWED || (mode == MODE_DEFAULT && startIfModeDefault));
+    }
+
+    @Override
+    public int startOperationUnchecked(IBinder clientId, int code, int uid,
+            @NonNull String packageName, @Nullable String attributionTag, int proxyUid,
+            String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
+            boolean startIfModeDefault, @AttributionFlags int attributionFlags,
+            int attributionChainId, boolean dryRun) {
+        PackageVerificationResult pvr;
+        try {
+            pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
+            if (!pvr.isAttributionTagValid) {
+                attributionTag = null;
+            }
+        } catch (SecurityException e) {
+            Slog.e(TAG, "startOperation", e);
+            return AppOpsManager.MODE_ERRORED;
+        }
+
+        boolean isRestricted;
+        int startType = START_TYPE_FAILED;
+        synchronized (this) {
+            final Ops ops = getOpsLocked(uid, packageName, attributionTag,
+                    pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
+            if (ops == null) {
+                if (!dryRun) {
+                    scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+                            flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags,
+                            attributionChainId);
+                }
+                if (DEBUG) {
+                    Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
+                            + " package " + packageName + " flags: "
+                            + AppOpsManager.flagsToString(flags));
+                }
+                return AppOpsManager.MODE_ERRORED;
+            }
+            final Op op = getOpLocked(ops, code, uid, true);
+            final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
+            final UidState uidState = ops.uidState;
+            isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass,
+                    false);
+            final int switchCode = AppOpsManager.opToSwitch(code);
+            // If there is a non-default per UID policy (we set UID op mode only if
+            // non-default) it takes over, otherwise use the per package policy.
+            if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
+                final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
+                if (!shouldStartForMode(uidMode, startIfModeDefault)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code "
+                                + switchCode + " (" + code + ") uid " + uid + " package "
+                                + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+                    }
+                    if (!dryRun) {
+                        attributedOp.rejected(uidState.getState(), flags);
+                        scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+                                flags, uidMode, startType, attributionFlags, attributionChainId);
+                    }
+                    return uidMode;
+                }
+            } else {
+                final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
+                        : op;
+                final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
+                if (!shouldStartForMode(mode, startIfModeDefault)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "startOperation: reject #" + mode + " for code "
+                                + switchCode + " (" + code + ") uid " + uid + " package "
+                                + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+                    }
+                    if (!dryRun) {
+                        attributedOp.rejected(uidState.getState(), flags);
+                        scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+                                flags, mode, startType, attributionFlags, attributionChainId);
+                    }
+                    return mode;
+                }
+            }
+            if (DEBUG) {
+                Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
+                        + " package " + packageName + " restricted: " + isRestricted
+                        + " flags: " + AppOpsManager.flagsToString(flags));
+            }
+            if (!dryRun) {
+                try {
+                    if (isRestricted) {
+                        attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
+                                proxyAttributionTag, uidState.getState(), flags,
+                                attributionFlags, attributionChainId);
+                    } else {
+                        attributedOp.started(clientId, proxyUid, proxyPackageName,
+                                proxyAttributionTag, uidState.getState(), flags,
+                                attributionFlags, attributionChainId);
+                        startType = START_TYPE_STARTED;
+                    }
+                } catch (RemoteException e) {
+                    throw new RuntimeException(e);
+                }
+                scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+                        isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
+                        attributionChainId);
+            }
+        }
+
+        // Possible bug? The raw mode could have been MODE_DEFAULT to reach here.
+        return isRestricted ? MODE_IGNORED : MODE_ALLOWED;
+    }
+
+    @Override
+    public void finishOperation(IBinder clientId, int code, int uid, String packageName,
+            String attributionTag) {
+        verifyIncomingUid(uid);
+        verifyIncomingOp(code);
+        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+            return;
+        }
+
+        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+        if (resolvedPackageName == null) {
+            return;
+        }
+
+        finishOperationUnchecked(clientId, code, uid, resolvedPackageName, attributionTag);
+    }
+
+    @Override
+    public void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName,
+            String attributionTag) {
+        PackageVerificationResult pvr;
+        try {
+            pvr = verifyAndGetBypass(uid, packageName, attributionTag);
+            if (!pvr.isAttributionTagValid) {
+                attributionTag = null;
+            }
+        } catch (SecurityException e) {
+            Slog.e(TAG, "Cannot finishOperation", e);
+            return;
+        }
+
+        synchronized (this) {
+            Op op = getOpLocked(code, uid, packageName, attributionTag, pvr.isAttributionTagValid,
+                    pvr.bypass, /* edit */ true);
+            if (op == null) {
+                Slog.e(TAG, "Operation not found: uid=" + uid + " pkg=" + packageName + "("
+                        + attributionTag + ") op=" + AppOpsManager.opToName(code));
+                return;
+            }
+            final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
+            if (attributedOp == null) {
+                Slog.e(TAG, "Attribution not found: uid=" + uid + " pkg=" + packageName + "("
+                        + attributionTag + ") op=" + AppOpsManager.opToName(code));
+                return;
+            }
+
+            if (attributedOp.isRunning() || attributedOp.isPaused()) {
+                attributedOp.finished(clientId);
+            } else {
+                Slog.e(TAG, "Operation not started: uid=" + uid + " pkg=" + packageName + "("
+                        + attributionTag + ") op=" + AppOpsManager.opToName(code));
+            }
+        }
+    }
+
+    void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull String packageName,
+            @Nullable String attributionTag, boolean active,
+            @AttributionFlags int attributionFlags, int attributionChainId) {
+        ArraySet<ActiveCallback> dispatchedCallbacks = null;
+        final int callbackListCount = mActiveWatchers.size();
+        for (int i = 0; i < callbackListCount; i++) {
+            final SparseArray<ActiveCallback> callbacks = mActiveWatchers.valueAt(i);
+            ActiveCallback callback = callbacks.get(code);
+            if (callback != null) {
+                if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
+                    continue;
+                }
+                if (dispatchedCallbacks == null) {
+                    dispatchedCallbacks = new ArraySet<>();
+                }
+                dispatchedCallbacks.add(callback);
+            }
+        }
+        if (dispatchedCallbacks == null) {
+            return;
+        }
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                AppOpsServiceImpl::notifyOpActiveChanged,
+                this, dispatchedCallbacks, code, uid, packageName, attributionTag, active,
+                attributionFlags, attributionChainId));
+    }
+
+    private void notifyOpActiveChanged(ArraySet<ActiveCallback> callbacks,
+            int code, int uid, @NonNull String packageName, @Nullable String attributionTag,
+            boolean active, @AttributionFlags int attributionFlags, int attributionChainId) {
+        // There are features watching for mode changes such as window manager
+        // and location manager which are in our process. The callbacks in these
+        // features may require permissions our remote caller does not have.
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final int callbackCount = callbacks.size();
+            for (int i = 0; i < callbackCount; i++) {
+                final ActiveCallback callback = callbacks.valueAt(i);
+                try {
+                    if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
+                        continue;
+                    }
+                    callback.mCallback.opActiveChanged(code, uid, packageName, attributionTag,
+                            active, attributionFlags, attributionChainId);
+                } catch (RemoteException e) {
+                    /* do nothing */
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName,
+            String attributionTag, @OpFlags int flags, @Mode int result,
+            @AppOpsManager.OnOpStartedListener.StartedType int startedType,
+            @AttributionFlags int attributionFlags, int attributionChainId) {
+        ArraySet<StartedCallback> dispatchedCallbacks = null;
+        final int callbackListCount = mStartedWatchers.size();
+        for (int i = 0; i < callbackListCount; i++) {
+            final SparseArray<StartedCallback> callbacks = mStartedWatchers.valueAt(i);
+
+            StartedCallback callback = callbacks.get(code);
+            if (callback != null) {
+                if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
+                    continue;
+                }
+
+                if (dispatchedCallbacks == null) {
+                    dispatchedCallbacks = new ArraySet<>();
+                }
+                dispatchedCallbacks.add(callback);
+            }
+        }
+
+        if (dispatchedCallbacks == null) {
+            return;
+        }
+
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                AppOpsServiceImpl::notifyOpStarted,
+                this, dispatchedCallbacks, code, uid, pkgName, attributionTag, flags,
+                result, startedType, attributionFlags, attributionChainId));
+    }
+
+    private void notifyOpStarted(ArraySet<StartedCallback> callbacks,
+            int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
+            @Mode int result, @AppOpsManager.OnOpStartedListener.StartedType int startedType,
+            @AttributionFlags int attributionFlags, int attributionChainId) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final int callbackCount = callbacks.size();
+            for (int i = 0; i < callbackCount; i++) {
+                final StartedCallback callback = callbacks.valueAt(i);
+                try {
+                    if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
+                        continue;
+                    }
+                    callback.mCallback.opStarted(code, uid, packageName, attributionTag, flags,
+                            result, startedType, attributionFlags, attributionChainId);
+                } catch (RemoteException e) {
+                    /* do nothing */
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    private void scheduleOpNotedIfNeededLocked(int code, int uid, String packageName,
+            String attributionTag, @OpFlags int flags, @Mode int result) {
+        ArraySet<NotedCallback> dispatchedCallbacks = null;
+        final int callbackListCount = mNotedWatchers.size();
+        for (int i = 0; i < callbackListCount; i++) {
+            final SparseArray<NotedCallback> callbacks = mNotedWatchers.valueAt(i);
+            final NotedCallback callback = callbacks.get(code);
+            if (callback != null) {
+                if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
+                    continue;
+                }
+                if (dispatchedCallbacks == null) {
+                    dispatchedCallbacks = new ArraySet<>();
+                }
+                dispatchedCallbacks.add(callback);
+            }
+        }
+        if (dispatchedCallbacks == null) {
+            return;
+        }
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                AppOpsServiceImpl::notifyOpChecked,
+                this, dispatchedCallbacks, code, uid, packageName, attributionTag, flags,
+                result));
+    }
+
+    private void notifyOpChecked(ArraySet<NotedCallback> callbacks,
+            int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
+            @Mode int result) {
+        // There are features watching for checks in our process. The callbacks in
+        // these features may require permissions our remote caller does not have.
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final int callbackCount = callbacks.size();
+            for (int i = 0; i < callbackCount; i++) {
+                final NotedCallback callback = callbacks.valueAt(i);
+                try {
+                    if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
+                        continue;
+                    }
+                    callback.mCallback.opNoted(code, uid, packageName, attributionTag, flags,
+                            result);
+                } catch (RemoteException e) {
+                    /* do nothing */
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    private void verifyIncomingUid(int uid) {
+        if (uid == Binder.getCallingUid()) {
+            return;
+        }
+        if (Binder.getCallingPid() == Process.myPid()) {
+            return;
+        }
+        mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
+                Binder.getCallingPid(), Binder.getCallingUid(), null);
+    }
+
+    private boolean shouldIgnoreCallback(int op, int watcherPid, int watcherUid) {
+        // If it's a restricted read op, ignore it if watcher doesn't have manage ops permission,
+        // as watcher should not use this to signal if the value is changed.
+        return opRestrictsRead(op) && mContext.checkPermission(Manifest.permission.MANAGE_APPOPS,
+                watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED;
+    }
+
+    private void verifyIncomingOp(int op) {
+        if (op >= 0 && op < AppOpsManager._NUM_OP) {
+            // Enforce manage appops permission if it's a restricted read op.
+            if (opRestrictsRead(op)) {
+                mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
+                        Binder.getCallingPid(), Binder.getCallingUid(), "verifyIncomingOp");
+            }
+            return;
+        }
+        throw new IllegalArgumentException("Bad operation #" + op);
+    }
+
+    private boolean isIncomingPackageValid(@Nullable String packageName, @UserIdInt int userId) {
+        final int callingUid = Binder.getCallingUid();
+        // Handle the special UIDs that don't have actual packages (audioserver, cameraserver, etc).
+        if (packageName == null || isSpecialPackage(callingUid, packageName)) {
+            return true;
+        }
+
+        // If the package doesn't exist, #verifyAndGetBypass would throw a SecurityException in
+        // the end. Although that exception would be caught and return, we could make it return
+        // early.
+        if (!isPackageExisted(packageName)) {
+            return false;
+        }
+
+        if (getPackageManagerInternal().filterAppAccess(packageName, callingUid, userId)) {
+            Slog.w(TAG, packageName + " not found from " + callingUid);
+            return false;
+        }
+
+        return true;
+    }
+
+    private boolean isSpecialPackage(int callingUid, @Nullable String packageName) {
+        final String resolvedPackage = AppOpsManager.resolvePackageName(callingUid, packageName);
+        return callingUid == Process.SYSTEM_UID
+                || resolveUid(resolvedPackage) != Process.INVALID_UID;
+    }
+
+    private @Nullable UidState getUidStateLocked(int uid, boolean edit) {
+        UidState uidState = mUidStates.get(uid);
+        if (uidState == null) {
+            if (!edit) {
+                return null;
+            }
+            uidState = new UidState(uid);
+            mUidStates.put(uid, uidState);
+        }
+
+        return uidState;
+    }
+
+    @Override
+    public void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible) {
+        synchronized (this) {
+            getUidStateTracker().updateAppWidgetVisibility(uidPackageNames, visible);
+        }
+    }
+
+    /**
+     * @return {@link PackageManagerInternal}
+     */
+    private @NonNull PackageManagerInternal getPackageManagerInternal() {
+        if (mPackageManagerInternal == null) {
+            mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+        }
+
+        return mPackageManagerInternal;
+    }
+
+    @Override
+    public void verifyPackage(int uid, String packageName) {
+        verifyAndGetBypass(uid, packageName, null);
+    }
+
+    /**
+     * Create a restriction description matching the properties of the package.
+     *
+     * @param pkg The package to create the restriction description for
+     * @return The restriction matching the package
+     */
+    private RestrictionBypass getBypassforPackage(@NonNull AndroidPackage pkg) {
+        return new RestrictionBypass(pkg.getUid() == Process.SYSTEM_UID, pkg.isPrivileged(),
+                mContext.checkPermission(android.Manifest.permission
+                        .EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS, -1, pkg.getUid())
+                        == PackageManager.PERMISSION_GRANTED);
+    }
+
+    /**
+     * @see #verifyAndGetBypass(int, String, String, String)
+     */
+    private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
+            @Nullable String attributionTag) {
+        return verifyAndGetBypass(uid, packageName, attributionTag, null);
+    }
+
+    /**
+     * Verify that package belongs to uid and return the {@link RestrictionBypass bypass
+     * description} for the package, along with a boolean indicating whether the attribution tag is
+     * valid.
+     *
+     * @param uid              The uid the package belongs to
+     * @param packageName      The package the might belong to the uid
+     * @param attributionTag   attribution tag or {@code null} if no need to verify
+     * @param proxyPackageName The proxy package, from which the attribution tag is to be pulled
+     * @return PackageVerificationResult containing {@link RestrictionBypass} and whether the
+     * attribution tag is valid
+     */
+    private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
+            @Nullable String attributionTag, @Nullable String proxyPackageName) {
+        if (uid == Process.ROOT_UID) {
+            // For backwards compatibility, don't check package name for root UID.
+            return new PackageVerificationResult(null,
+                    /* isAttributionTagValid */ true);
+        }
+        if (Process.isSdkSandboxUid(uid)) {
+            // SDK sandbox processes run in their own UID range, but their associated
+            // UID for checks should always be the UID of the package implementing SDK sandbox
+            // service.
+            // TODO: We will need to modify the callers of this function instead, so
+            // modifications and checks against the app ops state are done with the
+            // correct UID.
+            try {
+                final PackageManager pm = mContext.getPackageManager();
+                final String supplementalPackageName = pm.getSdkSandboxPackageName();
+                if (Objects.equals(packageName, supplementalPackageName)) {
+                    uid = pm.getPackageUidAsUser(supplementalPackageName,
+                            PackageManager.PackageInfoFlags.of(0), UserHandle.getUserId(uid));
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                // Shouldn't happen for the supplemental package
+                e.printStackTrace();
+            }
+        }
+
+
+        // Do not check if uid/packageName/attributionTag is already known.
+        synchronized (this) {
+            UidState uidState = mUidStates.get(uid);
+            if (uidState != null && uidState.pkgOps != null) {
+                Ops ops = uidState.pkgOps.get(packageName);
+
+                if (ops != null && (attributionTag == null || ops.knownAttributionTags.contains(
+                        attributionTag)) && ops.bypass != null) {
+                    return new PackageVerificationResult(ops.bypass,
+                            ops.validAttributionTags.contains(attributionTag));
+                }
+            }
+        }
+
+        int callingUid = Binder.getCallingUid();
+
+        // Allow any attribution tag for resolvable uids
+        int pkgUid;
+        if (Objects.equals(packageName, "com.android.shell")) {
+            // Special case for the shell which is a package but should be able
+            // to bypass app attribution tag restrictions.
+            pkgUid = Process.SHELL_UID;
+        } else {
+            pkgUid = resolveUid(packageName);
+        }
+        if (pkgUid != Process.INVALID_UID) {
+            if (pkgUid != UserHandle.getAppId(uid)) {
+                Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
+                        + "Package \"" + packageName + "\" does not belong to uid " + uid + ".");
+                String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
+                throw new SecurityException("Specified package \"" + packageName + "\" under uid "
+                        + UserHandle.getAppId(uid) + otherUidMessage);
+            }
+            return new PackageVerificationResult(RestrictionBypass.UNRESTRICTED,
+                    /* isAttributionTagValid */ true);
+        }
+
+        int userId = UserHandle.getUserId(uid);
+        RestrictionBypass bypass = null;
+        boolean isAttributionTagValid = false;
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
+            AndroidPackage pkg = pmInt.getPackage(packageName);
+            if (pkg != null) {
+                isAttributionTagValid = isAttributionInPackage(pkg, attributionTag);
+                pkgUid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid()));
+                bypass = getBypassforPackage(pkg);
+            }
+            if (!isAttributionTagValid) {
+                AndroidPackage proxyPkg = proxyPackageName != null
+                        ? pmInt.getPackage(proxyPackageName) : null;
+                // Re-check in proxy.
+                isAttributionTagValid = isAttributionInPackage(proxyPkg, attributionTag);
+                String msg;
+                if (pkg != null && isAttributionTagValid) {
+                    msg = "attributionTag " + attributionTag + " declared in manifest of the proxy"
+                            + " package " + proxyPackageName + ", this is not advised";
+                } else if (pkg != null) {
+                    msg = "attributionTag " + attributionTag + " not declared in manifest of "
+                            + packageName;
+                } else {
+                    msg = "package " + packageName + " not found, can't check for "
+                            + "attributionTag " + attributionTag;
+                }
+
+                try {
+                    if (!mPlatformCompat.isChangeEnabledByPackageName(
+                            SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, packageName,
+                            userId) || !mPlatformCompat.isChangeEnabledByUid(
+                            SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE,
+                            callingUid)) {
+                        // Do not override tags if overriding is not enabled for this package
+                        isAttributionTagValid = true;
+                    }
+                    Slog.e(TAG, msg);
+                } catch (RemoteException neverHappens) {
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+
+        if (pkgUid != uid) {
+            Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
+                    + "Package \"" + packageName + "\" does not belong to uid " + uid + ".");
+            String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
+            throw new SecurityException("Specified package \"" + packageName + "\" under uid " + uid
+                    + otherUidMessage);
+        }
+
+        return new PackageVerificationResult(bypass, isAttributionTagValid);
+    }
+
+    private boolean isAttributionInPackage(@Nullable AndroidPackage pkg,
+            @Nullable String attributionTag) {
+        if (pkg == null) {
+            return false;
+        } else if (attributionTag == null) {
+            return true;
+        }
+        if (pkg.getAttributions() != null) {
+            int numAttributions = pkg.getAttributions().size();
+            for (int i = 0; i < numAttributions; i++) {
+                if (pkg.getAttributions().get(i).getTag().equals(attributionTag)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Get (and potentially create) ops.
+     *
+     * @param uid                   The uid the package belongs to
+     * @param packageName           The name of the package
+     * @param attributionTag        attribution tag
+     * @param isAttributionTagValid whether the given attribution tag is valid
+     * @param bypass                When to bypass certain op restrictions (can be null if edit
+        *                              == false)
+     * @param edit                  If an ops does not exist, create the ops?
+     * @return The ops
+     */
+    private Ops getOpsLocked(int uid, String packageName, @Nullable String attributionTag,
+            boolean isAttributionTagValid, @Nullable RestrictionBypass bypass, boolean edit) {
+        UidState uidState = getUidStateLocked(uid, edit);
+        if (uidState == null) {
+            return null;
+        }
+
+        if (uidState.pkgOps == null) {
+            if (!edit) {
+                return null;
+            }
+            uidState.pkgOps = new ArrayMap<>();
+        }
+
+        Ops ops = uidState.pkgOps.get(packageName);
+        if (ops == null) {
+            if (!edit) {
+                return null;
+            }
+            ops = new Ops(packageName, uidState);
+            uidState.pkgOps.put(packageName, ops);
+        }
+
+        if (edit) {
+            if (bypass != null) {
+                ops.bypass = bypass;
+            }
+
+            if (attributionTag != null) {
+                ops.knownAttributionTags.add(attributionTag);
+                if (isAttributionTagValid) {
+                    ops.validAttributionTags.add(attributionTag);
+                } else {
+                    ops.validAttributionTags.remove(attributionTag);
+                }
+            }
+        }
+
+        return ops;
+    }
+
+    @Override
+    public void scheduleWriteLocked() {
+        if (!mWriteScheduled) {
+            mWriteScheduled = true;
+            mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
+        }
+    }
+
+    @Override
+    public void scheduleFastWriteLocked() {
+        if (!mFastWriteScheduled) {
+            mWriteScheduled = true;
+            mFastWriteScheduled = true;
+            mHandler.removeCallbacks(mWriteRunner);
+            mHandler.postDelayed(mWriteRunner, 10 * 1000);
+        }
+    }
+
+    /**
+     * Get the state of an op for a uid.
+     *
+     * @param code                  The code of the op
+     * @param uid                   The uid the of the package
+     * @param packageName           The package name for which to get the state for
+     * @param attributionTag        The attribution tag
+     * @param isAttributionTagValid Whether the given attribution tag is valid
+     * @param bypass                When to bypass certain op restrictions (can be null if edit
+     *                              == false)
+     * @param edit                  Iff {@code true} create the {@link Op} object if not yet created
+     * @return The {@link Op state} of the op
+     */
+    private @Nullable Op getOpLocked(int code, int uid, @NonNull String packageName,
+            @Nullable String attributionTag, boolean isAttributionTagValid,
+            @Nullable RestrictionBypass bypass, boolean edit) {
+        Ops ops = getOpsLocked(uid, packageName, attributionTag, isAttributionTagValid, bypass,
+                edit);
+        if (ops == null) {
+            return null;
+        }
+        return getOpLocked(ops, code, uid, edit);
+    }
+
+    private Op getOpLocked(Ops ops, int code, int uid, boolean edit) {
+        Op op = ops.get(code);
+        if (op == null) {
+            if (!edit) {
+                return null;
+            }
+            op = new Op(ops.uidState, ops.packageName, code, uid);
+            ops.put(code, op);
+        }
+        if (edit) {
+            scheduleWriteLocked();
+        }
+        return op;
+    }
+
+    private boolean isOpRestrictedDueToSuspend(int code, String packageName, int uid) {
+        if (!ArrayUtils.contains(OPS_RESTRICTED_ON_SUSPEND, code)) {
+            return false;
+        }
+        final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+        return pmi.isPackageSuspended(packageName, UserHandle.getUserId(uid));
+    }
+
+    private boolean isOpRestrictedLocked(int uid, int code, String packageName,
+            String attributionTag, @Nullable RestrictionBypass appBypass, boolean isCheckOp) {
+        int restrictionSetCount = mOpGlobalRestrictions.size();
+
+        for (int i = 0; i < restrictionSetCount; i++) {
+            ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.valueAt(i);
+            if (restrictionState.hasRestriction(code)) {
+                return true;
+            }
+        }
+
+        int userHandle = UserHandle.getUserId(uid);
+        restrictionSetCount = mOpUserRestrictions.size();
+
+        for (int i = 0; i < restrictionSetCount; i++) {
+            // For each client, check that the given op is not restricted, or that the given
+            // package is exempt from the restriction.
+            ClientUserRestrictionState restrictionState = mOpUserRestrictions.valueAt(i);
+            if (restrictionState.hasRestriction(code, packageName, attributionTag, userHandle,
+                    isCheckOp)) {
+                RestrictionBypass opBypass = opAllowSystemBypassRestriction(code);
+                if (opBypass != null) {
+                    // If we are the system, bypass user restrictions for certain codes
+                    synchronized (this) {
+                        if (opBypass.isSystemUid && appBypass != null && appBypass.isSystemUid) {
+                            return false;
+                        }
+                        if (opBypass.isPrivileged && appBypass != null && appBypass.isPrivileged) {
+                            return false;
+                        }
+                        if (opBypass.isRecordAudioRestrictionExcept && appBypass != null
+                                && appBypass.isRecordAudioRestrictionExcept) {
+                            return false;
+                        }
+                    }
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void readState() {
+        int oldVersion = NO_VERSION;
+        synchronized (mFile) {
+            synchronized (this) {
+                FileInputStream stream;
+                try {
+                    stream = mFile.openRead();
+                } catch (FileNotFoundException e) {
+                    Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
+                    return;
+                }
+                boolean success = false;
+                mUidStates.clear();
+                mAppOpsServiceInterface.clearAllModes();
+                try {
+                    TypedXmlPullParser parser = Xml.resolvePullParser(stream);
+                    int type;
+                    while ((type = parser.next()) != XmlPullParser.START_TAG
+                            && type != XmlPullParser.END_DOCUMENT) {
+                        // Parse next until we reach the start or end
+                    }
+
+                    if (type != XmlPullParser.START_TAG) {
+                        throw new IllegalStateException("no start tag found");
+                    }
+
+                    oldVersion = parser.getAttributeInt(null, "v", NO_VERSION);
+
+                    int outerDepth = parser.getDepth();
+                    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                            && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                        if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                            continue;
+                        }
+
+                        String tagName = parser.getName();
+                        if (tagName.equals("pkg")) {
+                            readPackage(parser);
+                        } else if (tagName.equals("uid")) {
+                            readUidOps(parser);
+                        } else {
+                            Slog.w(TAG, "Unknown element under <app-ops>: "
+                                    + parser.getName());
+                            XmlUtils.skipCurrentTag(parser);
+                        }
+                    }
+                    success = true;
+                } catch (IllegalStateException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } catch (NullPointerException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } catch (NumberFormatException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } catch (XmlPullParserException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } catch (IOException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } catch (IndexOutOfBoundsException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } finally {
+                    if (!success) {
+                        mUidStates.clear();
+                        mAppOpsServiceInterface.clearAllModes();
+                    }
+                    try {
+                        stream.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+        }
+        synchronized (this) {
+            upgradeLocked(oldVersion);
+        }
+    }
+
+    private void upgradeRunAnyInBackgroundLocked() {
+        for (int i = 0; i < mUidStates.size(); i++) {
+            final UidState uidState = mUidStates.valueAt(i);
+            if (uidState == null) {
+                continue;
+            }
+            SparseIntArray opModes = uidState.getNonDefaultUidModes();
+            if (opModes != null) {
+                final int idx = opModes.indexOfKey(AppOpsManager.OP_RUN_IN_BACKGROUND);
+                if (idx >= 0) {
+                    uidState.setUidMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
+                            opModes.valueAt(idx));
+                }
+            }
+            if (uidState.pkgOps == null) {
+                continue;
+            }
+            boolean changed = false;
+            for (int j = 0; j < uidState.pkgOps.size(); j++) {
+                Ops ops = uidState.pkgOps.valueAt(j);
+                if (ops != null) {
+                    final Op op = ops.get(AppOpsManager.OP_RUN_IN_BACKGROUND);
+                    if (op != null && op.getMode() != AppOpsManager.opToDefaultMode(op.op)) {
+                        final Op copy = new Op(op.uidState, op.packageName,
+                                AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uidState.uid);
+                        copy.setMode(op.getMode());
+                        ops.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, copy);
+                        changed = true;
+                    }
+                }
+            }
+            if (changed) {
+                uidState.evalForegroundOps();
+            }
+        }
+    }
+
+    private void upgradeLocked(int oldVersion) {
+        if (oldVersion >= CURRENT_VERSION) {
+            return;
+        }
+        Slog.d(TAG, "Upgrading app-ops xml from version " + oldVersion + " to " + CURRENT_VERSION);
+        switch (oldVersion) {
+            case NO_VERSION:
+                upgradeRunAnyInBackgroundLocked();
+                // fall through
+            case 1:
+                // for future upgrades
+        }
+        scheduleFastWriteLocked();
+    }
+
+    private void readUidOps(TypedXmlPullParser parser) throws NumberFormatException,
+            XmlPullParserException, IOException {
+        final int uid = parser.getAttributeInt(null, "n");
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("op")) {
+                final int code = parser.getAttributeInt(null, "n");
+                final int mode = parser.getAttributeInt(null, "m");
+                setUidMode(code, uid, mode, null);
+            } else {
+                Slog.w(TAG, "Unknown element under <uid-ops>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    private void readPackage(TypedXmlPullParser parser)
+            throws NumberFormatException, XmlPullParserException, IOException {
+        String pkgName = parser.getAttributeValue(null, "n");
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("uid")) {
+                readUid(parser, pkgName);
+            } else {
+                Slog.w(TAG, "Unknown element under <pkg>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    private void readUid(TypedXmlPullParser parser, String pkgName)
+            throws NumberFormatException, XmlPullParserException, IOException {
+        int uid = parser.getAttributeInt(null, "n");
+        final UidState uidState = getUidStateLocked(uid, true);
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+            String tagName = parser.getName();
+            if (tagName.equals("op")) {
+                readOp(parser, uidState, pkgName);
+            } else {
+                Slog.w(TAG, "Unknown element under <pkg>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+        uidState.evalForegroundOps();
+    }
+
+    private void readAttributionOp(TypedXmlPullParser parser, @NonNull Op parent,
+            @Nullable String attribution)
+            throws NumberFormatException, IOException, XmlPullParserException {
+        final AttributedOp attributedOp = parent.getOrCreateAttribution(parent, attribution);
+
+        final long key = parser.getAttributeLong(null, "n");
+        final int uidState = extractUidStateFromKey(key);
+        final int opFlags = extractFlagsFromKey(key);
+
+        final long accessTime = parser.getAttributeLong(null, "t", 0);
+        final long rejectTime = parser.getAttributeLong(null, "r", 0);
+        final long accessDuration = parser.getAttributeLong(null, "d", -1);
+        final String proxyPkg = XmlUtils.readStringAttribute(parser, "pp");
+        final int proxyUid = parser.getAttributeInt(null, "pu", Process.INVALID_UID);
+        final String proxyAttributionTag = XmlUtils.readStringAttribute(parser, "pc");
+
+        if (accessTime > 0) {
+            attributedOp.accessed(accessTime, accessDuration, proxyUid, proxyPkg,
+                    proxyAttributionTag, uidState, opFlags);
+        }
+        if (rejectTime > 0) {
+            attributedOp.rejected(rejectTime, uidState, opFlags);
+        }
+    }
+
+    private void readOp(TypedXmlPullParser parser,
+            @NonNull UidState uidState, @NonNull String pkgName)
+            throws NumberFormatException, XmlPullParserException, IOException {
+        int opCode = parser.getAttributeInt(null, "n");
+        Op op = new Op(uidState, pkgName, opCode, uidState.uid);
+
+        final int mode = parser.getAttributeInt(null, "m", AppOpsManager.opToDefaultMode(op.op));
+        op.setMode(mode);
+
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+            String tagName = parser.getName();
+            if (tagName.equals("st")) {
+                readAttributionOp(parser, op, XmlUtils.readStringAttribute(parser, "id"));
+            } else {
+                Slog.w(TAG, "Unknown element under <op>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+
+        if (uidState.pkgOps == null) {
+            uidState.pkgOps = new ArrayMap<>();
+        }
+        Ops ops = uidState.pkgOps.get(pkgName);
+        if (ops == null) {
+            ops = new Ops(pkgName, uidState);
+            uidState.pkgOps.put(pkgName, ops);
+        }
+        ops.put(op.op, op);
+    }
+
+    @Override
+    public void writeState() {
+        synchronized (mFile) {
+            FileOutputStream stream;
+            try {
+                stream = mFile.startWrite();
+            } catch (IOException e) {
+                Slog.w(TAG, "Failed to write state: " + e);
+                return;
+            }
+
+            List<AppOpsManager.PackageOps> allOps = getPackagesForOps(null);
+
+            try {
+                TypedXmlSerializer out = Xml.resolveSerializer(stream);
+                out.startDocument(null, true);
+                out.startTag(null, "app-ops");
+                out.attributeInt(null, "v", CURRENT_VERSION);
+
+                SparseArray<SparseIntArray> uidStatesClone;
+                synchronized (this) {
+                    uidStatesClone = new SparseArray<>(mUidStates.size());
+
+                    final int uidStateCount = mUidStates.size();
+                    for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
+                        UidState uidState = mUidStates.valueAt(uidStateNum);
+                        int uid = mUidStates.keyAt(uidStateNum);
+
+                        SparseIntArray opModes = uidState.getNonDefaultUidModes();
+                        if (opModes != null && opModes.size() > 0) {
+                            uidStatesClone.put(uid, opModes);
+                        }
+                    }
+                }
+
+                final int uidStateCount = uidStatesClone.size();
+                for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
+                    SparseIntArray opModes = uidStatesClone.valueAt(uidStateNum);
+                    if (opModes != null && opModes.size() > 0) {
+                        out.startTag(null, "uid");
+                        out.attributeInt(null, "n", uidStatesClone.keyAt(uidStateNum));
+                        final int opCount = opModes.size();
+                        for (int opCountNum = 0; opCountNum < opCount; opCountNum++) {
+                            final int op = opModes.keyAt(opCountNum);
+                            final int mode = opModes.valueAt(opCountNum);
+                            out.startTag(null, "op");
+                            out.attributeInt(null, "n", op);
+                            out.attributeInt(null, "m", mode);
+                            out.endTag(null, "op");
+                        }
+                        out.endTag(null, "uid");
+                    }
+                }
+
+                if (allOps != null) {
+                    String lastPkg = null;
+                    for (int i = 0; i < allOps.size(); i++) {
+                        AppOpsManager.PackageOps pkg = allOps.get(i);
+                        if (!Objects.equals(pkg.getPackageName(), lastPkg)) {
+                            if (lastPkg != null) {
+                                out.endTag(null, "pkg");
+                            }
+                            lastPkg = pkg.getPackageName();
+                            if (lastPkg != null) {
+                                out.startTag(null, "pkg");
+                                out.attribute(null, "n", lastPkg);
+                            }
+                        }
+                        out.startTag(null, "uid");
+                        out.attributeInt(null, "n", pkg.getUid());
+                        List<AppOpsManager.OpEntry> ops = pkg.getOps();
+                        for (int j = 0; j < ops.size(); j++) {
+                            AppOpsManager.OpEntry op = ops.get(j);
+                            out.startTag(null, "op");
+                            out.attributeInt(null, "n", op.getOp());
+                            if (op.getMode() != AppOpsManager.opToDefaultMode(op.getOp())) {
+                                out.attributeInt(null, "m", op.getMode());
+                            }
+
+                            for (String attributionTag : op.getAttributedOpEntries().keySet()) {
+                                final AttributedOpEntry attribution =
+                                        op.getAttributedOpEntries().get(attributionTag);
+
+                                final ArraySet<Long> keys = attribution.collectKeys();
+
+                                final int keyCount = keys.size();
+                                for (int k = 0; k < keyCount; k++) {
+                                    final long key = keys.valueAt(k);
+
+                                    final int uidState = AppOpsManager.extractUidStateFromKey(key);
+                                    final int flags = AppOpsManager.extractFlagsFromKey(key);
+
+                                    final long accessTime = attribution.getLastAccessTime(uidState,
+                                            uidState, flags);
+                                    final long rejectTime = attribution.getLastRejectTime(uidState,
+                                            uidState, flags);
+                                    final long accessDuration = attribution.getLastDuration(
+                                            uidState, uidState, flags);
+                                    // Proxy information for rejections is not backed up
+                                    final OpEventProxyInfo proxy = attribution.getLastProxyInfo(
+                                            uidState, uidState, flags);
+
+                                    if (accessTime <= 0 && rejectTime <= 0 && accessDuration <= 0
+                                            && proxy == null) {
+                                        continue;
+                                    }
+
+                                    String proxyPkg = null;
+                                    String proxyAttributionTag = null;
+                                    int proxyUid = Process.INVALID_UID;
+                                    if (proxy != null) {
+                                        proxyPkg = proxy.getPackageName();
+                                        proxyAttributionTag = proxy.getAttributionTag();
+                                        proxyUid = proxy.getUid();
+                                    }
+
+                                    out.startTag(null, "st");
+                                    if (attributionTag != null) {
+                                        out.attribute(null, "id", attributionTag);
+                                    }
+                                    out.attributeLong(null, "n", key);
+                                    if (accessTime > 0) {
+                                        out.attributeLong(null, "t", accessTime);
+                                    }
+                                    if (rejectTime > 0) {
+                                        out.attributeLong(null, "r", rejectTime);
+                                    }
+                                    if (accessDuration > 0) {
+                                        out.attributeLong(null, "d", accessDuration);
+                                    }
+                                    if (proxyPkg != null) {
+                                        out.attribute(null, "pp", proxyPkg);
+                                    }
+                                    if (proxyAttributionTag != null) {
+                                        out.attribute(null, "pc", proxyAttributionTag);
+                                    }
+                                    if (proxyUid >= 0) {
+                                        out.attributeInt(null, "pu", proxyUid);
+                                    }
+                                    out.endTag(null, "st");
+                                }
+                            }
+
+                            out.endTag(null, "op");
+                        }
+                        out.endTag(null, "uid");
+                    }
+                    if (lastPkg != null) {
+                        out.endTag(null, "pkg");
+                    }
+                }
+
+                out.endTag(null, "app-ops");
+                out.endDocument();
+                mFile.finishWrite(stream);
+            } catch (IOException e) {
+                Slog.w(TAG, "Failed to write state, restoring backup.", e);
+                mFile.failWrite(stream);
+            }
+        }
+        mHistoricalRegistry.writeAndClearDiscreteHistory();
+    }
+
+    private void dumpHelp(PrintWriter pw) {
+        pw.println("AppOps service (appops) dump options:");
+        pw.println("  -h");
+        pw.println("    Print this help text.");
+        pw.println("  --op [OP]");
+        pw.println("    Limit output to data associated with the given app op code.");
+        pw.println("  --mode [MODE]");
+        pw.println("    Limit output to data associated with the given app op mode.");
+        pw.println("  --package [PACKAGE]");
+        pw.println("    Limit output to data associated with the given package name.");
+        pw.println("  --attributionTag [attributionTag]");
+        pw.println("    Limit output to data associated with the given attribution tag.");
+        pw.println("  --include-discrete [n]");
+        pw.println("    Include discrete ops limited to n per dimension. Use zero for no limit.");
+        pw.println("  --watchers");
+        pw.println("    Only output the watcher sections.");
+        pw.println("  --history");
+        pw.println("    Only output history.");
+        pw.println("  --uid-state-changes");
+        pw.println("    Include logs about uid state changes.");
+    }
+
+    private void dumpStatesLocked(@NonNull PrintWriter pw, @Nullable String filterAttributionTag,
+            @HistoricalOpsRequestFilter int filter, long nowElapsed, @NonNull Op op, long now,
+            @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix) {
+        final int numAttributions = op.mAttributions.size();
+        for (int i = 0; i < numAttributions; i++) {
+            if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !Objects.equals(
+                    op.mAttributions.keyAt(i), filterAttributionTag)) {
+                continue;
+            }
+
+            pw.print(prefix + op.mAttributions.keyAt(i) + "=[\n");
+            dumpStatesLocked(pw, nowElapsed, op, op.mAttributions.keyAt(i), now, sdf, date,
+                    prefix + "  ");
+            pw.print(prefix + "]\n");
+        }
+    }
+
+    private void dumpStatesLocked(@NonNull PrintWriter pw, long nowElapsed, @NonNull Op op,
+            @Nullable String attributionTag, long now, @NonNull SimpleDateFormat sdf,
+            @NonNull Date date, @NonNull String prefix) {
+
+        final AttributedOpEntry entry = op.createSingleAttributionEntryLocked(
+                attributionTag).getAttributedOpEntries().get(attributionTag);
+
+        final ArraySet<Long> keys = entry.collectKeys();
+
+        final int keyCount = keys.size();
+        for (int k = 0; k < keyCount; k++) {
+            final long key = keys.valueAt(k);
+
+            final int uidState = AppOpsManager.extractUidStateFromKey(key);
+            final int flags = AppOpsManager.extractFlagsFromKey(key);
+
+            final long accessTime = entry.getLastAccessTime(uidState, uidState, flags);
+            final long rejectTime = entry.getLastRejectTime(uidState, uidState, flags);
+            final long accessDuration = entry.getLastDuration(uidState, uidState, flags);
+            final OpEventProxyInfo proxy = entry.getLastProxyInfo(uidState, uidState, flags);
+
+            String proxyPkg = null;
+            String proxyAttributionTag = null;
+            int proxyUid = Process.INVALID_UID;
+            if (proxy != null) {
+                proxyPkg = proxy.getPackageName();
+                proxyAttributionTag = proxy.getAttributionTag();
+                proxyUid = proxy.getUid();
+            }
+
+            if (accessTime > 0) {
+                pw.print(prefix);
+                pw.print("Access: ");
+                pw.print(AppOpsManager.keyToString(key));
+                pw.print(" ");
+                date.setTime(accessTime);
+                pw.print(sdf.format(date));
+                pw.print(" (");
+                TimeUtils.formatDuration(accessTime - now, pw);
+                pw.print(")");
+                if (accessDuration > 0) {
+                    pw.print(" duration=");
+                    TimeUtils.formatDuration(accessDuration, pw);
+                }
+                if (proxyUid >= 0) {
+                    pw.print(" proxy[");
+                    pw.print("uid=");
+                    pw.print(proxyUid);
+                    pw.print(", pkg=");
+                    pw.print(proxyPkg);
+                    pw.print(", attributionTag=");
+                    pw.print(proxyAttributionTag);
+                    pw.print("]");
+                }
+                pw.println();
+            }
+
+            if (rejectTime > 0) {
+                pw.print(prefix);
+                pw.print("Reject: ");
+                pw.print(AppOpsManager.keyToString(key));
+                date.setTime(rejectTime);
+                pw.print(sdf.format(date));
+                pw.print(" (");
+                TimeUtils.formatDuration(rejectTime - now, pw);
+                pw.print(")");
+                if (proxyUid >= 0) {
+                    pw.print(" proxy[");
+                    pw.print("uid=");
+                    pw.print(proxyUid);
+                    pw.print(", pkg=");
+                    pw.print(proxyPkg);
+                    pw.print(", attributionTag=");
+                    pw.print(proxyAttributionTag);
+                    pw.print("]");
+                }
+                pw.println();
+            }
+        }
+
+        final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
+        if (attributedOp.isRunning()) {
+            long earliestElapsedTime = Long.MAX_VALUE;
+            long maxNumStarts = 0;
+            int numInProgressEvents = attributedOp.mInProgressEvents.size();
+            for (int i = 0; i < numInProgressEvents; i++) {
+                AttributedOp.InProgressStartOpEvent event =
+                        attributedOp.mInProgressEvents.valueAt(i);
+
+                earliestElapsedTime = Math.min(earliestElapsedTime, event.getStartElapsedTime());
+                maxNumStarts = Math.max(maxNumStarts, event.mNumUnfinishedStarts);
+            }
+
+            pw.print(prefix + "Running start at: ");
+            TimeUtils.formatDuration(nowElapsed - earliestElapsedTime, pw);
+            pw.println();
+
+            if (maxNumStarts > 1) {
+                pw.print(prefix + "startNesting=");
+                pw.println(maxNumStarts);
+            }
+        }
+    }
+
+    @NeverCompile // Avoid size overhead of debugging code.
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
+
+        int dumpOp = OP_NONE;
+        String dumpPackage = null;
+        String dumpAttributionTag = null;
+        int dumpUid = Process.INVALID_UID;
+        int dumpMode = -1;
+        boolean dumpWatchers = false;
+        // TODO ntmyren: Remove the dumpHistory and dumpFilter
+        boolean dumpHistory = false;
+        boolean includeDiscreteOps = false;
+        boolean dumpUidStateChangeLogs = false;
+        int nDiscreteOps = 10;
+        @HistoricalOpsRequestFilter int dumpFilter = 0;
+        boolean dumpAll = false;
+
+        if (args != null) {
+            for (int i = 0; i < args.length; i++) {
+                String arg = args[i];
+                if ("-h".equals(arg)) {
+                    dumpHelp(pw);
+                    return;
+                } else if ("-a".equals(arg)) {
+                    // dump all data
+                    dumpAll = true;
+                } else if ("--op".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        pw.println("No argument for --op option");
+                        return;
+                    }
+                    dumpOp = AppOpsService.Shell.strOpToOp(args[i], pw);
+                    dumpFilter |= FILTER_BY_OP_NAMES;
+                    if (dumpOp < 0) {
+                        return;
+                    }
+                } else if ("--package".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        pw.println("No argument for --package option");
+                        return;
+                    }
+                    dumpPackage = args[i];
+                    dumpFilter |= FILTER_BY_PACKAGE_NAME;
+                    try {
+                        dumpUid = AppGlobals.getPackageManager().getPackageUid(dumpPackage,
+                                PackageManager.MATCH_KNOWN_PACKAGES | PackageManager.MATCH_INSTANT,
+                                0);
+                    } catch (RemoteException e) {
+                    }
+                    if (dumpUid < 0) {
+                        pw.println("Unknown package: " + dumpPackage);
+                        return;
+                    }
+                    dumpUid = UserHandle.getAppId(dumpUid);
+                    dumpFilter |= FILTER_BY_UID;
+                } else if ("--attributionTag".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        pw.println("No argument for --attributionTag option");
+                        return;
+                    }
+                    dumpAttributionTag = args[i];
+                    dumpFilter |= FILTER_BY_ATTRIBUTION_TAG;
+                } else if ("--mode".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        pw.println("No argument for --mode option");
+                        return;
+                    }
+                    dumpMode = AppOpsService.Shell.strModeToMode(args[i], pw);
+                    if (dumpMode < 0) {
+                        return;
+                    }
+                } else if ("--watchers".equals(arg)) {
+                    dumpWatchers = true;
+                } else if ("--include-discrete".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        pw.println("No argument for --include-discrete option");
+                        return;
+                    }
+                    try {
+                        nDiscreteOps = Integer.valueOf(args[i]);
+                    } catch (NumberFormatException e) {
+                        pw.println("Wrong parameter: " + args[i]);
+                        return;
+                    }
+                    includeDiscreteOps = true;
+                } else if ("--history".equals(arg)) {
+                    dumpHistory = true;
+                } else if (arg.length() > 0 && arg.charAt(0) == '-') {
+                    pw.println("Unknown option: " + arg);
+                    return;
+                } else if ("--uid-state-changes".equals(arg)) {
+                    dumpUidStateChangeLogs = true;
+                } else {
+                    pw.println("Unknown command: " + arg);
+                    return;
+                }
+            }
+        }
+
+        final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+        final Date date = new Date();
+        synchronized (this) {
+            pw.println("Current AppOps Service state:");
+            if (!dumpHistory && !dumpWatchers) {
+                mConstants.dump(pw);
+            }
+            pw.println();
+            final long now = System.currentTimeMillis();
+            final long nowElapsed = SystemClock.elapsedRealtime();
+            boolean needSep = false;
+            if (dumpFilter == 0 && dumpMode < 0 && mProfileOwners != null && !dumpWatchers
+                    && !dumpHistory) {
+                pw.println("  Profile owners:");
+                for (int poi = 0; poi < mProfileOwners.size(); poi++) {
+                    pw.print("    User #");
+                    pw.print(mProfileOwners.keyAt(poi));
+                    pw.print(": ");
+                    UserHandle.formatUid(pw, mProfileOwners.valueAt(poi));
+                    pw.println();
+                }
+                pw.println();
+            }
+
+            if (!dumpHistory) {
+                needSep |= mAppOpsServiceInterface.dumpListeners(dumpOp, dumpUid, dumpPackage, pw);
+            }
+
+            if (mModeWatchers.size() > 0 && dumpOp < 0 && !dumpHistory) {
+                boolean printedHeader = false;
+                for (int i = 0; i < mModeWatchers.size(); i++) {
+                    final ModeCallback cb = mModeWatchers.valueAt(i);
+                    if (dumpPackage != null
+                            && dumpUid != UserHandle.getAppId(cb.getWatchingUid())) {
+                        continue;
+                    }
+                    needSep = true;
+                    if (!printedHeader) {
+                        pw.println("  All op mode watchers:");
+                        printedHeader = true;
+                    }
+                    pw.print("    ");
+                    pw.print(Integer.toHexString(System.identityHashCode(mModeWatchers.keyAt(i))));
+                    pw.print(": ");
+                    pw.println(cb);
+                }
+            }
+            if (mActiveWatchers.size() > 0 && dumpMode < 0) {
+                needSep = true;
+                boolean printedHeader = false;
+                for (int watcherNum = 0; watcherNum < mActiveWatchers.size(); watcherNum++) {
+                    final SparseArray<ActiveCallback> activeWatchers =
+                            mActiveWatchers.valueAt(watcherNum);
+                    if (activeWatchers.size() <= 0) {
+                        continue;
+                    }
+                    final ActiveCallback cb = activeWatchers.valueAt(0);
+                    if (dumpOp >= 0 && activeWatchers.indexOfKey(dumpOp) < 0) {
+                        continue;
+                    }
+                    if (dumpPackage != null
+                            && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
+                        continue;
+                    }
+                    if (!printedHeader) {
+                        pw.println("  All op active watchers:");
+                        printedHeader = true;
+                    }
+                    pw.print("    ");
+                    pw.print(Integer.toHexString(System.identityHashCode(
+                            mActiveWatchers.keyAt(watcherNum))));
+                    pw.println(" ->");
+                    pw.print("        [");
+                    final int opCount = activeWatchers.size();
+                    for (int opNum = 0; opNum < opCount; opNum++) {
+                        if (opNum > 0) {
+                            pw.print(' ');
+                        }
+                        pw.print(AppOpsManager.opToName(activeWatchers.keyAt(opNum)));
+                        if (opNum < opCount - 1) {
+                            pw.print(',');
+                        }
+                    }
+                    pw.println("]");
+                    pw.print("        ");
+                    pw.println(cb);
+                }
+            }
+            if (mStartedWatchers.size() > 0 && dumpMode < 0) {
+                needSep = true;
+                boolean printedHeader = false;
+
+                final int watchersSize = mStartedWatchers.size();
+                for (int watcherNum = 0; watcherNum < watchersSize; watcherNum++) {
+                    final SparseArray<StartedCallback> startedWatchers =
+                            mStartedWatchers.valueAt(watcherNum);
+                    if (startedWatchers.size() <= 0) {
+                        continue;
+                    }
+
+                    final StartedCallback cb = startedWatchers.valueAt(0);
+                    if (dumpOp >= 0 && startedWatchers.indexOfKey(dumpOp) < 0) {
+                        continue;
+                    }
+
+                    if (dumpPackage != null
+                            && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
+                        continue;
+                    }
+
+                    if (!printedHeader) {
+                        pw.println("  All op started watchers:");
+                        printedHeader = true;
+                    }
+
+                    pw.print("    ");
+                    pw.print(Integer.toHexString(System.identityHashCode(
+                            mStartedWatchers.keyAt(watcherNum))));
+                    pw.println(" ->");
+
+                    pw.print("        [");
+                    final int opCount = startedWatchers.size();
+                    for (int opNum = 0; opNum < opCount; opNum++) {
+                        if (opNum > 0) {
+                            pw.print(' ');
+                        }
+
+                        pw.print(AppOpsManager.opToName(startedWatchers.keyAt(opNum)));
+                        if (opNum < opCount - 1) {
+                            pw.print(',');
+                        }
+                    }
+                    pw.println("]");
+
+                    pw.print("        ");
+                    pw.println(cb);
+                }
+            }
+            if (mNotedWatchers.size() > 0 && dumpMode < 0) {
+                needSep = true;
+                boolean printedHeader = false;
+                for (int watcherNum = 0; watcherNum < mNotedWatchers.size(); watcherNum++) {
+                    final SparseArray<NotedCallback> notedWatchers =
+                            mNotedWatchers.valueAt(watcherNum);
+                    if (notedWatchers.size() <= 0) {
+                        continue;
+                    }
+                    final NotedCallback cb = notedWatchers.valueAt(0);
+                    if (dumpOp >= 0 && notedWatchers.indexOfKey(dumpOp) < 0) {
+                        continue;
+                    }
+                    if (dumpPackage != null
+                            && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
+                        continue;
+                    }
+                    if (!printedHeader) {
+                        pw.println("  All op noted watchers:");
+                        printedHeader = true;
+                    }
+                    pw.print("    ");
+                    pw.print(Integer.toHexString(System.identityHashCode(
+                            mNotedWatchers.keyAt(watcherNum))));
+                    pw.println(" ->");
+                    pw.print("        [");
+                    final int opCount = notedWatchers.size();
+                    for (int opNum = 0; opNum < opCount; opNum++) {
+                        if (opNum > 0) {
+                            pw.print(' ');
+                        }
+                        pw.print(AppOpsManager.opToName(notedWatchers.keyAt(opNum)));
+                        if (opNum < opCount - 1) {
+                            pw.print(',');
+                        }
+                    }
+                    pw.println("]");
+                    pw.print("        ");
+                    pw.println(cb);
+                }
+            }
+            if (needSep) {
+                pw.println();
+            }
+            for (int i = 0; i < mUidStates.size(); i++) {
+                UidState uidState = mUidStates.valueAt(i);
+                final SparseIntArray opModes = uidState.getNonDefaultUidModes();
+                final ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
+
+                if (dumpWatchers || dumpHistory) {
+                    continue;
+                }
+                if (dumpOp >= 0 || dumpPackage != null || dumpMode >= 0) {
+                    boolean hasOp = dumpOp < 0 || (opModes != null
+                            && opModes.indexOfKey(dumpOp) >= 0);
+                    boolean hasPackage = dumpPackage == null || dumpUid == mUidStates.keyAt(i);
+                    boolean hasMode = dumpMode < 0;
+                    if (!hasMode && opModes != null) {
+                        for (int opi = 0; !hasMode && opi < opModes.size(); opi++) {
+                            if (opModes.valueAt(opi) == dumpMode) {
+                                hasMode = true;
+                            }
+                        }
+                    }
+                    if (pkgOps != null) {
+                        for (int pkgi = 0;
+                                (!hasOp || !hasPackage || !hasMode) && pkgi < pkgOps.size();
+                                pkgi++) {
+                            Ops ops = pkgOps.valueAt(pkgi);
+                            if (!hasOp && ops != null && ops.indexOfKey(dumpOp) >= 0) {
+                                hasOp = true;
+                            }
+                            if (!hasMode) {
+                                for (int opi = 0; !hasMode && opi < ops.size(); opi++) {
+                                    if (ops.valueAt(opi).getMode() == dumpMode) {
+                                        hasMode = true;
+                                    }
+                                }
+                            }
+                            if (!hasPackage && dumpPackage.equals(ops.packageName)) {
+                                hasPackage = true;
+                            }
+                        }
+                    }
+                    if (uidState.foregroundOps != null && !hasOp) {
+                        if (uidState.foregroundOps.indexOfKey(dumpOp) > 0) {
+                            hasOp = true;
+                        }
+                    }
+                    if (!hasOp || !hasPackage || !hasMode) {
+                        continue;
+                    }
+                }
+
+                pw.print("  Uid ");
+                UserHandle.formatUid(pw, uidState.uid);
+                pw.println(":");
+                uidState.dump(pw, nowElapsed);
+                if (uidState.foregroundOps != null && (dumpMode < 0
+                        || dumpMode == AppOpsManager.MODE_FOREGROUND)) {
+                    pw.println("    foregroundOps:");
+                    for (int j = 0; j < uidState.foregroundOps.size(); j++) {
+                        if (dumpOp >= 0 && dumpOp != uidState.foregroundOps.keyAt(j)) {
+                            continue;
+                        }
+                        pw.print("      ");
+                        pw.print(AppOpsManager.opToName(uidState.foregroundOps.keyAt(j)));
+                        pw.print(": ");
+                        pw.println(uidState.foregroundOps.valueAt(j) ? "WATCHER" : "SILENT");
+                    }
+                    pw.print("    hasForegroundWatchers=");
+                    pw.println(uidState.hasForegroundWatchers);
+                }
+                needSep = true;
+
+                if (opModes != null) {
+                    final int opModeCount = opModes.size();
+                    for (int j = 0; j < opModeCount; j++) {
+                        final int code = opModes.keyAt(j);
+                        final int mode = opModes.valueAt(j);
+                        if (dumpOp >= 0 && dumpOp != code) {
+                            continue;
+                        }
+                        if (dumpMode >= 0 && dumpMode != mode) {
+                            continue;
+                        }
+                        pw.print("      ");
+                        pw.print(AppOpsManager.opToName(code));
+                        pw.print(": mode=");
+                        pw.println(AppOpsManager.modeToName(mode));
+                    }
+                }
+
+                if (pkgOps == null) {
+                    continue;
+                }
+
+                for (int pkgi = 0; pkgi < pkgOps.size(); pkgi++) {
+                    final Ops ops = pkgOps.valueAt(pkgi);
+                    if (dumpPackage != null && !dumpPackage.equals(ops.packageName)) {
+                        continue;
+                    }
+                    boolean printedPackage = false;
+                    for (int j = 0; j < ops.size(); j++) {
+                        final Op op = ops.valueAt(j);
+                        final int opCode = op.op;
+                        if (dumpOp >= 0 && dumpOp != opCode) {
+                            continue;
+                        }
+                        if (dumpMode >= 0 && dumpMode != op.getMode()) {
+                            continue;
+                        }
+                        if (!printedPackage) {
+                            pw.print("    Package ");
+                            pw.print(ops.packageName);
+                            pw.println(":");
+                            printedPackage = true;
+                        }
+                        pw.print("      ");
+                        pw.print(AppOpsManager.opToName(opCode));
+                        pw.print(" (");
+                        pw.print(AppOpsManager.modeToName(op.getMode()));
+                        final int switchOp = AppOpsManager.opToSwitch(opCode);
+                        if (switchOp != opCode) {
+                            pw.print(" / switch ");
+                            pw.print(AppOpsManager.opToName(switchOp));
+                            final Op switchObj = ops.get(switchOp);
+                            int mode = switchObj == null
+                                    ? AppOpsManager.opToDefaultMode(switchOp) : switchObj.getMode();
+                            pw.print("=");
+                            pw.print(AppOpsManager.modeToName(mode));
+                        }
+                        pw.println("): ");
+                        dumpStatesLocked(pw, dumpAttributionTag, dumpFilter, nowElapsed, op, now,
+                                sdf, date, "        ");
+                    }
+                }
+            }
+            if (needSep) {
+                pw.println();
+            }
+
+            boolean showUserRestrictions = !(dumpMode < 0 && !dumpWatchers && !dumpHistory);
+            mAppOpsRestrictions.dumpRestrictions(pw, dumpOp, dumpPackage, showUserRestrictions);
+
+            if (dumpAll || dumpUidStateChangeLogs) {
+                pw.println();
+                pw.println("Uid State Changes Event Log:");
+                getUidStateTracker().dumpEvents(pw);
+            }
+        }
+
+        // Must not hold the appops lock
+        if (dumpHistory && !dumpWatchers) {
+            mHistoricalRegistry.dump("  ", pw, dumpUid, dumpPackage, dumpAttributionTag, dumpOp,
+                    dumpFilter);
+        }
+        if (includeDiscreteOps) {
+            pw.println("Discrete accesses: ");
+            mHistoricalRegistry.dumpDiscreteData(pw, dumpUid, dumpPackage, dumpAttributionTag,
+                    dumpFilter, dumpOp, sdf, date, "  ", nDiscreteOps);
+        }
+    }
+
+    @Override
+    public void setUserRestrictions(Bundle restrictions, IBinder token, int userHandle) {
+        checkSystemUid("setUserRestrictions");
+        Objects.requireNonNull(restrictions);
+        Objects.requireNonNull(token);
+        for (int i = 0; i < AppOpsManager._NUM_OP; i++) {
+            String restriction = AppOpsManager.opToRestriction(i);
+            if (restriction != null) {
+                setUserRestrictionNoCheck(i, restrictions.getBoolean(restriction, false), token,
+                        userHandle, null);
+            }
+        }
+    }
+
+    @Override
+    public void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle,
+            PackageTagsList excludedPackageTags) {
+        if (Binder.getCallingPid() != Process.myPid()) {
+            mContext.enforcePermission(Manifest.permission.MANAGE_APP_OPS_RESTRICTIONS,
+                    Binder.getCallingPid(), Binder.getCallingUid(), null);
+        }
+        if (userHandle != UserHandle.getCallingUserId()) {
+            if (mContext.checkCallingOrSelfPermission(Manifest.permission
+                    .INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED
+                    && mContext.checkCallingOrSelfPermission(Manifest.permission
+                    .INTERACT_ACROSS_USERS) != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("Need INTERACT_ACROSS_USERS_FULL or"
+                        + " INTERACT_ACROSS_USERS to interact cross user ");
+            }
+        }
+        verifyIncomingOp(code);
+        Objects.requireNonNull(token);
+        setUserRestrictionNoCheck(code, restricted, token, userHandle, excludedPackageTags);
+    }
+
+    private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token,
+            int userHandle, PackageTagsList excludedPackageTags) {
+        synchronized (AppOpsServiceImpl.this) {
+            ClientUserRestrictionState restrictionState = mOpUserRestrictions.get(token);
+
+            if (restrictionState == null) {
+                try {
+                    restrictionState = new ClientUserRestrictionState(token);
+                } catch (RemoteException e) {
+                    return;
+                }
+                mOpUserRestrictions.put(token, restrictionState);
+            }
+
+            if (restrictionState.setRestriction(code, restricted, excludedPackageTags,
+                    userHandle)) {
+                mHandler.sendMessage(PooledLambda.obtainMessage(
+                        AppOpsServiceImpl::notifyWatchersOfChange, this, code, UID_ANY));
+                mHandler.sendMessage(PooledLambda.obtainMessage(
+                        AppOpsServiceImpl::updateStartedOpModeForUser, this, code,
+                        restricted, userHandle));
+            }
+
+            if (restrictionState.isDefault()) {
+                mOpUserRestrictions.remove(token);
+                restrictionState.destroy();
+            }
+        }
+    }
+
+    @Override
+    public void setGlobalRestriction(int code, boolean restricted, IBinder token) {
+        if (Binder.getCallingPid() != Process.myPid()) {
+            throw new SecurityException("Only the system can set global restrictions");
+        }
+
+        synchronized (this) {
+            ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.get(token);
+
+            if (restrictionState == null) {
+                try {
+                    restrictionState = new ClientGlobalRestrictionState(token);
+                } catch (RemoteException e) {
+                    return;
+                }
+                mOpGlobalRestrictions.put(token, restrictionState);
+            }
+
+            if (restrictionState.setRestriction(code, restricted)) {
+                mHandler.sendMessage(PooledLambda.obtainMessage(
+                        AppOpsServiceImpl::notifyWatchersOfChange, this, code, UID_ANY));
+                mHandler.sendMessage(PooledLambda.obtainMessage(
+                        AppOpsServiceImpl::updateStartedOpModeForUser, this, code,
+                        restricted, UserHandle.USER_ALL));
+            }
+
+            if (restrictionState.isDefault()) {
+                mOpGlobalRestrictions.remove(token);
+                restrictionState.destroy();
+            }
+        }
+    }
+
+    @Override
+    public int getOpRestrictionCount(int code, UserHandle user, String pkg,
+            String attributionTag) {
+        int number = 0;
+        synchronized (this) {
+            int numRestrictions = mOpUserRestrictions.size();
+            for (int i = 0; i < numRestrictions; i++) {
+                if (mOpUserRestrictions.valueAt(i)
+                        .hasRestriction(code, pkg, attributionTag, user.getIdentifier(),
+                                false)) {
+                    number++;
+                }
+            }
+
+            numRestrictions = mOpGlobalRestrictions.size();
+            for (int i = 0; i < numRestrictions; i++) {
+                if (mOpGlobalRestrictions.valueAt(i).hasRestriction(code)) {
+                    number++;
+                }
+            }
+        }
+
+        return number;
+    }
+
+    private void updateStartedOpModeForUser(int code, boolean restricted, int userId) {
+        synchronized (AppOpsServiceImpl.this) {
+            int numUids = mUidStates.size();
+            for (int uidNum = 0; uidNum < numUids; uidNum++) {
+                int uid = mUidStates.keyAt(uidNum);
+                if (userId != UserHandle.USER_ALL && UserHandle.getUserId(uid) != userId) {
+                    continue;
+                }
+                updateStartedOpModeForUidLocked(code, restricted, uid);
+            }
+        }
+    }
+
+    private void updateStartedOpModeForUidLocked(int code, boolean restricted, int uid) {
+        UidState uidState = mUidStates.get(uid);
+        if (uidState == null || uidState.pkgOps == null) {
+            return;
+        }
+
+        int numPkgOps = uidState.pkgOps.size();
+        for (int pkgNum = 0; pkgNum < numPkgOps; pkgNum++) {
+            Ops ops = uidState.pkgOps.valueAt(pkgNum);
+            Op op = ops != null ? ops.get(code) : null;
+            if (op == null || (op.getMode() != MODE_ALLOWED && op.getMode() != MODE_FOREGROUND)) {
+                continue;
+            }
+            int numAttrTags = op.mAttributions.size();
+            for (int attrNum = 0; attrNum < numAttrTags; attrNum++) {
+                AttributedOp attrOp = op.mAttributions.valueAt(attrNum);
+                if (restricted && attrOp.isRunning()) {
+                    attrOp.pause();
+                } else if (attrOp.isPaused()) {
+                    attrOp.resume();
+                }
+            }
+        }
+    }
+
+    @Override
+    public void notifyWatchersOfChange(int code, int uid) {
+        final ArraySet<OnOpModeChangedListener> modeChangedListenerSet;
+        synchronized (this) {
+            modeChangedListenerSet = mAppOpsServiceInterface.getOpModeChangedListeners(code);
+            if (modeChangedListenerSet == null) {
+                return;
+            }
+        }
+
+        notifyOpChanged(modeChangedListenerSet, code, uid, null);
+    }
+
+    @Override
+    public void removeUser(int userHandle) throws RemoteException {
+        checkSystemUid("removeUser");
+        synchronized (AppOpsServiceImpl.this) {
+            final int tokenCount = mOpUserRestrictions.size();
+            for (int i = tokenCount - 1; i >= 0; i--) {
+                ClientUserRestrictionState opRestrictions = mOpUserRestrictions.valueAt(i);
+                opRestrictions.removeUser(userHandle);
+            }
+            removeUidsForUserLocked(userHandle);
+        }
+    }
+
+    @Override
+    public boolean isOperationActive(int code, int uid, String packageName) {
+        if (Binder.getCallingUid() != uid) {
+            if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
+                    != PackageManager.PERMISSION_GRANTED) {
+                return false;
+            }
+        }
+        verifyIncomingOp(code);
+        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+            return false;
+        }
+
+        final String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+        if (resolvedPackageName == null) {
+            return false;
+        }
+        // TODO moltmann: Allow to check for attribution op activeness
+        synchronized (AppOpsServiceImpl.this) {
+            Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null, false);
+            if (pkgOps == null) {
+                return false;
+            }
+
+            Op op = pkgOps.get(code);
+            if (op == null) {
+                return false;
+            }
+
+            return op.isRunning();
+        }
+    }
+
+    @Override
+    public boolean isProxying(int op, @NonNull String proxyPackageName,
+            @NonNull String proxyAttributionTag, int proxiedUid,
+            @NonNull String proxiedPackageName) {
+        Objects.requireNonNull(proxyPackageName);
+        Objects.requireNonNull(proxiedPackageName);
+        final long callingUid = Binder.getCallingUid();
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final List<AppOpsManager.PackageOps> packageOps = getOpsForPackage(proxiedUid,
+                    proxiedPackageName, new int[]{op});
+            if (packageOps == null || packageOps.isEmpty()) {
+                return false;
+            }
+            final List<OpEntry> opEntries = packageOps.get(0).getOps();
+            if (opEntries.isEmpty()) {
+                return false;
+            }
+            final OpEntry opEntry = opEntries.get(0);
+            if (!opEntry.isRunning()) {
+                return false;
+            }
+            final OpEventProxyInfo proxyInfo = opEntry.getLastProxyInfo(
+                    OP_FLAG_TRUSTED_PROXIED | AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED);
+            return proxyInfo != null && callingUid == proxyInfo.getUid()
+                    && proxyPackageName.equals(proxyInfo.getPackageName())
+                    && Objects.equals(proxyAttributionTag, proxyInfo.getAttributionTag());
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public void resetPackageOpsNoHistory(@NonNull String packageName) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "resetPackageOpsNoHistory");
+        synchronized (AppOpsServiceImpl.this) {
+            final int uid = mPackageManagerInternal.getPackageUid(packageName, 0,
+                    UserHandle.getCallingUserId());
+            if (uid == Process.INVALID_UID) {
+                return;
+            }
+            UidState uidState = mUidStates.get(uid);
+            if (uidState == null || uidState.pkgOps == null) {
+                return;
+            }
+            Ops removedOps = uidState.pkgOps.remove(packageName);
+            mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
+            if (removedOps != null) {
+                scheduleFastWriteLocked();
+            }
+        }
+    }
+
+    @Override
+    public void setHistoryParameters(@AppOpsManager.HistoricalMode int mode,
+            long baseSnapshotInterval, int compressionStep) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "setHistoryParameters");
+        // Must not hold the appops lock
+        mHistoricalRegistry.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
+    }
+
+    @Override
+    public void offsetHistory(long offsetMillis) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "offsetHistory");
+        // Must not hold the appops lock
+        mHistoricalRegistry.offsetHistory(offsetMillis);
+        mHistoricalRegistry.offsetDiscreteHistory(offsetMillis);
+    }
+
+    @Override
+    public void addHistoricalOps(HistoricalOps ops) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "addHistoricalOps");
+        // Must not hold the appops lock
+        mHistoricalRegistry.addHistoricalOps(ops);
+    }
+
+    @Override
+    public void resetHistoryParameters() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "resetHistoryParameters");
+        // Must not hold the appops lock
+        mHistoricalRegistry.resetHistoryParameters();
+    }
+
+    @Override
+    public void clearHistory() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "clearHistory");
+        // Must not hold the appops lock
+        mHistoricalRegistry.clearAllHistory();
+    }
+
+    @Override
+    public void rebootHistory(long offlineDurationMillis) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "rebootHistory");
+
+        Preconditions.checkArgument(offlineDurationMillis >= 0);
+
+        // Must not hold the appops lock
+        mHistoricalRegistry.shutdown();
+
+        if (offlineDurationMillis > 0) {
+            SystemClock.sleep(offlineDurationMillis);
+        }
+
+        mHistoricalRegistry = new HistoricalRegistry(mHistoricalRegistry);
+        mHistoricalRegistry.systemReady(mContext.getContentResolver());
+        mHistoricalRegistry.persistPendingHistory();
+    }
+
+    @GuardedBy("this")
+    private void removeUidsForUserLocked(int userHandle) {
+        for (int i = mUidStates.size() - 1; i >= 0; --i) {
+            final int uid = mUidStates.keyAt(i);
+            if (UserHandle.getUserId(uid) == userHandle) {
+                mUidStates.valueAt(i).clear();
+                mUidStates.removeAt(i);
+            }
+        }
+    }
+
+    private void checkSystemUid(String function) {
+        int uid = Binder.getCallingUid();
+        if (uid != Process.SYSTEM_UID) {
+            throw new SecurityException(function + " must by called by the system");
+        }
+    }
+
+    private static int resolveUid(String packageName) {
+        if (packageName == null) {
+            return Process.INVALID_UID;
+        }
+        switch (packageName) {
+            case "root":
+                return Process.ROOT_UID;
+            case "shell":
+            case "dumpstate":
+                return Process.SHELL_UID;
+            case "media":
+                return Process.MEDIA_UID;
+            case "audioserver":
+                return Process.AUDIOSERVER_UID;
+            case "cameraserver":
+                return Process.CAMERASERVER_UID;
+        }
+        return Process.INVALID_UID;
+    }
+
+    private static String[] getPackagesForUid(int uid) {
+        String[] packageNames = null;
+
+        // Very early during boot the package manager is not yet or not yet fully started. At this
+        // time there are no packages yet.
+        if (AppGlobals.getPackageManager() != null) {
+            try {
+                packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid);
+            } catch (RemoteException e) {
+                /* ignore - local call */
+            }
+        }
+        if (packageNames == null) {
+            return EmptyArray.STRING;
+        }
+        return packageNames;
+    }
+
+    private final class ClientUserRestrictionState implements DeathRecipient {
+        private final IBinder mToken;
+
+        ClientUserRestrictionState(IBinder token)
+                throws RemoteException {
+            token.linkToDeath(this, 0);
+            this.mToken = token;
+        }
+
+        public boolean setRestriction(int code, boolean restricted,
+                PackageTagsList excludedPackageTags, int userId) {
+            return mAppOpsRestrictions.setUserRestriction(mToken, userId, code,
+                    restricted, excludedPackageTags);
+        }
+
+        public boolean hasRestriction(int code, String packageName, String attributionTag,
+                int userId, boolean isCheckOp) {
+            return mAppOpsRestrictions.getUserRestriction(mToken, userId, code, packageName,
+                    attributionTag, isCheckOp);
+        }
+
+        public void removeUser(int userId) {
+            mAppOpsRestrictions.clearUserRestrictions(mToken, userId);
+        }
+
+        public boolean isDefault() {
+            return !mAppOpsRestrictions.hasUserRestrictions(mToken);
+        }
+
+        @Override
+        public void binderDied() {
+            synchronized (AppOpsServiceImpl.this) {
+                mAppOpsRestrictions.clearUserRestrictions(mToken);
+                mOpUserRestrictions.remove(mToken);
+                destroy();
+            }
+        }
+
+        public void destroy() {
+            mToken.unlinkToDeath(this, 0);
+        }
+    }
+
+    private final class ClientGlobalRestrictionState implements DeathRecipient {
+        final IBinder mToken;
+
+        ClientGlobalRestrictionState(IBinder token)
+                throws RemoteException {
+            token.linkToDeath(this, 0);
+            this.mToken = token;
+        }
+
+        boolean setRestriction(int code, boolean restricted) {
+            return mAppOpsRestrictions.setGlobalRestriction(mToken, code, restricted);
+        }
+
+        boolean hasRestriction(int code) {
+            return mAppOpsRestrictions.getGlobalRestriction(mToken, code);
+        }
+
+        boolean isDefault() {
+            return !mAppOpsRestrictions.hasGlobalRestrictions(mToken);
+        }
+
+        @Override
+        public void binderDied() {
+            mAppOpsRestrictions.clearGlobalRestrictions(mToken);
+            mOpGlobalRestrictions.remove(mToken);
+            destroy();
+        }
+
+        void destroy() {
+            mToken.unlinkToDeath(this, 0);
+        }
+    }
+
+    @Override
+    public void setDeviceAndProfileOwners(SparseIntArray owners) {
+        synchronized (this) {
+            mProfileOwners = owners;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
index 18f659e..8420fcb 100644
--- a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
+++ b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
@@ -13,197 +13,482 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package com.android.server.appop;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.app.AppOpsManager.Mode;
-import android.util.ArraySet;
-import android.util.SparseBooleanArray;
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.content.AttributionSource;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.PackageTagsList;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.SparseArray;
 import android.util.SparseIntArray;
 
+import com.android.internal.app.IAppOpsActiveCallback;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsNotedCallback;
+import com.android.internal.app.IAppOpsStartedCallback;
+
+import dalvik.annotation.optimization.NeverCompile;
+
+import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.List;
 
 /**
- * Interface for accessing and modifying modes for app-ops i.e. package and uid modes.
- * This interface also includes functions for added and removing op mode watchers.
- * In the future this interface will also include op restrictions.
+ *
  */
-public interface AppOpsServiceInterface {
-    /**
-     * Returns a copy of non-default app-ops with op as keys and their modes as values for a uid.
-     * Returns an empty SparseIntArray if nothing is set.
-     * @param uid for which we need the app-ops and their modes.
-     */
-    SparseIntArray getNonDefaultUidModes(int uid);
+public interface AppOpsServiceInterface extends PersistenceScheduler {
 
     /**
-     * Returns the app-op mode for a particular app-op of a uid.
-     * Returns default op mode if the op mode for particular uid and op is not set.
-     * @param uid user id for which we need the mode.
-     * @param op app-op for which we need the mode.
-     * @return mode of the app-op.
-     */
-    int getUidMode(int uid, int op);
-
-    /**
-     * Set the app-op mode for a particular uid and op.
-     * The mode is not set if the mode is the same as the default mode for the op.
-     * @param uid user id for which we want to set the mode.
-     * @param op app-op for which we want to set the mode.
-     * @param mode mode for the app-op.
-     * @return true if op mode is changed.
-     */
-    boolean setUidMode(int uid, int op, @Mode int mode);
-
-    /**
-     * Gets the app-op mode for a particular package.
-     * Returns default op mode if the op mode for the particular package is not set.
-     * @param packageName package name for which we need the op mode.
-     * @param op app-op for which we need the mode.
-     * @param userId user id associated with the package.
-     * @return the mode of the app-op.
-     */
-    int getPackageMode(@NonNull String packageName, int op, @UserIdInt int userId);
-
-    /**
-     * Sets the app-op mode for a particular package.
-     * @param packageName package name for which we need to set the op mode.
-     * @param op app-op for which we need to set the mode.
-     * @param mode the mode of the app-op.
-     * @param userId user id associated with the package.
      *
      */
-    void setPackageMode(@NonNull String packageName, int op, @Mode int mode, @UserIdInt int userId);
+    void systemReady();
 
     /**
-     * Stop tracking any app-op modes for a package.
-     * @param packageName Name of the package for which we want to remove all mode tracking.
-     * @param userId user id associated with the package.
+     *
      */
-    boolean removePackage(@NonNull String packageName,  @UserIdInt int userId);
+    void shutdown();
 
     /**
-     * Stop tracking any app-op modes for this uid.
-     * @param uid user id for which we want to remove all tracking.
+     *
+     * @param uid
+     * @param packageName
      */
-    void removeUid(int uid);
+    void verifyPackage(int uid, String packageName);
 
     /**
-     * Returns true if all uid modes for this uid are
-     * in default state.
-     * @param uid user id
+     *
+     * @param op
+     * @param packageName
+     * @param flags
+     * @param callback
      */
-    boolean areUidModesDefault(int uid);
+    void startWatchingModeWithFlags(int op, String packageName, int flags,
+            IAppOpsCallback callback);
 
     /**
-     * Returns true if all package modes for this package name are
-     * in default state.
-     * @param packageName package name.
-     * @param userId user id associated with the package.
+     *
+     * @param callback
      */
-    boolean arePackageModesDefault(String packageName, @UserIdInt int userId);
+    void stopWatchingMode(IAppOpsCallback callback);
 
     /**
-     * Stop tracking app-op modes for all uid and packages.
+     *
+     * @param ops
+     * @param callback
      */
-    void clearAllModes();
+    void startWatchingActive(int[] ops, IAppOpsActiveCallback callback);
 
     /**
-     * Registers changedListener to listen to op's mode change.
-     * @param changedListener the listener that must be trigger on the op's mode change.
-     * @param op op representing the app-op whose mode change needs to be listened to.
+     *
+     * @param callback
      */
-    void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener, int op);
+    void stopWatchingActive(IAppOpsActiveCallback callback);
 
     /**
-     * Registers changedListener to listen to package's app-op's mode change.
-     * @param changedListener the listener that must be trigger on the mode change.
-     * @param packageName of the package whose app-op's mode change needs to be listened to.
+     *
+     * @param ops
+     * @param callback
      */
-    void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener,
-            @NonNull String packageName);
+    void startWatchingStarted(int[] ops, @NonNull IAppOpsStartedCallback callback);
 
     /**
-     * Stop the changedListener from triggering on any mode change.
-     * @param changedListener the listener that needs to be removed.
+     *
+     * @param callback
      */
-    void removeListener(@NonNull OnOpModeChangedListener changedListener);
+    void stopWatchingStarted(IAppOpsStartedCallback callback);
 
     /**
-     * Temporary API which will be removed once we can safely untangle the methods that use this.
-     * Returns a set of OnOpModeChangedListener that are listening for op's mode changes.
-     * @param op app-op whose mode change is being listened to.
+     *
+     * @param ops
+     * @param callback
      */
-    ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op);
+    void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback);
 
     /**
-     * Temporary API which will be removed once we can safely untangle the methods that use this.
-     * Returns a set of OnOpModeChangedListener that are listening for package's op's mode changes.
-     * @param packageName of package whose app-op's mode change is being listened to.
+     *
+     * @param callback
      */
-    ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(@NonNull String packageName);
+    void stopWatchingNoted(IAppOpsNotedCallback callback);
 
     /**
-     * Temporary API which will be removed once we can safely untangle the methods that use this.
-     * Notify that the app-op's mode is changed by triggering the change listener.
-     * @param op App-op whose mode has changed
-     * @param uid user id associated with the app-op (or, if UID_ANY, notifies all users)
+     * @param clientId
+     * @param code
+     * @param uid
+     * @param packageName
+     * @param attributionTag
+     * @param startIfModeDefault
+     * @param message
+     * @param attributionFlags
+     * @param attributionChainId
+     * @return
      */
-    void notifyWatchersOfChange(int op, int uid);
+    int startOperation(@NonNull IBinder clientId, int code, int uid,
+            @Nullable String packageName, @Nullable String attributionTag,
+            boolean startIfModeDefault, @NonNull String message,
+            @AppOpsManager.AttributionFlags int attributionFlags,
+            int attributionChainId);
+
+
+    int startOperationUnchecked(IBinder clientId, int code, int uid, @NonNull String packageName,
+            @Nullable String attributionTag, int proxyUid, String proxyPackageName,
+            @Nullable String proxyAttributionTag, @AppOpsManager.OpFlags int flags,
+            boolean startIfModeDefault, @AppOpsManager.AttributionFlags int attributionFlags,
+            int attributionChainId, boolean dryRun);
 
     /**
-     * Temporary API which will be removed once we can safely untangle the methods that use this.
-     * Notify that the app-op's mode is changed by triggering the change listener.
-     * @param changedListener the change listener.
-     * @param op App-op whose mode has changed
-     * @param uid user id associated with the app-op
-     * @param packageName package name that is associated with the app-op
+     *
+     * @param clientId
+     * @param code
+     * @param uid
+     * @param packageName
+     * @param attributionTag
      */
-    void notifyOpChanged(@NonNull OnOpModeChangedListener changedListener, int op, int uid,
-            @Nullable String packageName);
+    void finishOperation(IBinder clientId, int code, int uid, String packageName,
+            String attributionTag);
 
     /**
-     * Temporary API which will be removed once we can safely untangle the methods that use this.
-     * Notify that the app-op's mode is changed to all packages associated with the uid by
-     * triggering the appropriate change listener.
-     * @param op App-op whose mode has changed
-     * @param uid user id associated with the app-op
-     * @param onlyForeground true if only watchers that
-     * @param callbackToIgnore callback that should be ignored.
+     *
+     * @param clientId
+     * @param code
+     * @param uid
+     * @param packageName
+     * @param attributionTag
      */
-    void notifyOpChangedForAllPkgsInUid(int op, int uid, boolean onlyForeground,
-            @Nullable OnOpModeChangedListener callbackToIgnore);
+    void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName,
+            String attributionTag);
 
     /**
-     * TODO: Move hasForegroundWatchers and foregroundOps into this.
-     * Go over the list of app-ops for the uid and mark app-ops with MODE_FOREGROUND in
-     * foregroundOps.
-     * @param uid for which the app-op's mode needs to be marked.
-     * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
-     * @return  foregroundOps.
+     *
+     * @param uidPackageNames
+     * @param visible
      */
-    SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps);
+    void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible);
 
     /**
-     * Go over the list of app-ops for the package name and mark app-ops with MODE_FOREGROUND in
-     * foregroundOps.
-     * @param packageName for which the app-op's mode needs to be marked.
-     * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
-     * @param userId user id associated with the package.
-     * @return foregroundOps.
+     *
      */
-    SparseBooleanArray evalForegroundPackageOps(String packageName,
-            SparseBooleanArray foregroundOps, @UserIdInt int userId);
+    void readState();
 
     /**
-     * Dump op mode and package mode listeners and their details.
-     * @param dumpOp if -1 then op mode listeners for all app-ops are dumped. If it's set to an
-     *               app-op, only the watchers for that app-op are dumped.
-     * @param dumpUid uid for which we want to dump op mode watchers.
-     * @param dumpPackage if not null and if dumpOp is -1, dumps watchers for the package name.
-     * @param printWriter writer to dump to.
+     *
      */
-    boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter);
+    void writeState();
+
+    /**
+     *
+     * @param uid
+     * @param packageName
+     */
+    void packageRemoved(int uid, String packageName);
+
+    /**
+     *
+     * @param uid
+     */
+    void uidRemoved(int uid);
+
+    /**
+     *
+     * @param uid
+     * @param procState
+     * @param capability
+     */
+    void updateUidProcState(int uid, int procState,
+            @ActivityManager.ProcessCapability int capability);
+
+    /**
+     *
+     * @param ops
+     * @return
+     */
+    List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops);
+
+    /**
+     *
+     * @param uid
+     * @param packageName
+     * @param ops
+     * @return
+     */
+    List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName,
+            int[] ops);
+
+    /**
+     *
+     * @param uid
+     * @param packageName
+     * @param attributionTag
+     * @param opNames
+     * @param dataType
+     * @param filter
+     * @param beginTimeMillis
+     * @param endTimeMillis
+     * @param flags
+     * @param callback
+     */
+    void getHistoricalOps(int uid, String packageName, String attributionTag,
+            List<String> opNames, int dataType, int filter, long beginTimeMillis,
+            long endTimeMillis, int flags, RemoteCallback callback);
+
+    /**
+     *
+     * @param uid
+     * @param packageName
+     * @param attributionTag
+     * @param opNames
+     * @param dataType
+     * @param filter
+     * @param beginTimeMillis
+     * @param endTimeMillis
+     * @param flags
+     * @param callback
+     */
+    void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag,
+            List<String> opNames, int dataType, int filter, long beginTimeMillis,
+            long endTimeMillis, int flags, RemoteCallback callback);
+
+    /**
+     *
+     */
+    void reloadNonHistoricalState();
+
+    /**
+     *
+     * @param uid
+     * @param ops
+     * @return
+     */
+    List<AppOpsManager.PackageOps> getUidOps(int uid, int[] ops);
+
+    /**
+     *
+     * @param owners
+     */
+    void setDeviceAndProfileOwners(SparseIntArray owners);
+
+    // used in audio restriction calls, might just copy the logic to avoid having this call.
+    /**
+     *
+     * @param callingPid
+     * @param callingUid
+     * @param targetUid
+     */
+    void enforceManageAppOpsModes(int callingPid, int callingUid, int targetUid);
+
+    /**
+     *
+     * @param code
+     * @param uid
+     * @param mode
+     * @param permissionPolicyCallback
+     */
+    void setUidMode(int code, int uid, int mode,
+            @Nullable IAppOpsCallback permissionPolicyCallback);
+
+    /**
+     *
+     * @param code
+     * @param uid
+     * @param packageName
+     * @param mode
+     * @param permissionPolicyCallback
+     */
+    void setMode(int code, int uid, @NonNull String packageName, int mode,
+            @Nullable IAppOpsCallback permissionPolicyCallback);
+
+    /**
+     *
+     * @param reqUserId
+     * @param reqPackageName
+     */
+    void resetAllModes(int reqUserId, String reqPackageName);
+
+    /**
+     *
+     * @param code
+     * @param uid
+     * @param packageName
+     * @param attributionTag
+     * @param raw
+     * @return
+     */
+    int checkOperation(int code, int uid, String packageName,
+            @Nullable String attributionTag, boolean raw);
+
+    /**
+     *
+     * @param uid
+     * @param packageName
+     * @return
+     */
+    int checkPackage(int uid, String packageName);
+
+    /**
+     *
+     * @param code
+     * @param uid
+     * @param packageName
+     * @param attributionTag
+     * @param message
+     * @return
+     */
+    int noteOperation(int code, int uid, @Nullable String packageName,
+            @Nullable String attributionTag, @Nullable String message);
+
+    /**
+     *
+     * @param code
+     * @param uid
+     * @param packageName
+     * @param attributionTag
+     * @param proxyUid
+     * @param proxyPackageName
+     * @param proxyAttributionTag
+     * @param flags
+     * @return
+     */
+    @AppOpsManager.Mode
+    int noteOperationUnchecked(int code, int uid, @NonNull String packageName,
+            @Nullable String attributionTag, int proxyUid, String proxyPackageName,
+            @Nullable String proxyAttributionTag, @AppOpsManager.OpFlags int flags);
+
+    boolean isAttributionTagValid(int uid, @NonNull String packageName,
+            @Nullable String attributionTag, @Nullable String proxyPackageName);
+
+    /**
+     *
+     * @param fd
+     * @param pw
+     * @param args
+     */
+    @NeverCompile
+        // Avoid size overhead of debugging code.
+    void dump(FileDescriptor fd, PrintWriter pw, String[] args);
+
+    /**
+     *
+     * @param restrictions
+     * @param token
+     * @param userHandle
+     */
+    void setUserRestrictions(Bundle restrictions, IBinder token, int userHandle);
+
+    /**
+     *
+     * @param code
+     * @param restricted
+     * @param token
+     * @param userHandle
+     * @param excludedPackageTags
+     */
+    void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle,
+            PackageTagsList excludedPackageTags);
+
+    /**
+     *
+     * @param code
+     * @param restricted
+     * @param token
+     */
+    void setGlobalRestriction(int code, boolean restricted, IBinder token);
+
+    /**
+     *
+     * @param code
+     * @param user
+     * @param pkg
+     * @param attributionTag
+     * @return
+     */
+    int getOpRestrictionCount(int code, UserHandle user, String pkg,
+            String attributionTag);
+
+    /**
+     *
+     * @param code
+     * @param uid
+     */
+    // added to interface for audio restriction stuff
+    void notifyWatchersOfChange(int code, int uid);
+
+    /**
+     *
+     * @param userHandle
+     * @throws RemoteException
+     */
+    void removeUser(int userHandle) throws RemoteException;
+
+    /**
+     *
+     * @param code
+     * @param uid
+     * @param packageName
+     * @return
+     */
+    boolean isOperationActive(int code, int uid, String packageName);
+
+    /**
+     *
+     * @param op
+     * @param proxyPackageName
+     * @param proxyAttributionTag
+     * @param proxiedUid
+     * @param proxiedPackageName
+     * @return
+     */
+    // TODO this one might not need to be in the interface
+    boolean isProxying(int op, @NonNull String proxyPackageName,
+            @NonNull String proxyAttributionTag, int proxiedUid,
+            @NonNull String proxiedPackageName);
+
+    /**
+     *
+     * @param packageName
+     */
+    void resetPackageOpsNoHistory(@NonNull String packageName);
+
+    /**
+     *
+     * @param mode
+     * @param baseSnapshotInterval
+     * @param compressionStep
+     */
+    void setHistoryParameters(@AppOpsManager.HistoricalMode int mode,
+            long baseSnapshotInterval, int compressionStep);
+
+    /**
+     *
+     * @param offsetMillis
+     */
+    void offsetHistory(long offsetMillis);
+
+    /**
+     *
+     * @param ops
+     */
+    void addHistoricalOps(AppOpsManager.HistoricalOps ops);
+
+    /**
+     *
+     */
+    void resetHistoryParameters();
+
+    /**
+     *
+     */
+    void clearHistory();
+
+    /**
+     *
+     * @param offlineDurationMillis
+     */
+    void rebootHistory(long offlineDurationMillis);
 }
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
index 5114bd5..c1434e4 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
@@ -59,7 +59,7 @@
     private final DelayableExecutor mExecutor;
     private final Clock mClock;
     private ActivityManagerInternal mActivityManagerInternal;
-    private AppOpsService.Constants mConstants;
+    private AppOpsServiceImpl.Constants mConstants;
 
     private SparseIntArray mUidStates = new SparseIntArray();
     private SparseIntArray mPendingUidStates = new SparseIntArray();
@@ -85,7 +85,7 @@
 
     AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal,
             Handler handler, Executor lockingExecutor, Clock clock,
-            AppOpsService.Constants constants) {
+            AppOpsServiceImpl.Constants constants) {
 
         this(activityManagerInternal, new DelayableExecutor() {
             @Override
@@ -102,7 +102,7 @@
 
     @VisibleForTesting
     AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal,
-            DelayableExecutor executor, Clock clock, AppOpsService.Constants constants,
+            DelayableExecutor executor, Clock clock, AppOpsServiceImpl.Constants constants,
             Thread executorThread) {
         mActivityManagerInternal = activityManagerInternal;
         mExecutor = executor;
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index dcc36bc..7970269 100644
--- a/services/core/java/com/android/server/appop/AttributedOp.java
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -40,9 +40,9 @@
 import java.util.NoSuchElementException;
 
 final class AttributedOp {
-    private final @NonNull AppOpsService mAppOpsService;
+    private final @NonNull AppOpsServiceImpl mAppOpsService;
     public final @Nullable String tag;
-    public final @NonNull AppOpsService.Op parent;
+    public final @NonNull AppOpsServiceImpl.Op parent;
 
     /**
      * Last successful accesses (noteOp + finished startOp) for each uidState/opFlag combination
@@ -80,8 +80,8 @@
     // @GuardedBy("mAppOpsService")
     @Nullable ArrayMap<IBinder, InProgressStartOpEvent> mPausedInProgressEvents;
 
-    AttributedOp(@NonNull AppOpsService appOpsService, @Nullable String tag,
-            @NonNull AppOpsService.Op parent) {
+    AttributedOp(@NonNull AppOpsServiceImpl appOpsService, @Nullable String tag,
+                 @NonNull AppOpsServiceImpl.Op parent) {
         mAppOpsService = appOpsService;
         this.tag = tag;
         this.parent = parent;
@@ -131,8 +131,8 @@
 
         AppOpsManager.OpEventProxyInfo proxyInfo = null;
         if (proxyUid != Process.INVALID_UID) {
-            proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
-                    proxyAttributionTag);
+            proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid,
+                    proxyPackageName, proxyAttributionTag);
         }
 
         AppOpsManager.NoteOpEvent existingEvent = mAccessEvents.get(key);
@@ -238,7 +238,7 @@
         if (event == null) {
             event = mAppOpsService.mInProgressStartOpEventPool.acquire(startTime,
                     SystemClock.elapsedRealtime(), clientId, tag,
-                    PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId),
+                    PooledLambda.obtainRunnable(AppOpsServiceImpl::onClientDeath, this, clientId),
                     proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags,
                     attributionFlags, attributionChainId);
             events.put(clientId, event);
@@ -251,9 +251,9 @@
         event.mNumUnfinishedStarts++;
 
         if (isStarted) {
-            mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
-                    parent.packageName, tag, uidState, flags, startTime, attributionFlags,
-                    attributionChainId);
+            mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op,
+                    parent.uid, parent.packageName, tag, uidState, flags, startTime,
+                    attributionFlags, attributionChainId);
         }
     }
 
@@ -309,8 +309,8 @@
             mAccessEvents.put(makeKey(event.getUidState(), event.getFlags()),
                     finishedEvent);
 
-            mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid,
-                    parent.packageName, tag, event.getUidState(),
+            mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op,
+                    parent.uid, parent.packageName, tag, event.getUidState(),
                     event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(),
                     event.getAttributionFlags(), event.getAttributionChainId());
 
@@ -334,13 +334,13 @@
     @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
     private void finishPossiblyPaused(@NonNull IBinder clientId, boolean isPausing) {
         if (!isPaused()) {
-            Slog.wtf(AppOpsService.TAG, "No ops running or paused");
+            Slog.wtf(AppOpsServiceImpl.TAG, "No ops running or paused");
             return;
         }
 
         int indexOfToken = mPausedInProgressEvents.indexOfKey(clientId);
         if (indexOfToken < 0) {
-            Slog.wtf(AppOpsService.TAG, "No op running or paused for the client");
+            Slog.wtf(AppOpsServiceImpl.TAG, "No op running or paused for the client");
             return;
         } else if (isPausing) {
             // already paused
@@ -416,9 +416,9 @@
             mInProgressEvents.put(event.getClientId(), event);
             event.setStartElapsedTime(SystemClock.elapsedRealtime());
             event.setStartTime(startTime);
-            mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
-                    parent.packageName, tag, event.getUidState(), event.getFlags(), startTime,
-                    event.getAttributionFlags(), event.getAttributionChainId());
+            mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op,
+                    parent.uid, parent.packageName, tag, event.getUidState(), event.getFlags(),
+                    startTime, event.getAttributionFlags(), event.getAttributionChainId());
             if (shouldSendActive) {
                 mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
                         parent.packageName, tag, true, event.getAttributionFlags(),
@@ -503,8 +503,8 @@
                         newEvent.mNumUnfinishedStarts += numPreviousUnfinishedStarts - 1;
                     }
                 } catch (RemoteException e) {
-                    if (AppOpsService.DEBUG) {
-                        Slog.e(AppOpsService.TAG,
+                    if (AppOpsServiceImpl.DEBUG) {
+                        Slog.e(AppOpsServiceImpl.TAG,
                                 "Cannot switch to new uidState " + newState);
                     }
                 }
@@ -555,8 +555,8 @@
             ArrayMap<IBinder, InProgressStartOpEvent> ignoredEvents =
                     opToAdd.isRunning()
                             ? opToAdd.mInProgressEvents : opToAdd.mPausedInProgressEvents;
-            Slog.w(AppOpsService.TAG, "Ignoring " + ignoredEvents.size() + " app-ops, running: "
-                    + opToAdd.isRunning());
+            Slog.w(AppOpsServiceImpl.TAG, "Ignoring " + ignoredEvents.size()
+                    + " app-ops, running: " + opToAdd.isRunning());
 
             int numInProgressEvents = ignoredEvents.size();
             for (int i = 0; i < numInProgressEvents; i++) {
@@ -668,16 +668,22 @@
         /**
          * Create a new {@link InProgressStartOpEvent}.
          *
-         * @param startTime          The time {@link #startOperation} was called
-         * @param startElapsedTime   The elapsed time when {@link #startOperation} was called
-         * @param clientId           The client id of the caller of {@link #startOperation}
+         * @param startTime          The time {@link AppOpCheckingServiceInterface#startOperation}
+         *                          was called
+         * @param startElapsedTime   The elapsed time whe
+         *                          {@link AppOpCheckingServiceInterface#startOperation} was called
+         * @param clientId           The client id of the caller of
+         *                          {@link AppOpCheckingServiceInterface#startOperation}
          * @param attributionTag     The attribution tag for the operation.
          * @param onDeath            The code to execute on client death
-         * @param uidState           The uidstate of the app {@link #startOperation} was called for
+         * @param uidState           The uidstate of the app
+         *                          {@link AppOpCheckingServiceInterface#startOperation} was called
+         *                          for
          * @param attributionFlags   the attribution flags for this operation.
          * @param attributionChainId the unique id of the attribution chain this op is a part of.
-         * @param proxy              The proxy information, if {@link #startProxyOperation} was
-         *                           called
+         * @param proxy              The proxy information, if
+         *                          {@link AppOpCheckingServiceInterface#startProxyOperation} was
+         *                          called
          * @param flags              The trusted/nontrusted/self flags.
          * @throws RemoteException If the client is dying
          */
@@ -718,15 +724,21 @@
         /**
          * Reinit existing object with new state.
          *
-         * @param startTime          The time {@link #startOperation} was called
-         * @param startElapsedTime   The elapsed time when {@link #startOperation} was called
-         * @param clientId           The client id of the caller of {@link #startOperation}
+         * @param startTime          The time {@link AppOpCheckingServiceInterface#startOperation}
+         *                          was called
+         * @param startElapsedTime   The elapsed time when
+         *                          {@link AppOpCheckingServiceInterface#startOperation} was called
+         * @param clientId           The client id of the caller of
+         *                          {@link AppOpCheckingServiceInterface#startOperation}
          * @param attributionTag     The attribution tag for this operation.
          * @param onDeath            The code to execute on client death
-         * @param uidState           The uidstate of the app {@link #startOperation} was called for
+         * @param uidState           The uidstate of the app
+         *                          {@link AppOpCheckingServiceInterface#startOperation} was called
+         *                          for
          * @param flags              The flags relating to the proxy
-         * @param proxy              The proxy information, if {@link #startProxyOperation}
-         *                           was called
+         * @param proxy              The proxy information, if
+         *                          {@link AppOpCheckingServiceInterface#startProxyOperation was
+         *                          called
          * @param attributionFlags   the attribution flags for this operation.
          * @param attributionChainId the unique id of the attribution chain this op is a part of.
          * @param proxyPool          The pool to release
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index ae929c4..8aa898e 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -5863,6 +5863,9 @@
     };
 
     private boolean isValidCommunicationDevice(AudioDeviceInfo device) {
+        if (!device.isSink()) {
+            return false;
+        }
         for (int type : VALID_COMMUNICATION_DEVICE_TYPES) {
             if (device.getType() == type) {
                 return true;
@@ -5897,7 +5900,11 @@
                 throw new IllegalArgumentException("invalid portID " + portId);
             }
             if (!isValidCommunicationDevice(device)) {
-                throw new IllegalArgumentException("invalid device type " + device.getType());
+                if (!device.isSink()) {
+                    throw new IllegalArgumentException("device must have sink role");
+                } else {
+                    throw new IllegalArgumentException("invalid device type: " + device.getType());
+                }
             }
         }
         final String eventSource = new StringBuilder()
@@ -7092,9 +7099,10 @@
 
     private @AudioManager.DeviceVolumeBehavior
             int getDeviceVolumeBehaviorInt(@NonNull AudioDeviceAttributes device) {
-        // translate Java device type to native device type (for the devices masks for full / fixed)
-        final int audioSystemDeviceOut = AudioDeviceInfo.convertDeviceTypeToInternalDevice(
-                device.getType());
+        // Get the internal type set by the AudioDeviceAttributes constructor which is always more
+        // exact (avoids double conversions) than a conversion from SDK type via
+        // AudioDeviceInfo.convertDeviceTypeToInternalDevice()
+        final int audioSystemDeviceOut = device.getInternalType();
 
         int setDeviceVolumeBehavior = retrieveStoredDeviceVolumeBehavior(audioSystemDeviceOut);
         if (setDeviceVolumeBehavior != AudioManager.DEVICE_VOLUME_BEHAVIOR_UNSET) {
diff --git a/services/core/java/com/android/server/cpu/CpuAvailabilityInfo.java b/services/core/java/com/android/server/cpu/CpuAvailabilityInfo.java
new file mode 100644
index 0000000..06b45bf
--- /dev/null
+++ b/services/core/java/com/android/server/cpu/CpuAvailabilityInfo.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 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.cpu;
+
+import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_ALL;
+import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_BACKGROUND;
+
+import com.android.internal.util.Preconditions;
+
+/** CPU availability information. */
+public final class CpuAvailabilityInfo {
+    /** Constant to indicate missing CPU availability percent. */
+    public static final int MISSING_CPU_AVAILABILITY_PERCENT = -1;
+
+    /**
+     * The CPUSET whose availability info is recorded in this object.
+     *
+     * <p>The contained value is one of the CPUSET_* constants from the
+     * {@link CpuAvailabilityMonitoringConfig}.
+     */
+    @CpuAvailabilityMonitoringConfig.Cpuset
+    public final int cpuset;
+
+    /** The latest average CPU availability percent. */
+    public final int latestAvgAvailabilityPercent;
+
+    /** The past N-second average CPU availability percent. */
+    public final int pastNSecAvgAvailabilityPercent;
+
+    /** The duration over which the {@link pastNSecAvgAvailabilityPercent} was calculated. */
+    public final int avgAvailabilityDurationSec;
+
+    @Override
+    public String toString() {
+        return "CpuAvailabilityInfo{" + "cpuset=" + cpuset + ", latestAvgAvailabilityPercent="
+                + latestAvgAvailabilityPercent + ", pastNSecAvgAvailabilityPercent="
+                + pastNSecAvgAvailabilityPercent + ", avgAvailabilityDurationSec="
+                + avgAvailabilityDurationSec + '}';
+    }
+
+    CpuAvailabilityInfo(int cpuset, int latestAvgAvailabilityPercent,
+            int pastNSecAvgAvailabilityPercent, int avgAvailabilityDurationSec) {
+        this.cpuset = Preconditions.checkArgumentInRange(cpuset, CPUSET_ALL, CPUSET_BACKGROUND,
+                "cpuset");
+        this.latestAvgAvailabilityPercent = latestAvgAvailabilityPercent;
+        this.pastNSecAvgAvailabilityPercent = pastNSecAvgAvailabilityPercent;
+        this.avgAvailabilityDurationSec = avgAvailabilityDurationSec;
+    }
+}
diff --git a/services/core/java/com/android/server/cpu/CpuAvailabilityMonitoringConfig.java b/services/core/java/com/android/server/cpu/CpuAvailabilityMonitoringConfig.java
new file mode 100644
index 0000000..a3c4c9e
--- /dev/null
+++ b/services/core/java/com/android/server/cpu/CpuAvailabilityMonitoringConfig.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 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.cpu;
+
+import android.annotation.IntDef;
+import android.util.IntArray;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** CPU availability monitoring config. */
+public final class CpuAvailabilityMonitoringConfig {
+    /** Constant to monitor all cpusets. */
+    public static final int CPUSET_ALL = 1;
+
+    /** Constant to monitor background cpusets. */
+    public static final int CPUSET_BACKGROUND = 2;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CPUSET_"}, value = {
+            CPUSET_ALL,
+            CPUSET_BACKGROUND
+    })
+    public @interface Cpuset {
+    }
+
+    /**
+     * The CPUSET to monitor.
+     *
+     * <p>The value must be one of the {@code CPUSET_*} constants.
+     */
+    @Cpuset
+    public final int cpuset;
+
+    /**
+     * CPU availability percent thresholds.
+     *
+     * <p>CPU availability change notifications are sent when the latest or last N seconds average
+     * CPU availability percent crosses any of these thresholds since the last notification.
+     */
+    private final IntArray mThresholds;
+
+    public IntArray getThresholds() {
+        return mThresholds;
+    }
+
+    /**
+     * Builder for the construction of {@link CpuAvailabilityMonitoringConfig} objects.
+     *
+     * <p>The builder must contain at least one threshold before calling {@link build}.
+     */
+    public static final class Builder {
+        private final int mCpuset;
+        private final IntArray mThresholds = new IntArray();
+
+        public Builder(int cpuset, int... thresholds) {
+            mCpuset = cpuset;
+            for (int threshold : thresholds) {
+                addThreshold(threshold);
+            }
+        }
+
+        /** Adds the given threshold to the builder object. */
+        public Builder addThreshold(int threshold) {
+            if (mThresholds.indexOf(threshold) == -1) {
+                mThresholds.add(threshold);
+            }
+            return this;
+        }
+
+        /** Returns the {@link CpuAvailabilityMonitoringConfig} object. */
+        public CpuAvailabilityMonitoringConfig build() {
+            return new CpuAvailabilityMonitoringConfig(this);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "CpuAvailabilityMonitoringConfig{cpuset=" + cpuset + ", mThresholds=" + mThresholds
+                + ')';
+    }
+
+    private CpuAvailabilityMonitoringConfig(Builder builder) {
+        if (builder.mCpuset != CPUSET_ALL && builder.mCpuset != CPUSET_BACKGROUND) {
+            throw new IllegalStateException("Cpuset must be either CPUSET_ALL (" + CPUSET_ALL
+                    + ") or CPUSET_BACKGROUND (" + CPUSET_BACKGROUND + "). Builder contains "
+                    + builder.mCpuset);
+        }
+        if (builder.mThresholds.size() == 0) {
+            throw new IllegalStateException("Must provide at least one threshold");
+        }
+        this.cpuset = builder.mCpuset;
+        this.mThresholds = builder.mThresholds.clone();
+    }
+}
diff --git a/services/core/java/com/android/server/cpu/CpuMonitorInternal.java b/services/core/java/com/android/server/cpu/CpuMonitorInternal.java
new file mode 100644
index 0000000..849a20b
--- /dev/null
+++ b/services/core/java/com/android/server/cpu/CpuMonitorInternal.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 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.cpu;
+
+import android.annotation.CallbackExecutor;
+
+import java.util.concurrent.Executor;
+
+/** CpuMonitorInternal hosts internal APIs to monitor CPU. */
+public abstract class CpuMonitorInternal {
+    /** Callback to get CPU availability change notifications. */
+    public interface CpuAvailabilityCallback {
+        /**
+         * Called when the CPU availability crosses the provided thresholds.
+         *
+         * <p>Called when the latest or past N-second (which will be specified in the
+         * {@link CpuAvailabilityInfo}) average CPU availability percent has crossed
+         * (either goes above or drop below) the {@link CpuAvailabilityMonitoringConfig#thresholds}
+         * since the last notification. Also called when a callback is added to the service.
+         *
+         * <p>The callback is called at the executor which is specified in
+         * {@link addCpuAvailabilityCallback} or at the service handler thread.
+         *
+         * @param info CPU availability information.
+         */
+        void onAvailabilityChanged(CpuAvailabilityInfo info);
+
+        /**
+         * Called when the CPU monitoring interval changes.
+         *
+         * <p>Also called when a callback is added to the service.
+         *
+         * @param intervalMilliseconds CPU monitoring interval in milliseconds.
+         */
+        void onMonitoringIntervalChanged(long intervalMilliseconds);
+    }
+
+    /**
+     * Adds the {@link CpuAvailabilityCallback} for the caller.
+     *
+     * <p>When the callback is added, the callback will be called to notify the current CPU
+     * availability and monitoring interval.
+     *
+     * <p>When the client needs to update the {@link config} for a previously added callback,
+     * the client has to remove the callback and add the callback with a new {@link config}.
+     *
+     * @param executor Executor to execute the callback. If an executor is not provided,
+     *                 the callback will be executed on the service handler thread.
+     * @param config CPU availability monitoring config.
+     * @param callback Callback implementing {@link CpuAvailabilityCallback}
+     * interface.
+     *
+     * @throws IllegalStateException if {@code callback} is already added.
+     */
+    public abstract void addCpuAvailabilityCallback(@CallbackExecutor Executor executor,
+            CpuAvailabilityMonitoringConfig config, CpuAvailabilityCallback callback);
+
+    /**
+     * Removes the {@link CpuAvailabilityCallback} for the caller.
+     *
+     * @param callback Callback implementing {@link CpuAvailabilityCallback}
+     * interface.
+     *
+     * @throws IllegalArgumentException if {@code callback} is not previously added.
+     */
+    public abstract void removeCpuAvailabilityCallback(CpuAvailabilityCallback callback);
+}
diff --git a/services/core/java/com/android/server/cpu/CpuMonitorService.java b/services/core/java/com/android/server/cpu/CpuMonitorService.java
new file mode 100644
index 0000000..b0dfb84
--- /dev/null
+++ b/services/core/java/com/android/server/cpu/CpuMonitorService.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2022 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.cpu;
+
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
+
+import android.content.Context;
+import android.os.Binder;
+import android.util.ArrayMap;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.DumpUtils;
+import com.android.server.SystemService;
+import com.android.server.utils.PriorityDump;
+import com.android.server.utils.Slogf;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/** Service to monitor CPU availability and usage. */
+public final class CpuMonitorService extends SystemService {
+    static final String TAG = CpuMonitorService.class.getSimpleName();
+    static final boolean DEBUG = Slogf.isLoggable(TAG, Log.DEBUG);
+    // TODO(b/242722241): Make this a resource overlay property.
+    //  Maintain 3 monitoring intervals:
+    //  * One to poll very frequently when mCpuAvailabilityCallbackInfoByCallbacks are available and
+    //    CPU availability is above a threshold (such as at least 10% of CPU is available).
+    //  * One to poll less frequently when mCpuAvailabilityCallbackInfoByCallbacks are available
+    //    and CPU availability is below a threshold (such as less than 10% of CPU is available).
+    //  * One to poll very less frequently when no callbacks are available and the build is either
+    //    user-debug or eng. This will be useful for debugging in development environment.
+    static final int DEFAULT_CPU_MONITORING_INTERVAL_MILLISECONDS = 5_000;
+
+    private final Context mContext;
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private final ArrayMap<CpuMonitorInternal.CpuAvailabilityCallback, CpuAvailabilityCallbackInfo>
+            mCpuAvailabilityCallbackInfoByCallbacks = new ArrayMap<>();
+    @GuardedBy("mLock")
+    private long mMonitoringIntervalMilliseconds = DEFAULT_CPU_MONITORING_INTERVAL_MILLISECONDS;
+
+    private final CpuMonitorInternal mLocalService = new CpuMonitorInternal() {
+        @Override
+        public void addCpuAvailabilityCallback(Executor executor,
+                CpuAvailabilityMonitoringConfig config, CpuAvailabilityCallback callback) {
+            Objects.requireNonNull(callback, "Callback must be non-null");
+            Objects.requireNonNull(config, "Config must be non-null");
+            synchronized (mLock) {
+                if (mCpuAvailabilityCallbackInfoByCallbacks.containsKey(callback)) {
+                    Slogf.i(TAG, "Overwriting the existing CpuAvailabilityCallback %s",
+                            mCpuAvailabilityCallbackInfoByCallbacks.get(callback));
+                    // TODO(b/242722241): Overwrite any internal cache (will be added in future CLs)
+                    //  that maps callbacks based on the CPU availability thresholds.
+                }
+                CpuAvailabilityCallbackInfo info = new CpuAvailabilityCallbackInfo(config,
+                        executor);
+                mCpuAvailabilityCallbackInfoByCallbacks.put(callback, info);
+                if (DEBUG) {
+                    Slogf.d(TAG, "Added a CPU availability callback: %s", info);
+                }
+            }
+            // TODO(b/242722241):
+            //  * On the executor or on the handler thread, call the callback with the latest CPU
+            //    availability info and monitoring interval.
+            //  * Monitor the CPU stats more frequently when the first callback is added.
+        }
+
+        @Override
+        public void removeCpuAvailabilityCallback(CpuAvailabilityCallback callback) {
+            synchronized (mLock) {
+                if (!mCpuAvailabilityCallbackInfoByCallbacks.containsKey(callback)) {
+                    Slogf.i(TAG, "CpuAvailabilityCallback was not previously added."
+                            + " Ignoring the remove request");
+                    return;
+                }
+                CpuAvailabilityCallbackInfo info =
+                        mCpuAvailabilityCallbackInfoByCallbacks.remove(callback);
+                if (DEBUG) {
+                    Slogf.d(TAG, "Removed a CPU availability callback: %s", info);
+                }
+            }
+            // TODO(b/242722241): Increase CPU monitoring interval when all callbacks are removed.
+        }
+    };
+
+    public CpuMonitorService(Context context) {
+        super(context);
+        mContext = context;
+    }
+
+    @Override
+    public void onStart() {
+        publishLocalService(CpuMonitorInternal.class, mLocalService);
+        publishBinderService("cpu_monitor", new CpuMonitorBinder(), /* allowIsolated= */ false,
+                DUMP_FLAG_PRIORITY_CRITICAL);
+    }
+
+    private void doDump(IndentingPrintWriter writer) {
+        writer.printf("*%s*\n", getClass().getSimpleName());
+        writer.increaseIndent();
+        synchronized (mLock) {
+            writer.printf("CPU monitoring interval: %d ms\n", mMonitoringIntervalMilliseconds);
+            if (!mCpuAvailabilityCallbackInfoByCallbacks.isEmpty()) {
+                writer.println("CPU availability change callbacks:");
+                writer.increaseIndent();
+                for (int i = 0; i < mCpuAvailabilityCallbackInfoByCallbacks.size(); i++) {
+                    writer.printf("%s: %s\n", mCpuAvailabilityCallbackInfoByCallbacks.keyAt(i),
+                            mCpuAvailabilityCallbackInfoByCallbacks.valueAt(i));
+                }
+                writer.decreaseIndent();
+            }
+        }
+        // TODO(b/242722241): Print the recent past CPU stats.
+        writer.decreaseIndent();
+    }
+
+    private static final class CpuAvailabilityCallbackInfo {
+        public final CpuAvailabilityMonitoringConfig config;
+        public final Executor executor;
+
+        CpuAvailabilityCallbackInfo(CpuAvailabilityMonitoringConfig config,
+                Executor executor) {
+            this.config = config;
+            this.executor = executor;
+        }
+
+        @Override
+        public String toString() {
+            return "CpuAvailabilityCallbackInfo{" + "config=" + config + ", mExecutor=" + executor
+                    + '}';
+        }
+    }
+
+    private final class CpuMonitorBinder extends Binder {
+        private final PriorityDump.PriorityDumper mPriorityDumper =
+                new PriorityDump.PriorityDumper() {
+                    @Override
+                    public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args,
+                            boolean asProto) {
+                        if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)
+                                || asProto) {
+                            return;
+                        }
+                        try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw)) {
+                            doDump(ipw);
+                        }
+                    }
+                };
+
+        @Override
+        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            PriorityDump.dump(mPriorityDumper, fd, pw, args);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 84dfe86..8811999 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -23,6 +23,7 @@
 import android.view.DisplayAddress;
 import android.view.DisplayCutout;
 import android.view.DisplayEventReceiver;
+import android.view.DisplayShape;
 import android.view.RoundedCorners;
 import android.view.Surface;
 
@@ -158,6 +159,19 @@
     public static final int FLAG_TOUCH_FEEDBACK_DISABLED = 1 << 16;
 
     /**
+     * Flag: Indicates that the display maintains its own focus and touch mode.
+     *
+     * This flag is similar to {@link com.android.internal.R.bool.config_perDisplayFocusEnabled} in
+     * behavior, but only applies to the specific display instead of system-wide to all displays.
+     *
+     * Note: The display must be trusted in order to have its own focus.
+     *
+     * @see #FLAG_TRUSTED
+     * @hide
+     */
+    public static final int FLAG_OWN_FOCUS = 1 << 17;
+
+    /**
      * Touch attachment: Display does not receive touch.
      */
     public static final int TOUCH_NONE = 0;
@@ -303,6 +317,11 @@
     public RoundedCorners roundedCorners;
 
     /**
+     * The {@link RoundedCorners} if present or {@code null} otherwise.
+     */
+    public DisplayShape displayShape;
+
+    /**
      * The touch attachment, per {@link DisplayViewport#touch}.
      */
     public int touch;
@@ -438,7 +457,8 @@
                 || !BrightnessSynchronizer.floatEquals(brightnessDefault,
                 other.brightnessDefault)
                 || !Objects.equals(roundedCorners, other.roundedCorners)
-                || installOrientation != other.installOrientation) {
+                || installOrientation != other.installOrientation
+                || !Objects.equals(displayShape, other.displayShape)) {
             diff |= DIFF_OTHER;
         }
         return diff;
@@ -484,6 +504,7 @@
         brightnessDefault = other.brightnessDefault;
         roundedCorners = other.roundedCorners;
         installOrientation = other.installOrientation;
+        displayShape = other.displayShape;
     }
 
     // For debugging purposes
@@ -533,6 +554,9 @@
         }
         sb.append(flagsToString(flags));
         sb.append(", installOrientation ").append(installOrientation);
+        if (displayShape != null) {
+            sb.append(", displayShape ").append(displayShape);
+        }
         sb.append("}");
         return sb.toString();
     }
@@ -584,9 +608,30 @@
         if ((flags & FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD) != 0) {
             msg.append(", FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD");
         }
+        if ((flags & FLAG_DESTROY_CONTENT_ON_REMOVAL) != 0) {
+            msg.append(", FLAG_DESTROY_CONTENT_ON_REMOVAL");
+        }
         if ((flags & FLAG_MASK_DISPLAY_CUTOUT) != 0) {
             msg.append(", FLAG_MASK_DISPLAY_CUTOUT");
         }
+        if ((flags & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0) {
+            msg.append(", FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS");
+        }
+        if ((flags & FLAG_TRUSTED) != 0) {
+            msg.append(", FLAG_TRUSTED");
+        }
+        if ((flags & FLAG_OWN_DISPLAY_GROUP) != 0) {
+            msg.append(", FLAG_OWN_DISPLAY_GROUP");
+        }
+        if ((flags & FLAG_ALWAYS_UNLOCKED) != 0) {
+            msg.append(", FLAG_ALWAYS_UNLOCKED");
+        }
+        if ((flags & FLAG_TOUCH_FEEDBACK_DISABLED) != 0) {
+            msg.append(", FLAG_TOUCH_FEEDBACK_DISABLED");
+        }
+        if ((flags & FLAG_OWN_FOCUS) != 0) {
+            msg.append(", FLAG_OWN_FOCUS");
+        }
         return msg.toString();
     }
 }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 05cd67f..c5cb08d 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -105,7 +105,6 @@
 import android.os.UserManager;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
-import android.sysprop.DisplayProperties;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.EventLog;
@@ -451,8 +450,6 @@
         }
     };
 
-    private final boolean mAllowNonNativeRefreshRateOverride;
-
     private final BrightnessSynchronizer mBrightnessSynchronizer;
 
     /**
@@ -506,7 +503,6 @@
         ColorSpace[] colorSpaces = SurfaceControl.getCompositionColorSpaces();
         mWideColorSpace = colorSpaces[1];
         mOverlayProperties = SurfaceControl.getOverlaySupport();
-        mAllowNonNativeRefreshRateOverride = mInjector.getAllowNonNativeRefreshRateOverride();
         mSystemReady = false;
     }
 
@@ -930,24 +926,20 @@
             }
         }
 
-        if (mAllowNonNativeRefreshRateOverride) {
-            overriddenInfo.refreshRateOverride = frameRateHz;
-            if (!CompatChanges.isChangeEnabled(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE,
-                    callingUid)) {
-                overriddenInfo.supportedModes = Arrays.copyOf(info.supportedModes,
-                        info.supportedModes.length + 1);
-                overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1] =
-                        new Display.Mode(Display.DISPLAY_MODE_ID_FOR_FRAME_RATE_OVERRIDE,
-                                currentMode.getPhysicalWidth(), currentMode.getPhysicalHeight(),
-                                overriddenInfo.refreshRateOverride);
-                overriddenInfo.modeId =
-                        overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1]
-                                .getModeId();
-            }
-            return overriddenInfo;
+        overriddenInfo.refreshRateOverride = frameRateHz;
+        if (!CompatChanges.isChangeEnabled(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE,
+                callingUid)) {
+            overriddenInfo.supportedModes = Arrays.copyOf(info.supportedModes,
+                    info.supportedModes.length + 1);
+            overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1] =
+                    new Display.Mode(Display.DISPLAY_MODE_ID_FOR_FRAME_RATE_OVERRIDE,
+                            currentMode.getPhysicalWidth(), currentMode.getPhysicalHeight(),
+                            overriddenInfo.refreshRateOverride);
+            overriddenInfo.modeId =
+                    overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1]
+                            .getModeId();
         }
-
-        return info;
+        return overriddenInfo;
     }
 
     private DisplayInfo getDisplayInfoInternal(int displayId, int callingUid) {
@@ -2602,11 +2594,6 @@
         long getDefaultDisplayDelayTimeout() {
             return WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT;
         }
-
-        boolean getAllowNonNativeRefreshRateOverride() {
-            return DisplayProperties
-                    .debug_allow_non_native_refresh_rate_override().orElse(true);
-        }
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 5a714f5..4bf1e98 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -38,6 +38,7 @@
 import android.view.DisplayAddress;
 import android.view.DisplayCutout;
 import android.view.DisplayEventReceiver;
+import android.view.DisplayShape;
 import android.view.RoundedCorners;
 import android.view.SurfaceControl;
 
@@ -686,6 +687,9 @@
                         res, mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
                 mInfo.installOrientation = mStaticDisplayInfo.installOrientation;
 
+                mInfo.displayShape = DisplayShape.fromResources(
+                        res, mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
+
                 if (mStaticDisplayInfo.isInternal) {
                     mInfo.type = Display.TYPE_INTERNAL;
                     mInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL;
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index dedc56a..28bdce3 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -233,6 +233,7 @@
                 info.displayCutout = mOverrideDisplayInfo.displayCutout;
                 info.logicalDensityDpi = mOverrideDisplayInfo.logicalDensityDpi;
                 info.roundedCorners = mOverrideDisplayInfo.roundedCorners;
+                info.displayShape = mOverrideDisplayInfo.displayShape;
             }
             mInfo.set(info);
         }
@@ -384,6 +385,9 @@
             if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_TOUCH_FEEDBACK_DISABLED) != 0) {
                 mBaseDisplayInfo.flags |= Display.FLAG_TOUCH_FEEDBACK_DISABLED;
             }
+            if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) != 0) {
+                mBaseDisplayInfo.flags |= Display.FLAG_OWN_FOCUS;
+            }
             Rect maskingInsets = getMaskingInsets(deviceInfo);
             int maskedWidth = deviceInfo.width - maskingInsets.left - maskingInsets.right;
             int maskedHeight = deviceInfo.height - maskingInsets.top - maskingInsets.bottom;
@@ -434,6 +438,7 @@
             mBaseDisplayInfo.brightnessDefault = deviceInfo.brightnessDefault;
             mBaseDisplayInfo.roundedCorners = deviceInfo.roundedCorners;
             mBaseDisplayInfo.installOrientation = deviceInfo.installOrientation;
+            mBaseDisplayInfo.displayShape = deviceInfo.displayShape;
             mPrimaryDisplayDeviceInfo = deviceInfo;
             mInfo.set(null);
         }
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index b0de844..0e11b53 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -29,6 +29,7 @@
 import android.util.DisplayMetrics;
 import android.util.Slog;
 import android.view.Display;
+import android.view.DisplayShape;
 import android.view.Gravity;
 import android.view.Surface;
 import android.view.SurfaceControl;
@@ -361,6 +362,8 @@
                 mInfo.state = mState;
                 // The display is trusted since it is created by system.
                 mInfo.flags |= FLAG_TRUSTED;
+                mInfo.displayShape =
+                        DisplayShape.createDefaultDisplayShape(mInfo.width, mInfo.height, false);
             }
             return mInfo;
         }
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 20b82c3..d0e518b 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -21,6 +21,7 @@
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
@@ -51,6 +52,7 @@
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.view.Display;
+import android.view.DisplayShape;
 import android.view.Surface;
 import android.view.SurfaceControl;
 
@@ -495,13 +497,25 @@
                 if ((mFlags & VIRTUAL_DISPLAY_FLAG_TRUSTED) != 0) {
                     mInfo.flags |= FLAG_TRUSTED;
                 }
-                if ((mFlags & VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED) != 0
-                        && (mInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0) {
-                    mInfo.flags |= FLAG_ALWAYS_UNLOCKED;
+                if ((mFlags & VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED) != 0) {
+                    if ((mFlags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) != 0) {
+                        mInfo.flags |= FLAG_ALWAYS_UNLOCKED;
+                    } else {
+                        Slog.w(TAG, "Ignoring VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED as it "
+                                + "requires VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP.");
+                    }
                 }
                 if ((mFlags & VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED) != 0) {
                     mInfo.flags |= FLAG_TOUCH_FEEDBACK_DISABLED;
                 }
+                if ((mFlags & VIRTUAL_DISPLAY_FLAG_OWN_FOCUS) != 0) {
+                    if ((mFlags & VIRTUAL_DISPLAY_FLAG_TRUSTED) != 0) {
+                        mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_FOCUS;
+                    } else {
+                        Slog.w(TAG, "Ignoring VIRTUAL_DISPLAY_FLAG_OWN_FOCUS as it requires "
+                                + "VIRTUAL_DISPLAY_FLAG_TRUSTED.");
+                    }
+                }
 
                 mInfo.type = Display.TYPE_VIRTUAL;
                 mInfo.touch = ((mFlags & VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH) == 0) ?
@@ -511,6 +525,9 @@
 
                 mInfo.ownerUid = mOwnerUid;
                 mInfo.ownerPackageName = mOwnerPackageName;
+
+                mInfo.displayShape =
+                        DisplayShape.createDefaultDisplayShape(mInfo.width, mInfo.height, false);
             }
             return mInfo;
         }
diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
index 146b003..c759d98 100644
--- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
@@ -34,6 +34,7 @@
 import android.util.Slog;
 import android.view.Display;
 import android.view.DisplayAddress;
+import android.view.DisplayShape;
 import android.view.Surface;
 import android.view.SurfaceControl;
 
@@ -655,6 +656,8 @@
                 mInfo.setAssumedDensityForExternalDisplay(mWidth, mHeight);
                 // The display is trusted since it is created by system.
                 mInfo.flags |= DisplayDeviceInfo.FLAG_TRUSTED;
+                mInfo.displayShape =
+                        DisplayShape.createDefaultDisplayShape(mInfo.width, mInfo.height, false);
             }
             return mInfo;
         }
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
index 015e576..c53f1a5 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
@@ -110,11 +110,10 @@
 
     @AnyThread
     void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privilegedOperations,
-            int configChanges, @InputMethodNavButtonFlags int navigationBarFlags) {
+            @InputMethodNavButtonFlags int navigationBarFlags) {
         final IInputMethod.InitParams params = new IInputMethod.InitParams();
         params.token = token;
         params.privilegedOperations = privilegedOperations;
-        params.configChanges = configChanges;
         params.navigationBarFlags = navigationBarFlags;
         try {
             mTarget.initializeInternal(params);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 6dbb362..079234c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -42,12 +42,15 @@
 import android.view.inputmethod.InputMethodInfo;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.inputmethod.IInputMethod;
 import com.android.internal.inputmethod.InputBindResult;
 import com.android.internal.inputmethod.UnbindReason;
 import com.android.server.EventLogTags;
 import com.android.server.wm.WindowManagerInternal;
 
+import java.util.concurrent.CountDownLatch;
+
 /**
  * A controller managing the state of the input method binding.
  */
@@ -77,19 +80,26 @@
     @GuardedBy("ImfLock.class") private boolean mVisibleBound;
     @GuardedBy("ImfLock.class") private boolean mSupportsStylusHw;
 
+    @Nullable private CountDownLatch mLatchForTesting;
+
     /**
      * Binding flags for establishing connection to the {@link InputMethodService}.
      */
-    private static final int IME_CONNECTION_BIND_FLAGS =
+    @VisibleForTesting
+    static final int IME_CONNECTION_BIND_FLAGS =
             Context.BIND_AUTO_CREATE
                     | Context.BIND_NOT_VISIBLE
                     | Context.BIND_NOT_FOREGROUND
                     | Context.BIND_IMPORTANT_BACKGROUND
                     | Context.BIND_SCHEDULE_LIKE_TOP_APP;
+
+    private final int mImeConnectionBindFlags;
+
     /**
      * Binding flags used only while the {@link InputMethodService} is showing window.
      */
-    private static final int IME_VISIBLE_BIND_FLAGS =
+    @VisibleForTesting
+    static final int IME_VISIBLE_BIND_FLAGS =
             Context.BIND_AUTO_CREATE
                     | Context.BIND_TREAT_LIKE_ACTIVITY
                     | Context.BIND_FOREGROUND_SERVICE
@@ -97,12 +107,19 @@
                     | Context.BIND_SHOWING_UI;
 
     InputMethodBindingController(@NonNull InputMethodManagerService service) {
+        this(service, IME_CONNECTION_BIND_FLAGS, null /* latchForTesting */);
+    }
+
+    InputMethodBindingController(@NonNull InputMethodManagerService service,
+            int imeConnectionBindFlags, CountDownLatch latchForTesting) {
         mService = service;
         mContext = mService.mContext;
         mMethodMap = mService.mMethodMap;
         mSettings = mService.mSettings;
         mPackageManagerInternal = mService.mPackageManagerInternal;
         mWindowManagerInternal = mService.mWindowManagerInternal;
+        mImeConnectionBindFlags = imeConnectionBindFlags;
+        mLatchForTesting = latchForTesting;
     }
 
     /**
@@ -242,7 +259,7 @@
         @Override public void onBindingDied(ComponentName name) {
             synchronized (ImfLock.class) {
                 mService.invalidateAutofillSessionLocked();
-                if (mVisibleBound) {
+                if (isVisibleBound()) {
                     unbindVisibleConnection();
                 }
             }
@@ -279,7 +296,7 @@
                     if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
                     final InputMethodInfo info = mMethodMap.get(mSelectedMethodId);
                     mSupportsStylusHw = info.supportsStylusHandwriting();
-                    mService.initializeImeLocked(mCurMethod, mCurToken, info.getConfigChanges());
+                    mService.initializeImeLocked(mCurMethod, mCurToken);
                     mService.scheduleNotifyImeUidToAudioService(mCurMethodUid);
                     mService.reRequestCurrentClientSessionLocked();
                     mService.performOnCreateInlineSuggestionsRequestLocked();
@@ -291,6 +308,10 @@
                 mService.scheduleResetStylusHandwriting();
             }
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+
+            if (mLatchForTesting != null) {
+                mLatchForTesting.countDown(); // Notify the finish to tests
+            }
         }
 
         @GuardedBy("ImfLock.class")
@@ -338,15 +359,15 @@
 
     @GuardedBy("ImfLock.class")
     void unbindCurrentMethod() {
-        if (mVisibleBound) {
+        if (isVisibleBound()) {
             unbindVisibleConnection();
         }
 
-        if (mHasConnection) {
+        if (hasConnection()) {
             unbindMainConnection();
         }
 
-        if (mCurToken != null) {
+        if (getCurToken() != null) {
             removeCurrentToken();
             mService.resetSystemUiLocked();
         }
@@ -448,17 +469,17 @@
 
     @GuardedBy("ImfLock.class")
     private boolean bindCurrentInputMethodService(ServiceConnection conn, int flags) {
-        if (mCurIntent == null || conn == null) {
+        if (getCurIntent() == null || conn == null) {
             Slog.e(TAG, "--- bind failed: service = " + mCurIntent + ", conn = " + conn);
             return false;
         }
-        return mContext.bindServiceAsUser(mCurIntent, conn, flags,
+        return mContext.bindServiceAsUser(getCurIntent(), conn, flags,
                 new UserHandle(mSettings.getCurrentUserId()));
     }
 
     @GuardedBy("ImfLock.class")
     private boolean bindCurrentInputMethodServiceMainConnection() {
-        mHasConnection = bindCurrentInputMethodService(mMainConnection, IME_CONNECTION_BIND_FLAGS);
+        mHasConnection = bindCurrentInputMethodService(mMainConnection, mImeConnectionBindFlags);
         return mHasConnection;
     }
 
@@ -472,7 +493,7 @@
     void setCurrentMethodVisible() {
         if (mCurMethod != null) {
             if (DEBUG) Slog.d(TAG, "setCurrentMethodVisible: mCurToken=" + mCurToken);
-            if (mHasConnection && !mVisibleBound) {
+            if (hasConnection() && !isVisibleBound()) {
                 mVisibleBound = bindCurrentInputMethodService(mVisibleConnection,
                         IME_VISIBLE_BIND_FLAGS);
             }
@@ -480,7 +501,7 @@
         }
 
         // No IME is currently connected. Reestablish the main connection.
-        if (!mHasConnection) {
+        if (!hasConnection()) {
             if (DEBUG) {
                 Slog.d(TAG, "Cannot show input: no IME bound. Rebinding.");
             }
@@ -512,7 +533,7 @@
      */
     @GuardedBy("ImfLock.class")
     void setCurrentMethodNotVisible() {
-        if (mVisibleBound) {
+        if (isVisibleBound()) {
             unbindVisibleConnection();
         }
     }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 8b083bd..080d582 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2692,14 +2692,13 @@
     }
 
     @GuardedBy("ImfLock.class")
-    void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token,
-            @android.content.pm.ActivityInfo.Config int configChanges) {
+    void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token) {
         if (DEBUG) {
             Slog.v(TAG, "Sending attach of token: " + token + " for display: "
                     + mCurTokenDisplayId);
         }
         inputMethod.initializeInternal(token, new InputMethodPrivilegedOperationsImpl(this, token),
-                configChanges, getInputMethodNavButtonFlagsLocked());
+                getInputMethodNavButtonFlagsLocked());
     }
 
     @AnyThread
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 8d247f6..3ce51c3 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -28,6 +28,7 @@
 import static android.location.LocationManager.NETWORK_PROVIDER;
 import static android.location.LocationRequest.LOW_POWER_EXCEPTIONS;
 import static android.location.provider.LocationProviderBase.ACTION_FUSED_PROVIDER;
+import static android.location.provider.LocationProviderBase.ACTION_GNSS_PROVIDER;
 import static android.location.provider.LocationProviderBase.ACTION_NETWORK_PROVIDER;
 
 import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
@@ -439,9 +440,24 @@
             mGnssManagerService = new GnssManagerService(mContext, mInjector, gnssNative);
             mGnssManagerService.onSystemReady();
 
+            boolean useGnssHardwareProvider = mContext.getResources().getBoolean(
+                    com.android.internal.R.bool.config_useGnssHardwareProvider);
+            AbstractLocationProvider gnssProvider = null;
+            if (!useGnssHardwareProvider) {
+                gnssProvider = ProxyLocationProvider.create(
+                        mContext,
+                        GPS_PROVIDER,
+                        ACTION_GNSS_PROVIDER,
+                        com.android.internal.R.bool.config_useGnssHardwareProvider,
+                        com.android.internal.R.string.config_gnssLocationProviderPackageName);
+            }
+            if (gnssProvider == null) {
+                gnssProvider = mGnssManagerService.getGnssLocationProvider();
+            }
+
             LocationProviderManager gnssManager = new LocationProviderManager(mContext, mInjector,
                     GPS_PROVIDER, mPassiveManager);
-            addLocationProviderManager(gnssManager, mGnssManagerService.getGnssLocationProvider());
+            addLocationProviderManager(gnssManager, gnssProvider);
         }
 
         // bind to geocoder provider
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index f3cfa95..b8bdabe 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -213,7 +213,7 @@
 
         final int appId = UserHandle.getAppId(pkg.getUid());
 
-        String pkgSeInfo = AndroidPackageUtils.getSeInfo(pkg, ps);
+        String pkgSeInfo = ps.getSeInfo();
 
         Preconditions.checkNotNull(pkgSeInfo);
 
diff --git a/services/core/java/com/android/server/pm/AppStateHelper.java b/services/core/java/com/android/server/pm/AppStateHelper.java
new file mode 100644
index 0000000..9ea350f
--- /dev/null
+++ b/services/core/java/com/android/server/pm/AppStateHelper.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
+import android.content.Context;
+import android.media.IAudioService;
+import android.os.ServiceManager;
+import android.text.TextUtils;
+import android.util.ArraySet;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A helper class to provide queries for app states concerning gentle-update.
+ */
+public class AppStateHelper {
+    private final Context mContext;
+
+    public AppStateHelper(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * True if the package is loaded into the process.
+     */
+    private static boolean isPackageLoaded(RunningAppProcessInfo info, String packageName) {
+        return ArrayUtils.contains(info.pkgList, packageName)
+                || ArrayUtils.contains(info.pkgDeps, packageName);
+    }
+
+    /**
+     * Returns the importance of the given package.
+     */
+    private int getImportance(String packageName) {
+        var am = mContext.getSystemService(ActivityManager.class);
+        return am.getPackageImportance(packageName);
+    }
+
+    /**
+     * True if the app owns the audio focus.
+     */
+    private boolean hasAudioFocus(String packageName) {
+        var audioService = IAudioService.Stub.asInterface(
+                ServiceManager.getService(Context.AUDIO_SERVICE));
+        try {
+            var focusInfos = audioService.getFocusStack();
+            int size = focusInfos.size();
+            var audioFocusPackage = (size > 0) ? focusInfos.get(size - 1).getPackageName() : null;
+            return TextUtils.equals(packageName, audioFocusPackage);
+        } catch (Exception ignore) {
+        }
+        return false;
+    }
+
+    /**
+     * True if the app is in the foreground.
+     */
+    private boolean isAppForeground(String packageName) {
+        return getImportance(packageName) <= RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
+    }
+
+    /**
+     * True if the app is currently at the top of the screen that the user is interacting with.
+     */
+    public boolean isAppTopVisible(String packageName) {
+        return getImportance(packageName) <= RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+    }
+
+    /**
+     * True if the app is playing/recording audio.
+     */
+    private boolean hasActiveAudio(String packageName) {
+        // TODO(b/235306967): also check recording
+        return hasAudioFocus(packageName);
+    }
+
+    /**
+     * True if the app is sending or receiving network data.
+     */
+    private boolean hasActiveNetwork(String packageName) {
+        // To be implemented
+        return false;
+    }
+
+    /**
+     * True if any app is interacting with the user.
+     */
+    public boolean hasInteractingApp(List<String> packageNames) {
+        for (var packageName : packageNames) {
+            if (hasActiveAudio(packageName)
+                    || hasActiveNetwork(packageName)
+                    || isAppTopVisible(packageName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * True if any app is in the foreground.
+     */
+    public boolean hasForegroundApp(List<String> packageNames) {
+        for (var packageName : packageNames) {
+            if (isAppForeground(packageName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * True if any app is top visible.
+     */
+    public boolean hasTopVisibleApp(List<String> packageNames) {
+        for (var packageName : packageNames) {
+            if (isAppTopVisible(packageName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * True if there is an ongoing phone call.
+     */
+    public boolean isInCall() {
+        // To be implemented
+        return false;
+    }
+
+    /**
+     * Returns a list of packages which depend on {@code packageNames}. These are the packages
+     * that will be affected when updating {@code packageNames} and should participate in
+     * the evaluation of install constraints.
+     *
+     * TODO(b/235306967): Also include bounded services as dependency.
+     */
+    public List<String> getDependencyPackages(List<String> packageNames) {
+        var results = new ArraySet<String>();
+        var am = mContext.getSystemService(ActivityManager.class);
+        for (var info : am.getRunningAppProcesses()) {
+            for (var packageName : packageNames) {
+                if (!isPackageLoaded(info, packageName)) {
+                    continue;
+                }
+                for (var pkg : info.pkgList) {
+                    results.add(pkg);
+                }
+            }
+        }
+        return new ArrayList<>(results);
+    }
+}
diff --git a/services/core/java/com/android/server/pm/GentleUpdateHelper.java b/services/core/java/com/android/server/pm/GentleUpdateHelper.java
new file mode 100644
index 0000000..247ac90
--- /dev/null
+++ b/services/core/java/com/android/server/pm/GentleUpdateHelper.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.annotation.WorkerThread;
+import android.app.ActivityThread;
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageInstaller.InstallConstraints;
+import android.content.pm.PackageInstaller.InstallConstraintsResult;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Slog;
+
+import java.util.ArrayDeque;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A helper class to coordinate install flow for sessions with install constraints.
+ * These sessions will be pending and wait until the constraints are satisfied to
+ * resume installation.
+ */
+public class GentleUpdateHelper {
+    private static final String TAG = "GentleUpdateHelper";
+    private static final int JOB_ID = 235306967; // bug id
+    // The timeout used to determine whether the device is idle or not.
+    private static final long PENDING_CHECK_MILLIS = TimeUnit.SECONDS.toMillis(10);
+
+    /**
+     * A wrapper class used by JobScheduler to schedule jobs.
+     */
+    public static class Service extends JobService {
+        @Override
+        public boolean onStartJob(JobParameters params) {
+            try {
+                var pis = (PackageInstallerService) ActivityThread.getPackageManager()
+                        .getPackageInstaller();
+                var helper = pis.getGentleUpdateHelper();
+                helper.mHandler.post(helper::runIdleJob);
+            } catch (Exception e) {
+                Slog.e(TAG, "Failed to get PackageInstallerService", e);
+            }
+            return false;
+        }
+
+        @Override
+        public boolean onStopJob(JobParameters params) {
+            return false;
+        }
+    }
+
+    private static class PendingInstallConstraintsCheck {
+        public final List<String> packageNames;
+        public final InstallConstraints constraints;
+        public final CompletableFuture<InstallConstraintsResult> future;
+        PendingInstallConstraintsCheck(List<String> packageNames,
+                InstallConstraints constraints,
+                CompletableFuture<InstallConstraintsResult> future) {
+            this.packageNames = packageNames;
+            this.constraints = constraints;
+            this.future = future;
+        }
+    }
+
+    private final Context mContext;
+    private final Handler mHandler;
+    private final AppStateHelper mAppStateHelper;
+    // Worker thread only
+    private final ArrayDeque<PendingInstallConstraintsCheck> mPendingChecks = new ArrayDeque<>();
+    private boolean mHasPendingIdleJob;
+
+    GentleUpdateHelper(Context context, Looper looper, AppStateHelper appStateHelper) {
+        mContext = context;
+        mHandler = new Handler(looper);
+        mAppStateHelper = appStateHelper;
+    }
+
+    /**
+     * Checks if install constraints are satisfied for the given packages.
+     */
+    CompletableFuture<InstallConstraintsResult> checkInstallConstraints(
+            List<String> packageNames, InstallConstraints constraints) {
+        var future = new CompletableFuture<InstallConstraintsResult>();
+        mHandler.post(() -> {
+            var pendingCheck = new PendingInstallConstraintsCheck(
+                    packageNames, constraints, future);
+            if (constraints.isRequireDeviceIdle()) {
+                mPendingChecks.add(pendingCheck);
+                // JobScheduler doesn't provide queries about whether the device is idle.
+                // We schedule 2 tasks to determine device idle. If the idle job is executed
+                // before the delayed runnable, we know the device is idle.
+                // Note #processPendingCheck will be no-op for the task executed later.
+                scheduleIdleJob();
+                mHandler.postDelayed(() -> processPendingCheck(pendingCheck, false),
+                        PENDING_CHECK_MILLIS);
+            } else {
+                processPendingCheck(pendingCheck, false);
+            }
+        });
+        return future;
+    }
+
+    @WorkerThread
+    private void scheduleIdleJob() {
+        if (mHasPendingIdleJob) {
+            // No need to schedule the job again
+            return;
+        }
+        mHasPendingIdleJob = true;
+        var componentName = new ComponentName(
+                mContext.getPackageName(), GentleUpdateHelper.Service.class.getName());
+        var jobInfo = new JobInfo.Builder(JOB_ID, componentName)
+                .setRequiresDeviceIdle(true)
+                .build();
+        var jobScheduler = mContext.getSystemService(JobScheduler.class);
+        jobScheduler.schedule(jobInfo);
+    }
+
+    @WorkerThread
+    private void runIdleJob() {
+        mHasPendingIdleJob = false;
+        processPendingChecksInIdle();
+    }
+
+    @WorkerThread
+    private void processPendingCheck(PendingInstallConstraintsCheck pendingCheck, boolean isIdle) {
+        var future = pendingCheck.future;
+        if (future.isDone()) {
+            return;
+        }
+        var constraints = pendingCheck.constraints;
+        var packageNames = mAppStateHelper.getDependencyPackages(pendingCheck.packageNames);
+        var constraintsSatisfied = (!constraints.isRequireDeviceIdle() || isIdle)
+                && (!constraints.isRequireAppNotForeground()
+                        || !mAppStateHelper.hasForegroundApp(packageNames))
+                && (!constraints.isRequireAppNotInteracting()
+                        || !mAppStateHelper.hasInteractingApp(packageNames))
+                && (!constraints.isRequireAppNotTopVisible()
+                        || !mAppStateHelper.hasTopVisibleApp(packageNames))
+                && (!constraints.isRequireNotInCall()
+                        || !mAppStateHelper.isInCall());
+        future.complete(new InstallConstraintsResult((constraintsSatisfied)));
+    }
+
+    @WorkerThread
+    private void processPendingChecksInIdle() {
+        while (!mPendingChecks.isEmpty()) {
+            processPendingCheck(mPendingChecks.remove(), true);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 78e4190..283640d 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -765,7 +765,7 @@
                 || (installFlags & PackageManager.INSTALL_REQUEST_DOWNGRADE) != 0);
 
         if (ps != null && doSnapshotOrRestore) {
-            final String seInfo = AndroidPackageUtils.getSeInfo(request.getPkg(), ps);
+            final String seInfo = ps.getSeInfo();
             final RollbackManagerInternal rollbackManager =
                     mInjector.getLocalService(RollbackManagerInternal.class);
             rollbackManager.snapshotAndRestoreUserData(packageName,
diff --git a/services/core/java/com/android/server/pm/MovePackageHelper.java b/services/core/java/com/android/server/pm/MovePackageHelper.java
index b27373e..b66c6ac 100644
--- a/services/core/java/com/android/server/pm/MovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/MovePackageHelper.java
@@ -129,7 +129,7 @@
         final InstallSource installSource = packageState.getInstallSource();
         final String packageAbiOverride = packageState.getCpuAbiOverride();
         final int appId = UserHandle.getAppId(pkg.getUid());
-        final String seinfo = AndroidPackageUtils.getSeInfo(pkg, packageState);
+        final String seinfo = packageState.getSeInfo();
         final String label = String.valueOf(pm.getApplicationLabel(
                 AndroidPackageUtils.generateAppInfoWithoutState(pkg)));
         final int targetSdkVersion = pkg.getTargetSdkVersion();
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 226a27e..49f3a3c 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -493,7 +493,7 @@
             // TODO: Consider adding 2 different APIs for primary and secondary dexopt.
             // installd only uses downgrade flag for secondary dex files and ignores it for
             // primary dex files.
-            String seInfo = AndroidPackageUtils.getSeInfo(pkg, pkgSetting);
+            String seInfo = pkgSetting.getSeInfo();
             boolean completed = getInstallerLI().dexopt(path, uid, pkg.getPackageName(), isa,
                     dexoptNeeded, oatDir, dexoptFlags, compilerFilter, pkg.getVolumeUuid(),
                     classLoaderContext, seInfo, /* downgrade= */ false ,
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 653a882..409d352 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -44,6 +44,7 @@
 import android.content.pm.IPackageInstallerSession;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.InstallConstraints;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageItemInfo;
@@ -54,12 +55,14 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
+import android.os.RemoteCallback;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.SELinux;
@@ -88,6 +91,7 @@
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.util.ImageUtils;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.IoThread;
@@ -186,6 +190,7 @@
 
     private final InternalCallback mInternalCallback = new InternalCallback();
     private final PackageSessionVerifier mSessionVerifier;
+    private final GentleUpdateHelper mGentleUpdateHelper;
 
     /**
      * Used for generating session IDs. Since this is created at boot time,
@@ -272,6 +277,8 @@
         mStagingManager = new StagingManager(context);
         mSessionVerifier = new PackageSessionVerifier(context, mPm, mApexManager,
                 apexParserSupplier, mInstallThread.getLooper());
+        mGentleUpdateHelper = new GentleUpdateHelper(
+                context, mInstallThread.getLooper(), new AppStateHelper(context));
 
         LocalServices.getService(SystemServiceManager.class).startService(
                 new Lifecycle(context, this));
@@ -1233,6 +1240,33 @@
     }
 
     @Override
+    public void checkInstallConstraints(String installerPackageName, List<String> packageNames,
+            InstallConstraints constraints, RemoteCallback callback) {
+        Preconditions.checkArgument(packageNames != null);
+        Preconditions.checkArgument(constraints != null);
+        Preconditions.checkArgument(callback != null);
+
+        final var snapshot = mPm.snapshotComputer();
+        final int callingUid = Binder.getCallingUid();
+        if (!isCalledBySystemOrShell(callingUid)) {
+            for (var packageName : packageNames) {
+                var ps = snapshot.getPackageStateInternal(packageName);
+                if (ps == null || !TextUtils.equals(
+                        ps.getInstallSource().mInstallerPackageName, installerPackageName)) {
+                    throw new SecurityException("Caller has no access to package " + packageName);
+                }
+            }
+        }
+
+        var future = mGentleUpdateHelper.checkInstallConstraints(packageNames, constraints);
+        future.thenAccept(result -> {
+            var b = new Bundle();
+            b.putParcelable("result", result);
+            callback.sendResult(b);
+        });
+    }
+
+    @Override
     public void registerCallback(IPackageInstallerCallback callback, int userId) {
         final Computer snapshot = mPm.snapshotComputer();
         snapshot.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false,
@@ -1265,6 +1299,11 @@
     }
 
     @Override
+    public GentleUpdateHelper getGentleUpdateHelper() {
+        return mGentleUpdateHelper;
+    }
+
+    @Override
     public void bypassNextStagedInstallerCheck(boolean value) {
         if (!isCalledBySystemOrShell(Binder.getCallingUid())) {
             throw new SecurityException("Caller not allowed to bypass staged installer check");
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8f8cc8a..9e1bffb 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1560,7 +1560,7 @@
                 AndroidPackage pkg = packageState.getPkg();
                 SharedUserApi sharedUser = snapshot.getSharedUser(
                         packageState.getSharedUserAppId());
-                String oldSeInfo = AndroidPackageUtils.getSeInfo(pkg, packageState);
+                String oldSeInfo = packageState.getSeInfo();
 
                 if (pkg == null) {
                     Slog.e(TAG, "Failed to find package " + packageName);
diff --git a/services/core/java/com/android/server/pm/PackageSessionProvider.java b/services/core/java/com/android/server/pm/PackageSessionProvider.java
index ad5cf13..79b88b3 100644
--- a/services/core/java/com/android/server/pm/PackageSessionProvider.java
+++ b/services/core/java/com/android/server/pm/PackageSessionProvider.java
@@ -29,4 +29,9 @@
     PackageInstallerSession getSession(int sessionId);
 
     PackageSessionVerifier getSessionVerifier();
+
+    /**
+     * Get the GentleUpdateHelper instance.
+     */
+    GentleUpdateHelper getGentleUpdateHelper();
 }
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 6d90593..3ec6e7d 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -1358,6 +1358,17 @@
     }
 
     @Nullable
+    @Override
+    public String getSeInfo() {
+        String overrideSeInfo = getTransientState().getOverrideSeInfo();
+        if (!TextUtils.isEmpty(overrideSeInfo)) {
+            return overrideSeInfo;
+        }
+
+        return getTransientState().getSeInfo();
+    }
+
+    @Nullable
     public String getPrimaryCpuAbiLegacy() {
         return mPrimaryCpuAbi;
     }
@@ -1518,10 +1529,10 @@
     }
 
     @DataClass.Generated(
-            time = 1662666062860L,
+            time = 1665779003744L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java",
-            inputSignatures = "private  int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate  int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate  float mLoadingProgress\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate  long mLastModifiedTime\nprivate  long lastUpdateTime\nprivate  long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate  boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate  int categoryOverride\nprivate  boolean updateAvailable\nprivate  boolean forceQueryableOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic  com.android.server.pm.PackageSetting snapshot()\npublic  void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic  com.android.server.pm.PackageSetting setAppId(int)\npublic  com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic  com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic  com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic  com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic  com.android.server.pm.PackageSetting setInstallerPackageName(java.lang.String)\npublic  com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n  com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic  com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic  com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic  com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic  com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic  boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic  com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic  com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic  com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic  void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected  void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  void updateFrom(com.android.server.pm.PackageSetting)\n  com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic  com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic  boolean isPrivileged()\npublic  boolean isOem()\npublic  boolean isVendor()\npublic  boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic  boolean isSystemExt()\npublic  boolean isOdm()\npublic  boolean isSystem()\npublic  android.content.pm.SigningDetails getSigningDetails()\npublic  com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic  void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic  com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n  void setEnabled(int,int,java.lang.String)\n  int getEnabled(int)\n  void setInstalled(boolean,int)\n  boolean getInstalled(int)\n  int getInstallReason(int)\n  void setInstallReason(int,int)\n  int getUninstallReason(int)\n  void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n  boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n  boolean isAnyInstalled(int[])\n  int[] queryInstalledUsers(int[],boolean)\n  long getCeDataInode(int)\n  void setCeDataInode(long,int)\n  boolean getStopped(int)\n  void setStopped(boolean,int)\n  boolean getNotLaunched(int)\n  void setNotLaunched(boolean,int)\n  boolean getHidden(int)\n  void setHidden(boolean,int)\n  int getDistractionFlags(int)\n  void setDistractionFlags(int,int)\npublic  boolean getInstantApp(int)\n  void setInstantApp(boolean,int)\n  boolean getVirtualPreload(int)\n  void setVirtualPreload(boolean,int)\n  void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long)\n  void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n  void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n  void addDisabledComponent(java.lang.String,int)\n  void addEnabledComponent(java.lang.String,int)\n  boolean enableComponentLPw(java.lang.String,int)\n  boolean disableComponentLPw(java.lang.String,int)\n  boolean restoreComponentLPw(java.lang.String,int)\n  int getCurrentEnabledStateLPr(java.lang.String,int)\n  void removeUser(int)\npublic  int[] getNotInstalledUserIds()\n  void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected  void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\n  com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic  void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic  boolean isLoading()\npublic  com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getUsesLibraries()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic  com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic  com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic  com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic  com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic  com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
+            inputSignatures = "private  int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate  int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate  float mLoadingProgress\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate  long mLastModifiedTime\nprivate  long lastUpdateTime\nprivate  long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate  boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate  int categoryOverride\nprivate  boolean updateAvailable\nprivate  boolean forceQueryableOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic  com.android.server.pm.PackageSetting snapshot()\npublic  void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic  com.android.server.pm.PackageSetting setAppId(int)\npublic  com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic  com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic  com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic  com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic  com.android.server.pm.PackageSetting setInstallerPackageName(java.lang.String)\npublic  com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n  com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic  com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic  com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic  com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic  com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic  boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic  com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic  com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic  com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic  void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected  void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  void updateFrom(com.android.server.pm.PackageSetting)\n  com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic  com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic  boolean isPrivileged()\npublic  boolean isOem()\npublic  boolean isVendor()\npublic  boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic  boolean isSystemExt()\npublic  boolean isOdm()\npublic  boolean isSystem()\npublic  android.content.pm.SigningDetails getSigningDetails()\npublic  com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic  void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic  com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n  void setEnabled(int,int,java.lang.String)\n  int getEnabled(int)\n  void setInstalled(boolean,int)\n  boolean getInstalled(int)\n  int getInstallReason(int)\n  void setInstallReason(int,int)\n  int getUninstallReason(int)\n  void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n  boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n  boolean isAnyInstalled(int[])\n  int[] queryInstalledUsers(int[],boolean)\n  long getCeDataInode(int)\n  void setCeDataInode(long,int)\n  boolean getStopped(int)\n  void setStopped(boolean,int)\n  boolean getNotLaunched(int)\n  void setNotLaunched(boolean,int)\n  boolean getHidden(int)\n  void setHidden(boolean,int)\n  int getDistractionFlags(int)\n  void setDistractionFlags(int,int)\npublic  boolean getInstantApp(int)\n  void setInstantApp(boolean,int)\n  boolean getVirtualPreload(int)\n  void setVirtualPreload(boolean,int)\n  void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long)\n  void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n  void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n  void addDisabledComponent(java.lang.String,int)\n  void addEnabledComponent(java.lang.String,int)\n  boolean enableComponentLPw(java.lang.String,int)\n  boolean disableComponentLPw(java.lang.String,int)\n  boolean restoreComponentLPw(java.lang.String,int)\n  int getCurrentEnabledStateLPr(java.lang.String,int)\n  void removeUser(int)\npublic  int[] getNotInstalledUserIds()\n  void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected  void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\n  com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic  void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic  boolean isLoading()\npublic  com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getUsesLibraries()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic  com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic  com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic  com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic  com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic  com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index a905df9..6572d7b 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -265,8 +265,8 @@
             pkgSetting.getPkgState().setUpdatedSystemApp(true);
         }
 
-        parsedPackage.setSeInfo(SELinuxMMAC.getSeInfo(parsedPackage, sharedUserSetting,
-                injector.getCompatibility()));
+        pkgSetting.getTransientState().setSeInfo(SELinuxMMAC.getSeInfo(parsedPackage,
+                sharedUserSetting, injector.getCompatibility()));
 
         if (parsedPackage.isSystem()) {
             configurePackageComponents(parsedPackage);
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index a40d404..4aba016 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -102,7 +102,6 @@
 import com.android.server.backup.PreferredActivityBackupHelper;
 import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.parsing.PackageInfoUtils;
-import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.permission.LegacyPermissionDataProvider;
 import com.android.server.pm.permission.LegacyPermissionSettings;
 import com.android.server.pm.permission.LegacyPermissionState;
@@ -2900,7 +2899,7 @@
                 sb.append(isDebug ? " 1 " : " 0 ");
                 sb.append(dataPath);
                 sb.append(" ");
-                sb.append(AndroidPackageUtils.getSeInfo(pkg.getPkg(), pkg));
+                sb.append(pkg.getSeInfo());
                 sb.append(" ");
                 final int gidsSize = gids.size();
                 if (gids != null && gids.size() > 0) {
@@ -4359,7 +4358,7 @@
                     // (CE storage is not ready yet; the CE data directories will be created later,
                     // when the user is "unlocked".)  Accumulate all required args, and call the
                     // installer after the mPackages lock has been released.
-                    final String seInfo = AndroidPackageUtils.getSeInfo(ps.getPkg(), ps);
+                    final String seInfo = ps.getSeInfo();
                     final boolean usesSdk = !ps.getPkg().getUsesSdkLibraries().isEmpty();
                     final CreateAppDataArgs args = Installer.buildCreateAppDataArgs(
                             ps.getVolumeUuid(), ps.getPackageName(), userHandle,
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 1da442b..74594cc 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -53,7 +53,6 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
-import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.PackageStateUtils;
@@ -347,7 +346,7 @@
             // an update, and hence need to restore data for all installed users.
             final int[] installedUsers = PackageStateUtils.queryInstalledUsers(ps, allUsers, true);
 
-            final String seInfo = AndroidPackageUtils.getSeInfo(pkg, ps);
+            final String seInfo = ps.getSeInfo();
             rm.snapshotAndRestoreUserData(packageName, UserHandle.toUserHandles(installedUsers),
                     appId, ceDataInode, seInfo, 0 /*token*/);
         }
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 1027f4c..0f920c6 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -435,10 +435,20 @@
     /** Removes a {@link UserVisibilityListener}. */
     public abstract void removeUserVisibilityListener(UserVisibilityListener listener);
 
-    /** TODO(b/244333150): temporary method until UserVisibilityMediator handles that logic */
-    public abstract void onUserVisibilityChanged(@UserIdInt int userId, boolean visible);
+    // TODO(b/242195409): remove this method if not needed anymore
+    /** Notify {@link UserVisibilityListener listeners} that the visibility of the
+     * {@link android.os.UserHandle#USER_SYSTEM} changed. */
+    public abstract void onSystemUserVisibilityChanged(boolean visible);
 
     /** Return the integer types of the given user IDs. Only used for reporting metrics to statsd.
      */
     public abstract int[] getUserTypesForStatsd(@UserIdInt int[] userIds);
+
+    /**
+     * Returns the user id of the main user, or {@link android.os.UserHandle#USER_NULL} if there is
+     * no main user.
+     *
+     * @see UserManager#isMainUser()
+     */
+    public abstract @UserIdInt int getMainUserId();
 }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 23a6b67..3234e87 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -97,7 +97,6 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
-import android.util.EventLog;
 import android.util.IndentingPrintWriter;
 import android.util.IntArray;
 import android.util.Slog;
@@ -126,7 +125,6 @@
 import com.android.server.LocalServices;
 import com.android.server.LockGuard;
 import com.android.server.SystemService;
-import com.android.server.am.EventLogTags;
 import com.android.server.am.UserState;
 import com.android.server.pm.UserManagerInternal.UserLifecycleListener;
 import com.android.server.pm.UserManagerInternal.UserRestrictionsListener;
@@ -191,6 +189,7 @@
     private static final String ATTR_CREATION_TIME = "created";
     private static final String ATTR_LAST_LOGGED_IN_TIME = "lastLoggedIn";
     private static final String ATTR_LAST_LOGGED_IN_FINGERPRINT = "lastLoggedInFingerprint";
+    private static final String ATTR_LAST_ENTERED_FOREGROUND_TIME = "lastEnteredForeground";
     private static final String ATTR_SERIAL_NO = "serialNumber";
     private static final String ATTR_NEXT_SERIAL_NO = "nextSerialNumber";
     private static final String ATTR_PARTIAL = "partial";
@@ -341,6 +340,9 @@
         /** Elapsed realtime since boot when the user was unlocked. */
         long unlockRealtime;
 
+        /** Wall clock time in millis when the user last entered the foreground. */
+        long mLastEnteredForegroundTimeMillis;
+
         private long mLastRequestQuietModeEnabledMillis;
 
         /**
@@ -508,10 +510,6 @@
     @GuardedBy("mUserLifecycleListeners")
     private final ArrayList<UserLifecycleListener> mUserLifecycleListeners = new ArrayList<>();
 
-    // TODO(b/244333150): temporary array, should belong to UserVisibilityMediator
-    @GuardedBy("mUserVisibilityListeners")
-    private final ArrayList<UserVisibilityListener> mUserVisibilityListeners = new ArrayList<>();
-
     private final LockPatternUtils mLockPatternUtils;
 
     private final String ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK =
@@ -680,6 +678,10 @@
                 final UserData user = mUms.getUserDataLU(targetUser.getUserIdentifier());
                 if (user != null) {
                     user.startRealtime = SystemClock.elapsedRealtime();
+                    if (targetUser.getUserIdentifier() == UserHandle.USER_SYSTEM
+                            && targetUser.isFull()) {
+                        mUms.setLastEnteredForegroundTimeToNow(user);
+                    }
                 }
             }
         }
@@ -695,6 +697,16 @@
         }
 
         @Override
+        public void onUserSwitching(@NonNull TargetUser from, @NonNull TargetUser to) {
+            synchronized (mUms.mUsersLock) {
+                final UserData user = mUms.getUserDataLU(to.getUserIdentifier());
+                if (user != null) {
+                    mUms.setLastEnteredForegroundTimeToNow(user);
+                }
+            }
+        }
+
+        @Override
         public void onUserStopping(@NonNull TargetUser targetUser) {
             synchronized (mUms.mUsersLock) {
                 final UserData user = mUms.getUserDataLU(targetUser.getUserIdentifier());
@@ -901,6 +913,49 @@
         return null;
     }
 
+    @Override
+    public @UserIdInt int getMainUserId() {
+        checkQueryOrCreateUsersPermission("get main user id");
+        return getMainUserIdUnchecked();
+    }
+
+    private @UserIdInt int getMainUserIdUnchecked() {
+        synchronized (mUsersLock) {
+            final int userSize = mUsers.size();
+            for (int i = 0; i < userSize; i++) {
+                final UserInfo user = mUsers.valueAt(i).info;
+                if (user.isMain() && !mRemovingUserIds.get(user.id)) {
+                    return user.id;
+                }
+            }
+        }
+        return UserHandle.USER_NULL;
+    }
+
+    @Override
+    public int getPreviousFullUserToEnterForeground() {
+        checkQueryOrCreateUsersPermission("get previous user");
+        int previousUser = UserHandle.USER_NULL;
+        long latestEnteredTime = 0;
+        final int currentUser = getCurrentUserId();
+        synchronized (mUsersLock) {
+            final int userSize = mUsers.size();
+            for (int i = 0; i < userSize; i++) {
+                final UserData userData = mUsers.valueAt(i);
+                final int userId = userData.info.id;
+                if (userId != currentUser && userData.info.isFull() && !userData.info.partial
+                        && !mRemovingUserIds.get(userId)) {
+                    final long userEnteredTime = userData.mLastEnteredForegroundTimeMillis;
+                    if (userEnteredTime > latestEnteredTime) {
+                        latestEnteredTime = userEnteredTime;
+                        previousUser = userId;
+                    }
+                }
+            }
+        }
+        return previousUser;
+    }
+
     public @NonNull List<UserInfo> getUsers(boolean excludeDying) {
         return getUsers(/*excludePartial= */ true, excludeDying, /* excludePreCreated= */
                 true);
@@ -3339,13 +3394,13 @@
                     Slogf.wtf(LOG_TAG, "emulateSystemUserModeIfNeeded(): no system user data");
                     return;
                 }
+                final int oldMainUserId = getMainUserIdUnchecked();
                 final int oldFlags = systemUserData.info.flags;
                 final int newFlags;
                 final String newUserType;
-                // TODO(b/256624031): Also handle FLAG_MAIN
                 if (newHeadlessSystemUserMode) {
                     newUserType = UserManager.USER_TYPE_SYSTEM_HEADLESS;
-                    newFlags = oldFlags & ~UserInfo.FLAG_FULL;
+                    newFlags = oldFlags & ~UserInfo.FLAG_FULL & ~UserInfo.FLAG_MAIN;
                 } else {
                     newUserType = UserManager.USER_TYPE_FULL_SYSTEM;
                     newFlags = oldFlags | UserInfo.FLAG_FULL;
@@ -3360,9 +3415,38 @@
                         + "%s, flags changed from %s to %s",
                         systemUserData.info.userType, newUserType,
                         UserInfo.flagsToString(oldFlags), UserInfo.flagsToString(newFlags));
+
                 systemUserData.info.userType = newUserType;
                 systemUserData.info.flags = newFlags;
                 writeUserLP(systemUserData);
+
+                // Switch the MainUser to a reasonable choice if needed.
+                // (But if there was no MainUser, we deliberately continue to have no MainUser.)
+                final UserData oldMain = getUserDataNoChecks(oldMainUserId);
+                if (newHeadlessSystemUserMode) {
+                    if (oldMain != null && (oldMain.info.flags & UserInfo.FLAG_SYSTEM) != 0) {
+                        // System was MainUser. So we need a new choice for Main. Pick the oldest.
+                        // If no oldest, don't set any. Let the BootUserInitializer do that later.
+                        final UserInfo newMainUser = getEarliestCreatedFullUser();
+                        if (newMainUser != null) {
+                            Slogf.i(LOG_TAG, "Designating user " + newMainUser.id + " to be Main");
+                            newMainUser.flags |= UserInfo.FLAG_MAIN;
+                            writeUserLP(getUserDataNoChecks(newMainUser.id));
+                        }
+                    }
+                } else {
+                    // TODO(b/256624031): For now, we demand the Main user (if there is one) is
+                    //  always the system in non-HSUM. In the future, when we relax this, change how
+                    //  we handle MAIN.
+                    if (oldMain != null && (oldMain.info.flags & UserInfo.FLAG_SYSTEM) == 0) {
+                        // Someone else was the MainUser; transfer it to System.
+                        Slogf.i(LOG_TAG, "Transferring Main to user 0 from " + oldMain.info.id);
+                        oldMain.info.flags &= ~UserInfo.FLAG_MAIN;
+                        systemUserData.info.flags |= UserInfo.FLAG_MAIN;
+                        writeUserLP(oldMain);
+                        writeUserLP(systemUserData);
+                    }
+                }
             }
         }
 
@@ -3639,8 +3723,10 @@
             // Add FLAG_MAIN
             if (isHeadlessSystemUserMode()) {
                 final UserInfo earliestCreatedUser = getEarliestCreatedFullUser();
-                earliestCreatedUser.flags |= UserInfo.FLAG_MAIN;
-                userIdsToWrite.add(earliestCreatedUser.id);
+                if (earliestCreatedUser != null) {
+                    earliestCreatedUser.flags |= UserInfo.FLAG_MAIN;
+                    userIdsToWrite.add(earliestCreatedUser.id);
+                }
             } else {
                 synchronized (mUsersLock) {
                     final UserData userData = mUsers.get(UserHandle.USER_SYSTEM);
@@ -3780,9 +3866,10 @@
         userInfo.profileBadge = getFreeProfileBadgeLU(userInfo.profileGroupId, userInfo.userType);
     }
 
-    private UserInfo getEarliestCreatedFullUser() {
+    /** Returns the oldest Full Admin user, or null is if there none. */
+    private @Nullable UserInfo getEarliestCreatedFullUser() {
         final List<UserInfo> users = getUsersInternal(true, true, true);
-        UserInfo earliestUser = users.get(0);
+        UserInfo earliestUser = null;
         long earliestCreationTime = Long.MAX_VALUE;
         for (int i = 0; i < users.size(); i++) {
             final UserInfo info = users.get(i);
@@ -3922,6 +4009,8 @@
             serializer.attribute(null, ATTR_LAST_LOGGED_IN_FINGERPRINT,
                     userInfo.lastLoggedInFingerprint);
         }
+        serializer.attributeLong(
+                null, ATTR_LAST_ENTERED_FOREGROUND_TIME, userData.mLastEnteredForegroundTimeMillis);
         if (userInfo.iconPath != null) {
             serializer.attribute(null,  ATTR_ICON_PATH, userInfo.iconPath);
         }
@@ -4095,6 +4184,7 @@
         long lastLoggedInTime = 0L;
         long lastRequestQuietModeEnabledTimestamp = 0L;
         String lastLoggedInFingerprint = null;
+        long lastEnteredForegroundTime = 0L;
         int profileGroupId = UserInfo.NO_PROFILE_GROUP_ID;
         int profileBadge = 0;
         int restrictedProfileParentId = UserInfo.NO_PROFILE_GROUP_ID;
@@ -4140,6 +4230,8 @@
             lastLoggedInTime = parser.getAttributeLong(null, ATTR_LAST_LOGGED_IN_TIME, 0);
             lastLoggedInFingerprint = parser.getAttributeValue(null,
                     ATTR_LAST_LOGGED_IN_FINGERPRINT);
+            lastEnteredForegroundTime =
+                    parser.getAttributeLong(null, ATTR_LAST_ENTERED_FOREGROUND_TIME, 0L);
             profileGroupId = parser.getAttributeInt(null, ATTR_PROFILE_GROUP_ID,
                     UserInfo.NO_PROFILE_GROUP_ID);
             profileBadge = parser.getAttributeInt(null, ATTR_PROFILE_BADGE, 0);
@@ -4234,6 +4326,7 @@
         userData.seedAccountOptions = seedAccountOptions;
         userData.userProperties = userProperties;
         userData.setLastRequestQuietModeEnabledMillis(lastRequestQuietModeEnabledTimestamp);
+        userData.mLastEnteredForegroundTimeMillis = lastEnteredForegroundTime;
         if (ignorePrepareStorageErrors) {
             userData.setIgnorePrepareStorageErrors();
         }
@@ -6153,6 +6246,11 @@
                         || someUserHasSeedAccountNoChecks(accountName, accountType));
     }
 
+    private void setLastEnteredForegroundTimeToNow(@NonNull UserData userData) {
+        userData.mLastEnteredForegroundTimeMillis = System.currentTimeMillis();
+        scheduleWriteUser(userData);
+    }
+
     @Override
     public void onShellCommand(FileDescriptor in, FileDescriptor out,
             FileDescriptor err, String[] args, ShellCallback callback,
@@ -6279,9 +6377,6 @@
         synchronized (mUserLifecycleListeners) {
             pw.println("  user lifecycle events: " + mUserLifecycleListeners.size());
         }
-        synchronized (mUserVisibilityListeners) {
-            pw.println("  user visibility events: " + mUserVisibilityListeners.size());
-        }
 
         // Dump UserTypes
         pw.println();
@@ -6377,6 +6472,9 @@
         pw.print("    Unlock time: ");
         dumpTimeAgo(pw, tempStringBuilder, nowRealtime, userData.unlockRealtime);
 
+        pw.print("    Last entered foreground: ");
+        dumpTimeAgo(pw, tempStringBuilder, now, userData.mLastEnteredForegroundTimeMillis);
+
         pw.print("    Has profile owner: ");
         pw.println(mIsUserManaged.get(userId));
         pw.println("    Restrictions:");
@@ -6854,31 +6952,17 @@
 
         @Override
         public void addUserVisibilityListener(UserVisibilityListener listener) {
-            synchronized (mUserVisibilityListeners) {
-                mUserVisibilityListeners.add(listener);
-            }
+            mUserVisibilityMediator.addListener(listener);
         }
 
         @Override
         public void removeUserVisibilityListener(UserVisibilityListener listener) {
-            synchronized (mUserVisibilityListeners) {
-                mUserVisibilityListeners.remove(listener);
-            }
+            mUserVisibilityMediator.removeListener(listener);
         }
 
         @Override
-        public void onUserVisibilityChanged(@UserIdInt int userId, boolean visible) {
-            EventLog.writeEvent(EventLogTags.UM_USER_VISIBILITY_CHANGED, userId, visible ? 1 : 0);
-            mHandler.post(() -> {
-                UserVisibilityListener[] listeners;
-                synchronized (mUserVisibilityListeners) {
-                    listeners = new UserVisibilityListener[mUserVisibilityListeners.size()];
-                    mUserVisibilityListeners.toArray(listeners);
-                }
-                for (UserVisibilityListener listener : listeners) {
-                    listener.onUserVisibilityChanged(userId, visible);
-                }
-            });
+        public void onSystemUserVisibilityChanged(boolean visible) {
+            mUserVisibilityMediator.onSystemUserVisibilityChanged(visible);
         }
 
         @Override
@@ -6898,6 +6982,12 @@
             }
             return userTypes;
         }
+
+        @Override
+        public @UserIdInt int getMainUserId() {
+            return getMainUserIdUnchecked();
+        }
+
     } // class LocalService
 
 
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index 2650b23..9b9ca10 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -32,6 +32,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Dumpable;
+import android.util.EventLog;
 import android.util.IndentingPrintWriter;
 import android.util.IntArray;
 import android.util.SparseIntArray;
@@ -40,6 +41,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
+import com.android.server.am.EventLogTags;
 import com.android.server.pm.UserManagerInternal.UserAssignmentResult;
 import com.android.server.pm.UserManagerInternal.UserVisibilityListener;
 import com.android.server.utils.Slogf;
@@ -68,6 +70,7 @@
 public final class UserVisibilityMediator implements Dumpable {
 
     private static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
+    private static final boolean VERBOSE = false; // DO NOT SUBMIT WITH TRUE
 
     private static final String TAG = UserVisibilityMediator.class.getSimpleName();
 
@@ -381,8 +384,8 @@
     public boolean isUserVisible(@UserIdInt int userId) {
         // First check current foreground user and their profiles (on main display)
         if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
-            if (DBG) {
-                Slogf.d(TAG, "isUserVisible(%d): true to current user or profile", userId);
+            if (VERBOSE) {
+                Slogf.v(TAG, "isUserVisible(%d): true to current user or profile", userId);
             }
             return true;
         }
@@ -517,6 +520,14 @@
         }
     }
 
+    // TODO(b/242195409): remove this method if not needed anymore
+    /**
+     * Nofify all listeners that the system user visibility changed.
+     */
+    void onSystemUserVisibilityChanged(boolean visible) {
+        dispatchVisibilityChanged(mListeners, USER_SYSTEM, visible);
+    }
+
     /**
      * Nofify all listeners about the visibility changes from before / after a change of state.
      */
@@ -534,7 +545,7 @@
             Slogf.d(TAG,
                     "dispatchVisibilityChanged(): visibleUsersBefore=%s, visibleUsersAfter=%s, "
                     + "%d listeners (%s)", visibleUsersBefore, visibleUsersAfter, listeners.size(),
-                    mListeners);
+                    listeners);
         }
         for (int i = 0; i < visibleUsersBefore.size(); i++) {
             int userId = visibleUsersBefore.get(i);
@@ -552,13 +563,14 @@
 
     private void dispatchVisibilityChanged(CopyOnWriteArrayList<UserVisibilityListener> listeners,
             @UserIdInt int userId, boolean visible) {
+        EventLog.writeEvent(EventLogTags.UM_USER_VISIBILITY_CHANGED, userId, visible ? 1 : 0);
         if (DBG) {
             Slogf.d(TAG, "dispatchVisibilityChanged(%d -> %b): sending to %d listeners",
                     userId, visible, listeners.size());
         }
         for (int i = 0; i < mListeners.size(); i++) {
             UserVisibilityListener listener =  mListeners.get(i);
-            if (DBG) {
+            if (VERBOSE) {
                 Slogf.v(TAG, "dispatchVisibilityChanged(%d -> %b): sending to %s",
                         userId, visible, listener);
             }
@@ -575,9 +587,7 @@
             ipw.println(mCurrentUserId);
 
             ipw.print("Visible users: ");
-            // TODO: merge 2 lines below if/when IntArray implements toString()...
-            IntArray visibleUsers = getVisibleUsers();
-            ipw.println(java.util.Arrays.toString(visibleUsers.toArray()));
+            ipw.println(getVisibleUsers());
 
             dumpSparseIntArray(ipw, mStartedProfileGroupIds, "started user / profile group",
                     "u", "pg");
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index a7d4cea..558202b 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -452,7 +452,7 @@
             info.category = pkgSetting.getCategoryOverride();
         }
 
-        info.seInfo = AndroidPackageUtils.getSeInfo(pkg, pkgSetting);
+        info.seInfo = pkgSetting.getSeInfo();
         info.primaryCpuAbi = pkgSetting.getPrimaryCpuAbi();
         info.secondaryCpuAbi = pkgSetting.getSecondaryCpuAbi();
 
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java
index 944e4ad..876bf17 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java
@@ -40,13 +40,6 @@
     String getPrimaryCpuAbi();
 
     /**
-     * @see ApplicationInfo#seInfo
-     * TODO: This field is deriveable and might not have to be cached here.
-     */
-    @Nullable
-    String getSeInfo();
-
-    /**
      * @see ApplicationInfo#secondaryCpuAbi
      */
     @Nullable
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index 5b0cc51..c76b129 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -27,7 +27,6 @@
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
 import android.os.incremental.IncrementalManager;
-import android.text.TextUtils;
 
 import com.android.internal.content.NativeLibraryHelper;
 import com.android.internal.util.ArrayUtils;
@@ -288,16 +287,6 @@
         return ((AndroidPackageHidden) pkg).getSecondaryCpuAbi();
     }
 
-    public static String getSeInfo(AndroidPackage pkg, @Nullable PackageStateInternal pkgSetting) {
-        if (pkgSetting != null) {
-            String overrideSeInfo = pkgSetting.getTransientState().getOverrideSeInfo();
-            if (!TextUtils.isEmpty(overrideSeInfo)) {
-                return overrideSeInfo;
-            }
-        }
-        return ((AndroidPackageHidden) pkg).getSeInfo();
-    }
-
     @Deprecated
     @NonNull
     public static ApplicationInfo generateAppInfoWithoutState(AndroidPackage pkg) {
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
index a43b979..ba36ab7 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
@@ -462,10 +462,6 @@
     @DataClass.ParcelWith(ForInternedString.class)
     protected String secondaryNativeLibraryDir;
 
-    @Nullable
-    @DataClass.ParcelWith(ForInternedString.class)
-    protected String seInfo;
-
     /**
      * This is an appId, the uid if the userId is == USER_SYSTEM
      */
@@ -1339,6 +1335,11 @@
     }
 
     @Override
+    public UUID getStorageUuid() {
+        return mStorageUuid;
+    }
+
+    @Override
     public int getTargetSandboxVersion() {
         return targetSandboxVersion;
     }
@@ -2905,12 +2906,6 @@
     }
 
     @Override
-    public PackageImpl setSeInfo(@Nullable String seInfo) {
-        this.seInfo = TextUtils.safeIntern(seInfo);
-        return this;
-    }
-
-    @Override
     public PackageImpl setSplitCodePaths(@Nullable String[] splitCodePaths) {
         this.splitCodePaths = splitCodePaths;
         if (splitCodePaths != null) {
@@ -2993,7 +2988,6 @@
         appInfo.primaryCpuAbi = primaryCpuAbi;
         appInfo.secondaryCpuAbi = secondaryCpuAbi;
         appInfo.secondaryNativeLibraryDir = secondaryNativeLibraryDir;
-        appInfo.seInfo = seInfo;
         appInfo.seInfoUser = SELinuxUtil.COMPLETE_STR;
         appInfo.uid = uid;
         return appInfo;
@@ -3147,7 +3141,6 @@
         sForInternedString.parcel(this.primaryCpuAbi, dest, flags);
         sForInternedString.parcel(this.secondaryCpuAbi, dest, flags);
         dest.writeString(this.secondaryNativeLibraryDir);
-        dest.writeString(this.seInfo);
         dest.writeInt(this.uid);
         dest.writeLong(this.mBooleans);
         dest.writeLong(this.mBooleans2);
@@ -3307,7 +3300,6 @@
         this.primaryCpuAbi = sForInternedString.unparcel(in);
         this.secondaryCpuAbi = sForInternedString.unparcel(in);
         this.secondaryNativeLibraryDir = in.readString();
-        this.seInfo = in.readString();
         this.uid = in.readInt();
         this.mBooleans = in.readLong();
         this.mBooleans2 = in.readLong();
@@ -3377,12 +3369,6 @@
         return secondaryNativeLibraryDir;
     }
 
-    @Nullable
-    @Override
-    public String getSeInfo() {
-        return seInfo;
-    }
-
     @Override
     public boolean isCoreApp() {
         return getBoolean(Booleans.CORE_APP);
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java b/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java
index d306341..aeaff6d 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java
@@ -103,8 +103,6 @@
 
     ParsedPackage setRestrictUpdateHash(byte[] restrictUpdateHash);
 
-    ParsedPackage setSeInfo(String seInfo);
-
     ParsedPackage setSecondaryNativeLibraryDir(String secondaryNativeLibraryDir);
 
     /**
diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
index e3dad45..84907a5 100644
--- a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -34,6 +34,7 @@
 import android.content.pm.ServiceInfo;
 import android.content.pm.SigningDetails;
 import android.os.Bundle;
+import android.os.storage.StorageManager;
 import android.processor.immutability.Immutable;
 import android.util.ArraySet;
 import android.util.Pair;
@@ -58,6 +59,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.UUID;
 
 /**
  * The representation of an application on disk, as parsed from its split APKs' manifests.
@@ -111,6 +113,13 @@
     String getStaticSharedLibraryName();
 
     /**
+     * @return The {@link UUID} for use with {@link StorageManager} APIs identifying where this
+     * package was installed.
+     */
+    @NonNull
+    UUID getStorageUuid();
+
+    /**
      * @see ApplicationInfo#targetSdkVersion
      * @see R.styleable#AndroidManifestUsesSdk_targetSdkVersion
      */
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index 3c79cdf..e8d0640 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -131,6 +131,14 @@
     String getSecondaryCpuAbi();
 
     /**
+     * @see ApplicationInfo#seInfo
+     * @return The SE info for this package, which may be overridden by a system configured value,
+     * or null if the package isn't available.
+     */
+    @Nullable
+    String getSeInfo();
+
+    /**
      * @see AndroidPackage#isPrivileged()
      */
     boolean isPrivileged();
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
index c6ce40e..e552a34 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
@@ -129,6 +129,8 @@
     private final String mPrimaryCpuAbi;
     @Nullable
     private final String mSecondaryCpuAbi;
+    @Nullable
+    private final String mSeInfo;
     private final boolean mHasSharedUser;
     private final int mSharedUserAppId;
     @NonNull
@@ -175,6 +177,7 @@
         mPath = pkgState.getPath();
         mPrimaryCpuAbi = pkgState.getPrimaryCpuAbi();
         mSecondaryCpuAbi = pkgState.getSecondaryCpuAbi();
+        mSeInfo = pkgState.getSeInfo();
         mHasSharedUser = pkgState.hasSharedUser();
         mSharedUserAppId = pkgState.getSharedUserAppId();
         mUsesSdkLibraries = pkgState.getUsesSdkLibraries();
@@ -542,7 +545,7 @@
         }
 
         @DataClass.Generated(
-                time = 1661977809886L,
+                time = 1665778832625L,
                 codegenVersion = "1.0.23",
                 sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
                 inputSignatures = "private  int mBooleans\nprivate final  long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final  int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final  long mFirstInstallTime\npublic static  com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final  int HIDDEN\nprivate static final  int INSTALLED\nprivate static final  int INSTANT_APP\nprivate static final  int NOT_LAUNCHED\nprivate static final  int STOPPED\nprivate static final  int SUSPENDED\nprivate static final  int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
@@ -641,6 +644,11 @@
     }
 
     @DataClass.Generated.Member
+    public @Nullable String getSeInfo() {
+        return mSeInfo;
+    }
+
+    @DataClass.Generated.Member
     public boolean isHasSharedUser() {
         return mHasSharedUser;
     }
@@ -697,10 +705,10 @@
     }
 
     @DataClass.Generated(
-            time = 1661977809932L,
+            time = 1665778832668L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
-            inputSignatures = "private  int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackage mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final  int mAppId\nprivate final  int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final  long mLastModifiedTime\nprivate final  long mLastUpdateTime\nprivate final  long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final  boolean mHasSharedUser\nprivate final  int mSharedUserAppId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibrary> mUsesLibraries\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\npublic static  com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override int getSharedUserAppId()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final  int SYSTEM\nprivate static final  int EXTERNAL_STORAGE\nprivate static final  int PRIVILEGED\nprivate static final  int OEM\nprivate static final  int VENDOR\nprivate static final  int PRODUCT\nprivate static final  int SYSTEM_EXT\nprivate static final  int REQUIRED_FOR_SYSTEM_USER\nprivate static final  int ODM\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nprivate static final  int HIDDEN_UNTIL_INSTALLED\nprivate static final  int INSTALL_PERMISSIONS_FIXED\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int UPDATED_SYSTEM_APP\nprivate static final  int APK_IN_UPDATED_APEX\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
+            inputSignatures = "private  int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackage mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final  int mAppId\nprivate final  int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final  long mLastModifiedTime\nprivate final  long mLastUpdateTime\nprivate final  long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSeInfo\nprivate final  boolean mHasSharedUser\nprivate final  int mSharedUserAppId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibrary> mUsesLibraries\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\npublic static  com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override int getSharedUserAppId()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final  int SYSTEM\nprivate static final  int EXTERNAL_STORAGE\nprivate static final  int PRIVILEGED\nprivate static final  int OEM\nprivate static final  int VENDOR\nprivate static final  int PRODUCT\nprivate static final  int SYSTEM_EXT\nprivate static final  int REQUIRED_FOR_SYSTEM_USER\nprivate static final  int ODM\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nprivate static final  int HIDDEN_UNTIL_INSTALLED\nprivate static final  int INSTALL_PERMISSIONS_FIXED\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int UPDATED_SYSTEM_APP\nprivate static final  int APK_IN_UPDATED_APEX\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java b/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
index b22c038..57fbfe9 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
@@ -22,6 +22,7 @@
 import android.annotation.Nullable;
 import android.content.pm.PackageManager;
 import android.content.pm.SharedLibraryInfo;
+import android.text.TextUtils;
 
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DataClass;
@@ -62,6 +63,9 @@
     @Nullable
     private String overrideSeInfo;
 
+    @NonNull
+    private String seInfo;
+
     // TODO: Remove in favor of finer grained change notification
     @NonNull
     private final PackageSetting mPackageSetting;
@@ -138,6 +142,7 @@
         this.apkInUpdatedApex = other.apkInUpdatedApex;
         this.lastPackageUsageTimeInMills = other.lastPackageUsageTimeInMills;
         this.overrideSeInfo = other.overrideSeInfo;
+        this.seInfo = other.seInfo;
         mPackageSetting.onChanged();
     }
 
@@ -206,6 +211,13 @@
         return this;
     }
 
+    @NonNull
+    public PackageStateUnserialized setSeInfo(@NonNull String value) {
+        seInfo = TextUtils.safeIntern(value);
+        mPackageSetting.onChanged();
+        return this;
+    }
+
 
 
     // Code below generated by codegen v1.0.23.
@@ -271,15 +283,20 @@
     }
 
     @DataClass.Generated.Member
+    public @NonNull String getSeInfo() {
+        return seInfo;
+    }
+
+    @DataClass.Generated.Member
     public @NonNull PackageSetting getPackageSetting() {
         return mPackageSetting;
     }
 
     @DataClass.Generated(
-            time = 1661373697219L,
+            time = 1666291743725L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java",
-            inputSignatures = "private  boolean hiddenUntilInstalled\nprivate @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibraryWrapper> usesLibraryInfos\nprivate @android.annotation.NonNull java.util.List<java.lang.String> usesLibraryFiles\nprivate  boolean updatedSystemApp\nprivate  boolean apkInApex\nprivate  boolean apkInUpdatedApex\nprivate volatile @android.annotation.NonNull long[] lastPackageUsageTimeInMills\nprivate @android.annotation.Nullable java.lang.String overrideSeInfo\nprivate final @android.annotation.NonNull com.android.server.pm.PackageSetting mPackageSetting\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryInfo(com.android.server.pm.pkg.SharedLibraryWrapper)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryFile(java.lang.String)\nprivate  long[] lazyInitLastPackageUsageTimeInMills()\npublic  com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(int,long)\npublic  long getLatestPackageUseTimeInMills()\npublic  long getLatestForegroundPackageUseTimeInMills()\npublic  void updateFrom(com.android.server.pm.pkg.PackageStateUnserialized)\npublic @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> getNonNativeUsesLibraryInfos()\npublic  com.android.server.pm.pkg.PackageStateUnserialized setHiddenUntilInstalled(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryInfos(java.util.List<android.content.pm.SharedLibraryInfo>)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryFiles(java.util.List<java.lang.String>)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUpdatedSystemApp(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setApkInApex(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setApkInUpdatedApex(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(long)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setOverrideSeInfo(java.lang.String)\nclass PackageStateUnserialized extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genConstructor=false, genBuilder=false)")
+            inputSignatures = "private  boolean hiddenUntilInstalled\nprivate @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibraryWrapper> usesLibraryInfos\nprivate @android.annotation.NonNull java.util.List<java.lang.String> usesLibraryFiles\nprivate  boolean updatedSystemApp\nprivate  boolean apkInApex\nprivate  boolean apkInUpdatedApex\nprivate volatile @android.annotation.NonNull long[] lastPackageUsageTimeInMills\nprivate @android.annotation.Nullable java.lang.String overrideSeInfo\nprivate @android.annotation.NonNull java.lang.String seInfo\nprivate final @android.annotation.NonNull com.android.server.pm.PackageSetting mPackageSetting\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryInfo(com.android.server.pm.pkg.SharedLibraryWrapper)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryFile(java.lang.String)\nprivate  long[] lazyInitLastPackageUsageTimeInMills()\npublic  com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(int,long)\npublic  long getLatestPackageUseTimeInMills()\npublic  long getLatestForegroundPackageUseTimeInMills()\npublic  void updateFrom(com.android.server.pm.pkg.PackageStateUnserialized)\npublic @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> getNonNativeUsesLibraryInfos()\npublic  com.android.server.pm.pkg.PackageStateUnserialized setHiddenUntilInstalled(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryInfos(java.util.List<android.content.pm.SharedLibraryInfo>)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryFiles(java.util.List<java.lang.String>)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUpdatedSystemApp(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setApkInApex(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setApkInUpdatedApex(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(long)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setOverrideSeInfo(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized setSeInfo(java.lang.String)\nclass PackageStateUnserialized extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genConstructor=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 3aa333a..e9c93ee 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -4208,11 +4208,13 @@
             wakeUpFromWakeKey(event);
         }
 
-        if ((result & ACTION_PASS_TO_USER) != 0) {
+        if ((result & ACTION_PASS_TO_USER) != 0 && !mPerDisplayFocusEnabled
+                && displayId != INVALID_DISPLAY && displayId != mTopFocusedDisplayId) {
             // If the key event is targeted to a specific display, then the user is interacting with
-            // that display. Therefore, give focus to the display that the user is interacting with.
-            if (!mPerDisplayFocusEnabled
-                    && displayId != INVALID_DISPLAY && displayId != mTopFocusedDisplayId) {
+            // that display. Therefore, give focus to the display that the user is interacting with,
+            // unless that display maintains its own focus.
+            Display display = mDisplayManager.getDisplay(displayId);
+            if ((display.getFlags() & Display.FLAG_OWN_FOCUS) == 0) {
                 // An event is targeting a non-focused display. Move the display to top so that
                 // it can become the focused display to interact with the user.
                 // This should be done asynchronously, once the focus logic is fully moved to input
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 9281f4b..1ea0988 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -2204,6 +2204,15 @@
                     if (sQuiescent) {
                         mDirty |= DIRTY_QUIESCENT;
                     }
+                    PowerGroup defaultGroup = mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP);
+                    if (defaultGroup.getWakefulnessLocked() == WAKEFULNESS_DOZING) {
+                        // Workaround for b/187231320 where the AOD can get stuck in a "half on /
+                        // half off" state when a non-default-group VirtualDisplay causes the global
+                        // wakefulness to change to awake, even though the default display is
+                        // dozing. We set sandman summoned to restart dreaming to get it unstuck.
+                        // TODO(b/255688811) - fix this so that AOD never gets interrupted at all.
+                        defaultGroup.setSandmanSummonedLocked(true);
+                    }
                     break;
 
                 case WAKEFULNESS_ASLEEP:
diff --git a/services/core/java/com/android/server/sensors/SensorManagerInternal.java b/services/core/java/com/android/server/sensors/SensorManagerInternal.java
index fbb6644..f17e5e7 100644
--- a/services/core/java/com/android/server/sensors/SensorManagerInternal.java
+++ b/services/core/java/com/android/server/sensors/SensorManagerInternal.java
@@ -43,6 +43,43 @@
     public abstract void removeProximityActiveListener(@NonNull ProximityActiveListener listener);
 
     /**
+     * Creates a sensor that is registered at runtime by the system with the sensor service.
+     *
+     * The runtime sensors created here are different from the
+     * <a href="https://source.android.com/docs/core/interaction/sensors/sensors-hal2#dynamic-sensors">
+     * dynamic sensor support in the HAL</a>. These sensors have no HAL dependency and correspond to
+     * sensors that belong to an external (virtual) device.
+     *
+     * @param deviceId The identifier of the device this sensor is associated with.
+     * @param type The generic type of the sensor.
+     * @param name The name of the sensor.
+     * @param vendor The vendor string of the sensor.
+     * @param callback The callback to get notified when the sensor listeners have changed.
+     * @return The sensor handle.
+     */
+    public abstract int createRuntimeSensor(int deviceId, int type, @NonNull String name,
+            @NonNull String vendor, @NonNull RuntimeSensorStateChangeCallback callback);
+
+    /**
+     * Unregisters the sensor with the given handle from the framework.
+     */
+    public abstract void removeRuntimeSensor(int handle);
+
+    /**
+     * Sends an event for the runtime sensor with the given handle to the framework.
+     *
+     * Only relevant for sending runtime sensor events. @see #createRuntimeSensor.
+     *
+     * @param handle The sensor handle.
+     * @param type The type of the sensor.
+     * @param timestampNanos When the event occurred.
+     * @param values The values of the event.
+     * @return Whether the event injection was successful.
+     */
+    public abstract boolean sendSensorEvent(int handle, int type, long timestampNanos,
+            @NonNull float[] values);
+
+    /**
      * Listener for proximity sensor state changes.
      */
     public interface ProximityActiveListener {
@@ -52,4 +89,17 @@
          */
         void onProximityActive(boolean isActive);
     }
+
+    /**
+     * Callback for runtime sensor state changes. Only relevant to sensors created via
+     * {@link #createRuntimeSensor}, i.e. the dynamic sensors created via the dynamic sensor HAL are
+     * not covered.
+     */
+    public interface RuntimeSensorStateChangeCallback {
+        /**
+         * Invoked when the listeners of the runtime sensor have changed.
+         */
+        void onStateChanged(boolean enabled, int samplingPeriodMicros,
+                int batchReportLatencyMicros);
+    }
 }
diff --git a/services/core/java/com/android/server/sensors/SensorService.java b/services/core/java/com/android/server/sensors/SensorService.java
index 8fe2d52..d8e3bdd 100644
--- a/services/core/java/com/android/server/sensors/SensorService.java
+++ b/services/core/java/com/android/server/sensors/SensorService.java
@@ -29,7 +29,9 @@
 import com.android.server.SystemService;
 import com.android.server.utils.TimingsTraceAndSlog;
 
+import java.util.HashSet;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Future;
 
@@ -40,6 +42,8 @@
     private final ArrayMap<ProximityActiveListener, ProximityListenerProxy> mProximityListeners =
             new ArrayMap<>();
     @GuardedBy("mLock")
+    private final Set<Integer> mRuntimeSensorHandles = new HashSet<>();
+    @GuardedBy("mLock")
     private Future<?> mSensorServiceStart;
     @GuardedBy("mLock")
     private long mPtr;
@@ -51,6 +55,12 @@
     private static native void registerProximityActiveListenerNative(long ptr);
     private static native void unregisterProximityActiveListenerNative(long ptr);
 
+    private static native int registerRuntimeSensorNative(long ptr, int deviceId, int type,
+            String name, String vendor,
+            SensorManagerInternal.RuntimeSensorStateChangeCallback callback);
+    private static native void unregisterRuntimeSensorNative(long ptr, int handle);
+    private static native boolean sendRuntimeSensorEventNative(long ptr, int handle, int type,
+            long timestampNanos, float[] values);
 
     public SensorService(Context ctx) {
         super(ctx);
@@ -85,6 +95,38 @@
 
     class LocalService extends SensorManagerInternal {
         @Override
+        public int createRuntimeSensor(int deviceId, int type, @NonNull String name,
+                @NonNull String vendor, @NonNull RuntimeSensorStateChangeCallback callback) {
+            synchronized (mLock) {
+                int handle = registerRuntimeSensorNative(mPtr, deviceId, type, name, vendor,
+                        callback);
+                mRuntimeSensorHandles.add(handle);
+                return handle;
+            }
+        }
+
+        @Override
+        public void removeRuntimeSensor(int handle) {
+            synchronized (mLock) {
+                if (mRuntimeSensorHandles.contains(handle)) {
+                    mRuntimeSensorHandles.remove(handle);
+                    unregisterRuntimeSensorNative(mPtr, handle);
+                }
+            }
+        }
+
+        @Override
+        public boolean sendSensorEvent(int handle, int type, long timestampNanos,
+                @NonNull float[] values) {
+            synchronized (mLock) {
+                if (!mRuntimeSensorHandles.contains(handle)) {
+                    return false;
+                }
+                return sendRuntimeSensorEventNative(mPtr, handle, type, timestampNanos, values);
+            }
+        }
+
+        @Override
         public void addProximityActiveListener(@NonNull Executor executor,
                 @NonNull ProximityActiveListener listener) {
             Objects.requireNonNull(executor, "executor must not be null");
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index 0b1f6b9..f971db9 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -107,6 +107,7 @@
     // Trust state
     private boolean mTrusted;
     private boolean mWaitingForTrustableDowngrade = false;
+    private boolean mWithinSecurityLockdownWindow = false;
     private boolean mTrustable;
     private CharSequence mMessage;
     private boolean mDisplayTrustGrantedMessage;
@@ -160,6 +161,7 @@
                     mDisplayTrustGrantedMessage = (flags & FLAG_GRANT_TRUST_DISPLAY_MESSAGE) != 0;
                     if ((flags & FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0) {
                         mWaitingForTrustableDowngrade = true;
+                        setSecurityWindowTimer();
                     } else {
                         mWaitingForTrustableDowngrade = false;
                     }
@@ -452,6 +454,9 @@
             if (mBound) {
                 scheduleRestart();
             }
+            if (mWithinSecurityLockdownWindow) {
+                mTrustManagerService.lockUser(mUserId);
+            }
             // mTrustDisabledByDpm maintains state
         }
     };
@@ -673,6 +678,22 @@
         }
     }
 
+    private void setSecurityWindowTimer() {
+        mWithinSecurityLockdownWindow = true;
+        long expiration = SystemClock.elapsedRealtime() + (15 * 1000); // timer for 15 seconds
+        mAlarmManager.setExact(
+                AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                expiration,
+                TAG,
+                new AlarmManager.OnAlarmListener() {
+                    @Override
+                    public void onAlarm() {
+                        mWithinSecurityLockdownWindow = false;
+                    }
+                },
+                Handler.getMain());
+    }
+
     public boolean isManagingTrust() {
         return mManagingTrust && !mTrustDisabledByDpm;
     }
@@ -691,7 +712,6 @@
 
     public void destroy() {
         mHandler.removeMessages(MSG_RESTART_TIMEOUT);
-
         if (!mBound) {
             return;
         }
diff --git a/services/core/java/com/android/server/utils/EventLogger.java b/services/core/java/com/android/server/utils/EventLogger.java
index 770cb72..4772bbf 100644
--- a/services/core/java/com/android/server/utils/EventLogger.java
+++ b/services/core/java/com/android/server/utils/EventLogger.java
@@ -36,8 +36,11 @@
  */
 public class EventLogger {
 
+    /** Prefix for the title added at the beginning of a {@link #dump(PrintWriter)} operation */
+    private static final String DUMP_TITLE_PREFIX = "Events log: ";
+
     /** Identifies the source of events. */
-    private final String mTag;
+    @Nullable private final String mTag;
 
     /** Stores the events using a ring buffer. */
     private final ArrayDeque<Event> mEvents;
@@ -55,7 +58,7 @@
      * @param size the maximum number of events to keep in log
      * @param tag the string displayed before the recorded log
      */
-    public EventLogger(int size, String tag) {
+    public EventLogger(int size, @Nullable String tag) {
         mEvents = new ArrayDeque<>(size);
         mMemSize = size;
         mTag = tag;
@@ -64,10 +67,10 @@
     /** Enqueues {@code event} to be logged. */
     public synchronized void enqueue(Event event) {
         if (mEvents.size() >= mMemSize) {
-            mEvents.removeLast();
+            mEvents.removeFirst();
         }
 
-        mEvents.addFirst(event);
+        mEvents.addLast(event);
     }
 
     /**
@@ -91,13 +94,19 @@
         dump(pw, "" /* prefix */);
     }
 
+    protected String getDumpTitle() {
+        if (mTag == null) {
+            return DUMP_TITLE_PREFIX;
+        }
+        return DUMP_TITLE_PREFIX + mTag;
+    }
+
     /** Dumps events using {@link PrintWriter} with a certain indent. */
     public synchronized void dump(PrintWriter pw, String indent) {
-        pw.println(indent + "Events log: " + mTag);
+        pw.println(getDumpTitle());
 
-        String childrenIndention = indent + "  ";
         for (Event evt : mEvents) {
-            pw.println(childrenIndention + evt.toString());
+            pw.println(indent + evt.toString());
         }
     }
 
diff --git a/services/core/java/com/android/server/utils/Slogf.java b/services/core/java/com/android/server/utils/Slogf.java
index e88ac63..6efbd89 100644
--- a/services/core/java/com/android/server/utils/Slogf.java
+++ b/services/core/java/com/android/server/utils/Slogf.java
@@ -162,7 +162,7 @@
     }
 
     /**
-     * Logs a {@link Log.VEBOSE} message with an exception
+     * Logs a {@link Log.VEBOSE} message with a throwable
      *
      * <p><strong>Note: </strong>the message will only be formatted if {@link Log#VERBOSE} logging
      * is enabled for the given {@code tag}, but the compiler will still create an intermediate
@@ -170,10 +170,10 @@
      * you're calling this method in a critical path, make sure to explicitly do the check before
      * calling it.
      */
-    public static void v(String tag, Exception exception, String format, @Nullable Object... args) {
+    public static void v(String tag, Throwable throwable, String format, @Nullable Object... args) {
         if (!isLoggable(tag, Log.VERBOSE)) return;
 
-        v(tag, getMessage(format, args), exception);
+        v(tag, getMessage(format, args), throwable);
     }
 
     /**
@@ -192,7 +192,7 @@
     }
 
     /**
-     * Logs a {@link Log.DEBUG} message with an exception
+     * Logs a {@link Log.DEBUG} message with a throwable
      *
      * <p><strong>Note: </strong>the message will only be formatted if {@link Log#DEBUG} logging
      * is enabled for the given {@code tag}, but the compiler will still create an intermediate
@@ -200,10 +200,10 @@
      * you're calling this method in a critical path, make sure to explicitly do the check before
      * calling it.
      */
-    public static void d(String tag, Exception exception, String format, @Nullable Object... args) {
+    public static void d(String tag, Throwable throwable, String format, @Nullable Object... args) {
         if (!isLoggable(tag, Log.DEBUG)) return;
 
-        d(tag, getMessage(format, args), exception);
+        d(tag, getMessage(format, args), throwable);
     }
 
     /**
@@ -222,7 +222,7 @@
     }
 
     /**
-     * Logs a {@link Log.INFO} message with an exception
+     * Logs a {@link Log.INFO} message with a throwable
      *
      * <p><strong>Note: </strong>the message will only be formatted if {@link Log#INFO} logging
      * is enabled for the given {@code tag}, but the compiler will still create an intermediate
@@ -230,10 +230,10 @@
      * you're calling this method in a critical path, make sure to explicitly do the check before
      * calling it.
      */
-    public static void i(String tag, Exception exception, String format, @Nullable Object... args) {
+    public static void i(String tag, Throwable throwable, String format, @Nullable Object... args) {
         if (!isLoggable(tag, Log.INFO)) return;
 
-        i(tag, getMessage(format, args), exception);
+        i(tag, getMessage(format, args), throwable);
     }
 
     /**
@@ -252,7 +252,7 @@
     }
 
     /**
-     * Logs a {@link Log.WARN} message with an exception
+     * Logs a {@link Log.WARN} message with a throwable
      *
      * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
      * enabled for the given {@code tag}, but the compiler will still create an intermediate array
@@ -260,10 +260,10 @@
      * calling this method in a critical path, make sure to explicitly do the check before calling
      * it.
      */
-    public static void w(String tag, Exception exception, String format, @Nullable Object... args) {
+    public static void w(String tag, Throwable throwable, String format, @Nullable Object... args) {
         if (!isLoggable(tag, Log.WARN)) return;
 
-        w(tag, getMessage(format, args), exception);
+        w(tag, getMessage(format, args), throwable);
     }
 
     /**
@@ -282,7 +282,7 @@
     }
 
     /**
-     * Logs a {@link Log.ERROR} message with an exception
+     * Logs a {@link Log.ERROR} message with a throwable
      *
      * <p><strong>Note: </strong>the message will only be formatted if {@link Log#ERROR} logging is
      * enabled for the given {@code tag}, but the compiler will still create an intermediate array
@@ -290,10 +290,10 @@
      * calling this method in a critical path, make sure to explicitly do the check before calling
      * it.
      */
-    public static void e(String tag, Exception exception, String format, @Nullable Object... args) {
+    public static void e(String tag, Throwable throwable, String format, @Nullable Object... args) {
         if (!isLoggable(tag, Log.ERROR)) return;
 
-        e(tag, getMessage(format, args), exception);
+        e(tag, getMessage(format, args), throwable);
     }
 
     /**
@@ -304,11 +304,11 @@
     }
 
     /**
-     * Logs a {@code wtf} message with an exception.
+     * Logs a {@code wtf} message with a throwable.
      */
-    public static void wtf(String tag, Exception exception, String format,
+    public static void wtf(String tag, Throwable throwable, String format,
             @Nullable Object... args) {
-        wtf(tag, getMessage(format, args), exception);
+        wtf(tag, getMessage(format, args), throwable);
     }
 
     private static String getMessage(String format, @Nullable Object... args) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index f74956b..5d08461 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1559,8 +1559,9 @@
                     try {
                         mReply.sendResult(null);
                     } catch (RemoteException e) {
-                        Binder.restoreCallingIdentity(ident);
                         Slog.d(TAG, "failed to send callback!", e);
+                    } finally {
+                        Binder.restoreCallingIdentity(ident);
                     }
                     t.traceEnd();
                     mReply = null;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 530fa4d..12424c0 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3317,9 +3317,17 @@
                 mAtmService.mUgmInternal.grantUriPermissionUncheckedFromIntent(resultGrants,
                         resultTo.getUriPermissionsLocked());
             }
-            if (mForceSendResultForMediaProjection) {
-                resultTo.sendResult(this.getUid(), resultWho, requestCode, resultCode,
-                        resultData, resultGrants, true /* forceSendForMediaProjection */);
+            if (mForceSendResultForMediaProjection || resultTo.isState(RESUMED)) {
+                // Sending the result to the resultTo activity asynchronously to prevent the
+                // resultTo activity getting results before this Activity paused.
+                final ActivityRecord resultToActivity = resultTo;
+                mAtmService.mH.post(() -> {
+                    synchronized (mAtmService.mGlobalLock) {
+                        resultToActivity.sendResult(this.getUid(), resultWho, requestCode,
+                                resultCode, resultData, resultGrants,
+                                mForceSendResultForMediaProjection);
+                    }
+                });
             } else {
                 resultTo.addResultLocked(this, resultWho, requestCode, resultCode, resultData);
             }
@@ -4630,7 +4638,7 @@
                 false /* forceSendForMediaProjection */);
     }
 
-    private void sendResult(int callingUid, String resultWho, int requestCode, int resultCode,
+    void sendResult(int callingUid, String resultWho, int requestCode, int resultCode,
             Intent data, NeededUriGrants dataGrants, boolean forceSendForMediaProjection) {
         if (callingUid > 0) {
             mAtmService.mUgmInternal.grantUriPermissionUncheckedFromIntent(dataGrants,
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index d7c5e93..719f72c 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -399,8 +399,11 @@
      * @return The intercepting intent if needed.
      */
     private Intent interceptWithConfirmCredentialsIfNeeded(ActivityInfo aInfo, int userId) {
+        if (!mService.mAmInternal.shouldConfirmCredentials(userId)) {
+            return null;
+        }
         if ((aInfo.flags & ActivityInfo.FLAG_SHOW_WHEN_LOCKED) != 0
-                || !mService.mAmInternal.shouldConfirmCredentials(userId)) {
+                && (mUserManager.isUserUnlocked(userId) || aInfo.directBootAware)) {
             return null;
         }
         final IntentSender target = createIntentSenderForOriginalIntent(mCallingUid,
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 5938e7f..0b16a4d 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -78,7 +78,6 @@
 import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
 import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
 import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK;
-import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT;
 import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
@@ -2777,11 +2776,6 @@
                 errMsg = "The app:" + mCallingUid + "is not trusted to " + mStartActivity;
                 break;
             }
-            case EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT: {
-                errMsg = "Cannot embed activity across TaskFragments for result, resultTo: "
-                        + mStartActivity.resultTo;
-                break;
-            }
             default:
                 errMsg = "Unhandled embed result:" + result;
         }
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 14d6d7b..798e739 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -241,6 +241,7 @@
                 // We have another Activity in the same currentTask to go to
                 backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY;
                 removedWindowContainer = currentActivity;
+                prevTask = prevActivity.getTask();
             } else if (currentTask.returnsToHomeRootTask()) {
                 // Our Task should bring back to home
                 removedWindowContainer = currentTask;
@@ -608,26 +609,23 @@
                 // reset leash after animation finished.
                 leashes.add(screenshotSurface);
             }
-        } else if (prevTask != null) {
-            prevActivity = prevTask.getTopNonFinishingActivity();
-            if (prevActivity != null) {
-                // Make previous task show from behind by marking its top activity as visible
-                // and launch-behind to bump its visibility for the duration of the back gesture.
-                setLaunchBehind(prevActivity);
+        } else if (prevTask != null && prevActivity != null) {
+            // Make previous task show from behind by marking its top activity as visible
+            // and launch-behind to bump its visibility for the duration of the back gesture.
+            setLaunchBehind(prevActivity);
 
-                final SurfaceControl leash = prevActivity.makeAnimationLeash()
-                        .setName("BackPreview Leash for " + prevActivity)
-                        .setHidden(false)
-                        .build();
-                prevActivity.reparentSurfaceControl(startedTransaction, leash);
-                behindAppTarget = createRemoteAnimationTargetLocked(
-                        prevTask, leash, MODE_OPENING);
+            final SurfaceControl leash = prevActivity.makeAnimationLeash()
+                    .setName("BackPreview Leash for " + prevActivity)
+                    .setHidden(false)
+                    .build();
+            prevActivity.reparentSurfaceControl(startedTransaction, leash);
+            behindAppTarget = createRemoteAnimationTargetLocked(
+                    prevTask, leash, MODE_OPENING);
 
-                // reset leash after animation finished.
-                leashes.add(leash);
-                prevActivity.reparentSurfaceControl(finishedTransaction,
-                        prevActivity.getParentSurfaceControl());
-            }
+            // reset leash after animation finished.
+            leashes.add(leash);
+            prevActivity.reparentSurfaceControl(finishedTransaction,
+                    prevActivity.getParentSurfaceControl());
         }
 
         if (mShowWallpaper) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 0119e4d..d1122e1 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -207,6 +207,7 @@
 import android.view.Display;
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
+import android.view.DisplayShape;
 import android.view.Gravity;
 import android.view.IDisplayWindowInsetsController;
 import android.view.ISystemGestureExclusionListener;
@@ -391,6 +392,10 @@
             mPrivacyIndicatorBoundsCache = new
             RotationCache<>(this::calculatePrivacyIndicatorBoundsForRotationUncached);
 
+    DisplayShape mInitialDisplayShape;
+    private final RotationCache<DisplayShape, DisplayShape> mDisplayShapeCache =
+            new RotationCache<>(this::calculateDisplayShapeForRotationUncached);
+
     /**
      * Overridden display size. Initialized with {@link #mInitialDisplayWidth}
      * and {@link #mInitialDisplayHeight}, but can be set via shell command "adb shell wm size".
@@ -1095,7 +1100,8 @@
         mDisplayFrames = new DisplayFrames(mInsetsStateController.getRawInsetsState(),
                 mDisplayInfo, calculateDisplayCutoutForRotation(mDisplayInfo.rotation),
                 calculateRoundedCornersForRotation(mDisplayInfo.rotation),
-                calculatePrivacyIndicatorBoundsForRotation(mDisplayInfo.rotation));
+                calculatePrivacyIndicatorBoundsForRotation(mDisplayInfo.rotation),
+                calculateDisplayShapeForRotation(mDisplayInfo.rotation));
         initializeDisplayBaseInfo();
 
         mHoldScreenWakeLock = mWmService.mPowerManager.newWakeLock(
@@ -1969,8 +1975,9 @@
         final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation);
         final PrivacyIndicatorBounds indicatorBounds =
                 calculatePrivacyIndicatorBoundsForRotation(rotation);
+        final DisplayShape displayShape = calculateDisplayShapeForRotation(rotation);
         final DisplayFrames displayFrames = new DisplayFrames(new InsetsState(), info,
-                cutout, roundedCorners, indicatorBounds);
+                cutout, roundedCorners, indicatorBounds, displayShape);
         token.applyFixedRotationTransform(info, displayFrames, mTmpConfiguration);
     }
 
@@ -2178,6 +2185,7 @@
         // Update application display metrics.
         final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(rotation);
         final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation);
+        final DisplayShape displayShape = calculateDisplayShapeForRotation(rotation);
 
         final Rect appFrame = mDisplayPolicy.getDecorInsetsInfo(rotation, dw, dh).mNonDecorFrame;
         mDisplayInfo.rotation = rotation;
@@ -2194,6 +2202,7 @@
         }
         mDisplayInfo.displayCutout = displayCutout.isEmpty() ? null : displayCutout;
         mDisplayInfo.roundedCorners = roundedCorners;
+        mDisplayInfo.displayShape = displayShape;
         mDisplayInfo.getAppMetrics(mDisplayMetrics);
         if (mDisplayScalingDisabled) {
             mDisplayInfo.flags |= Display.FLAG_SCALING_DISABLED;
@@ -2280,6 +2289,23 @@
         return bounds.rotate(rotation);
     }
 
+    DisplayShape calculateDisplayShapeForRotation(int rotation) {
+        return mDisplayShapeCache.getOrCompute(mInitialDisplayShape, rotation);
+    }
+
+    private DisplayShape calculateDisplayShapeForRotationUncached(
+            DisplayShape displayShape, int rotation) {
+        if (displayShape == null) {
+            return DisplayShape.NONE;
+        }
+
+        if (rotation == ROTATION_0) {
+            return displayShape;
+        }
+
+        return displayShape.setRotation(rotation);
+    }
+
     /**
      * Compute display info and configuration according to the given rotation without changing
      * current display.
@@ -2781,7 +2807,8 @@
         return displayFrames.update(rotation, w, h,
                 calculateDisplayCutoutForRotation(rotation),
                 calculateRoundedCornersForRotation(rotation),
-                calculatePrivacyIndicatorBoundsForRotation(rotation));
+                calculatePrivacyIndicatorBoundsForRotation(rotation),
+                calculateDisplayShapeForRotation(rotation));
     }
 
     @Override
@@ -2821,6 +2848,7 @@
         mInitialRoundedCorners = mDisplayInfo.roundedCorners;
         mCurrentPrivacyIndicatorBounds = new PrivacyIndicatorBounds(new Rect[4],
                 mDisplayInfo.rotation);
+        mInitialDisplayShape = mDisplayInfo.displayShape;
         final Display.Mode maxDisplayMode =
                 DisplayUtils.getMaximumResolutionDisplayMode(mDisplayInfo.supportedModes);
         mPhysicalDisplaySize = new Point(
@@ -2848,6 +2876,7 @@
                 ? DisplayCutout.NO_CUTOUT : mDisplayInfo.displayCutout;
         final String newUniqueId = mDisplayInfo.uniqueId;
         final RoundedCorners newRoundedCorners = mDisplayInfo.roundedCorners;
+        final DisplayShape newDisplayShape = mDisplayInfo.displayShape;
 
         final boolean displayMetricsChanged = mInitialDisplayWidth != newWidth
                 || mInitialDisplayHeight != newHeight
@@ -2855,7 +2884,8 @@
                 || mInitialPhysicalXDpi != newXDpi
                 || mInitialPhysicalYDpi != newYDpi
                 || !Objects.equals(mInitialDisplayCutout, newCutout)
-                || !Objects.equals(mInitialRoundedCorners, newRoundedCorners);
+                || !Objects.equals(mInitialRoundedCorners, newRoundedCorners)
+                || !Objects.equals(mInitialDisplayShape, newDisplayShape);
         final boolean physicalDisplayChanged = !newUniqueId.equals(mCurrentUniqueDisplayId);
 
         if (displayMetricsChanged || physicalDisplayChanged) {
@@ -2893,6 +2923,7 @@
             mInitialPhysicalYDpi = newYDpi;
             mInitialDisplayCutout = newCutout;
             mInitialRoundedCorners = newRoundedCorners;
+            mInitialDisplayShape = newDisplayShape;
             mCurrentUniqueDisplayId = newUniqueId;
             reconfigureDisplayLocked();
 
@@ -3380,7 +3411,7 @@
                     }
                 }
                 mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
-                controller.mTransitionMetricsReporter.associate(t,
+                controller.mTransitionMetricsReporter.associate(t.getToken(),
                         startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN));
                 startAsyncRotation(false /* shouldDebounce */);
             }
@@ -3683,7 +3714,7 @@
      * @return The focused window or null if there isn't any or no need to seek.
      */
     WindowState findFocusedWindowIfNeeded(int topFocusedDisplayId) {
-        return (mWmService.mPerDisplayFocusEnabled || topFocusedDisplayId == INVALID_DISPLAY)
+        return (hasOwnFocus() || topFocusedDisplayId == INVALID_DISPLAY)
                     ? findFocusedWindow() : null;
     }
 
@@ -6315,6 +6346,14 @@
     }
 
     /**
+     * @return whether this display maintains its own focus and touch mode.
+     */
+    boolean hasOwnFocus() {
+        return mWmService.mPerDisplayFocusEnabled
+                || (mDisplayInfo.flags & Display.FLAG_OWN_FOCUS) != 0;
+    }
+
+    /**
      * @return whether the keyguard is occluded on this display
      */
     boolean isKeyguardOccluded() {
diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java
index 33641f7..e984456 100644
--- a/services/core/java/com/android/server/wm/DisplayFrames.java
+++ b/services/core/java/com/android/server/wm/DisplayFrames.java
@@ -26,6 +26,7 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
+import android.view.DisplayShape;
 import android.view.InsetsState;
 import android.view.PrivacyIndicatorBounds;
 import android.view.RoundedCorners;
@@ -56,10 +57,11 @@
     public int mRotation;
 
     public DisplayFrames(InsetsState insetsState, DisplayInfo info, DisplayCutout cutout,
-            RoundedCorners roundedCorners, PrivacyIndicatorBounds indicatorBounds) {
+            RoundedCorners roundedCorners, PrivacyIndicatorBounds indicatorBounds,
+            DisplayShape displayShape) {
         mInsetsState = insetsState;
         update(info.rotation, info.logicalWidth, info.logicalHeight, cutout, roundedCorners,
-                indicatorBounds);
+                indicatorBounds, displayShape);
     }
 
     DisplayFrames() {
@@ -73,7 +75,8 @@
      */
     public boolean update(int rotation, int w, int h, @NonNull DisplayCutout displayCutout,
             @NonNull RoundedCorners roundedCorners,
-            @NonNull PrivacyIndicatorBounds indicatorBounds) {
+            @NonNull PrivacyIndicatorBounds indicatorBounds,
+            @NonNull DisplayShape displayShape) {
         final InsetsState state = mInsetsState;
         final Rect safe = mDisplayCutoutSafe;
         if (mRotation == rotation && mWidth == w && mHeight == h
@@ -91,6 +94,7 @@
         state.setDisplayCutout(displayCutout);
         state.setRoundedCorners(roundedCorners);
         state.setPrivacyIndicatorBounds(indicatorBounds);
+        state.setDisplayShape(displayShape);
         state.getDisplayCutoutSafe(safe);
         if (safe.left > unrestricted.left) {
             state.getSource(ITYPE_LEFT_DISPLAY_CUTOUT).setFrame(
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 7860b15..3e1105b 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -270,7 +270,7 @@
                 InputConfigAdapter.getMask());
 
         final boolean focusable = w.canReceiveKeys()
-                && (mService.mPerDisplayFocusEnabled || mDisplayContent.isOnTop());
+                && (mDisplayContent.hasOwnFocus() || mDisplayContent.isOnTop());
         inputWindowHandle.setFocusable(focusable);
 
         final boolean hasWallpaper = mDisplayContent.mWallpaperController.isWallpaperTarget(w)
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 2dbccae..bb4c482 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -87,10 +87,6 @@
     private final LetterboxConfiguration mLetterboxConfiguration;
     private final ActivityRecord mActivityRecord;
 
-    // Taskbar expanded height. Used to determine whether to crop an app window to display rounded
-    // corners above the taskbar.
-    private final float mExpandedTaskBarHeight;
-
     private boolean mShowWallpaperForLetterboxBackground;
 
     @Nullable
@@ -102,8 +98,6 @@
         // is created in its constructor. It shouldn't be used in this constructor but it's safe
         // to use it after since controller is only used in ActivityRecord.
         mActivityRecord = activityRecord;
-        mExpandedTaskBarHeight =
-                getResources().getDimensionPixelSize(R.dimen.taskbar_frame_height);
     }
 
     /** Cleans up {@link Letterbox} if it exists.*/
@@ -285,14 +279,17 @@
     }
 
     float getSplitScreenAspectRatio() {
+        // Getting the same aspect ratio that apps get in split screen.
+        final DisplayContent displayContent = mActivityRecord.getDisplayContent();
+        if (displayContent == null) {
+            return getDefaultMinAspectRatioForUnresizableApps();
+        }
         int dividerWindowWidth =
                 getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_thickness);
         int dividerInsets =
                 getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_insets);
         int dividerSize = dividerWindowWidth - dividerInsets * 2;
-
-        // Getting the same aspect ratio that apps get in split screen.
-        Rect bounds = new Rect(mActivityRecord.getDisplayContent().getBounds());
+        final Rect bounds = new Rect(displayContent.getBounds());
         if (bounds.width() >= bounds.height()) {
             bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0);
             bounds.right = bounds.centerX();
@@ -555,7 +552,6 @@
         final InsetsSource taskbarInsetsSource = getTaskbarInsetsSource(mainWindow);
 
         return taskbarInsetsSource != null
-                && taskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight
                 && taskbarInsetsSource.isVisible();
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 91cb037..d3a3cf5 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -172,13 +172,6 @@
      * indicate that an Activity can't be embedded because the Activity is started on a new task.
      */
     static final int EMBEDDING_DISALLOWED_NEW_TASK = 3;
-    /**
-     * An embedding check result of
-     * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}:
-     * indicate that an Activity can't be embedded because the Activity is started on a new
-     * TaskFragment, e.g. start an Activity on a new TaskFragment for result.
-     */
-    static final int EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT = 4;
 
     /**
      * Embedding check results of {@link #isAllowedToEmbedActivity(ActivityRecord)} or
@@ -189,7 +182,6 @@
             EMBEDDING_DISALLOWED_UNTRUSTED_HOST,
             EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION,
             EMBEDDING_DISALLOWED_NEW_TASK,
-            EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT,
     })
     @interface EmbeddingCheckResult {}
 
@@ -616,14 +608,6 @@
             return EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
         }
 
-        // Cannot embed activity across TaskFragments for activity result.
-        // If the activity that started for result is finishing, it's likely that this start mode
-        // is used to place an activity in the same task. Since the finishing activity won't be
-        // able to get the results, so it's OK to embed in a different TaskFragment.
-        if (a.resultTo != null && !a.resultTo.finishing && a.resultTo.getTaskFragment() != this) {
-            return EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT;
-        }
-
         return EMBEDDING_ALLOWED;
     }
 
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index b277804..9cb13e4 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -91,6 +91,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
@@ -100,7 +101,7 @@
  * Represents a logical transition.
  * @see TransitionController
  */
-class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListener {
+class Transition implements BLASTSyncEngine.TransactionReadyListener {
     private static final String TAG = "Transition";
     private static final String TRACE_NAME_PLAY_TRANSITION = "PlayTransition";
 
@@ -151,6 +152,7 @@
     private @TransitionFlags int mFlags;
     private final TransitionController mController;
     private final BLASTSyncEngine mSyncEngine;
+    private final Token mToken;
     private RemoteTransition mRemoteTransition = null;
 
     /** Only use for clean-up after binder death! */
@@ -213,10 +215,26 @@
         mFlags = flags;
         mController = controller;
         mSyncEngine = syncEngine;
+        mToken = new Token(this);
 
         controller.mTransitionTracer.logState(this);
     }
 
+    @Nullable
+    static Transition fromBinder(@NonNull IBinder token) {
+        try {
+            return ((Token) token).mTransition.get();
+        } catch (ClassCastException e) {
+            Slog.w(TAG, "Invalid transition token: " + token, e);
+            return null;
+        }
+    }
+
+    @NonNull
+    IBinder getToken() {
+        return mToken;
+    }
+
     void addFlag(int flag) {
         mFlags |= flag;
     }
@@ -726,6 +744,11 @@
             Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION,
                     System.identityHashCode(this));
         }
+        // Close the transactions now. They were originally copied to Shell in case we needed to
+        // apply them due to a remote failure. Since we don't need to apply them anymore, free them
+        // immediately.
+        if (mStartTransaction != null) mStartTransaction.close();
+        if (mFinishTransaction != null) mFinishTransaction.close();
         mStartTransaction = mFinishTransaction = null;
         if (mState < STATE_PLAYING) {
             throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId);
@@ -867,6 +890,7 @@
             mController.mAtm.mWindowManager.updateRotation(false /* alwaysSendConfiguration */,
                     false /* forceRelayout */);
         }
+        cleanUpInternal();
     }
 
     void abort() {
@@ -909,6 +933,7 @@
             dc.getPendingTransaction().merge(transaction);
             mSyncId = -1;
             mOverrideOptions = null;
+            cleanUpInternal();
             return;
         }
 
@@ -1026,7 +1051,9 @@
                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                         "Calling onTransitionReady: %s", info);
                 mController.getTransitionPlayer().onTransitionReady(
-                        this, info, transaction, mFinishTransaction);
+                        mToken, info, transaction, mFinishTransaction);
+                // Since we created root-leash but no longer reference it from core, release it now
+                info.releaseAnimSurfaces();
                 if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
                     Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION,
                             System.identityHashCode(this));
@@ -1059,7 +1086,17 @@
         if (mFinishTransaction != null) {
             mFinishTransaction.apply();
         }
-        mController.finishTransition(this);
+        mController.finishTransition(mToken);
+    }
+
+    private void cleanUpInternal() {
+        // Clean-up any native references.
+        for (int i = 0; i < mChanges.size(); ++i) {
+            final ChangeInfo ci = mChanges.valueAt(i);
+            if (ci.mSnapshot != null) {
+                ci.mSnapshot.release();
+            }
+        }
     }
 
     /** @see RecentsAnimationController#attachNavigationBarToApp */
@@ -1815,10 +1852,6 @@
         return isCollecting() && mSyncId >= 0;
     }
 
-    static Transition fromBinder(IBinder binder) {
-        return (Transition) binder;
-    }
-
     @VisibleForTesting
     static class ChangeInfo {
         private static final int FLAG_NONE = 0;
@@ -2325,4 +2358,18 @@
             }
         }
     }
+
+    private static class Token extends Binder {
+        final WeakReference<Transition> mTransition;
+
+        Token(Transition transition) {
+            mTransition = new WeakReference<>(transition);
+        }
+
+        @Override
+        public String toString() {
+            return "Token{" + Integer.toHexString(System.identityHashCode(this)) + " "
+                    + mTransition.get() + "}";
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 25df511..99527b1 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -458,8 +458,9 @@
                 info = new ActivityManager.RunningTaskInfo();
                 startTask.fillTaskInfo(info);
             }
-            mTransitionPlayer.requestStartTransition(transition, new TransitionRequestInfo(
-                    transition.mType, info, remoteTransition, displayChange));
+            mTransitionPlayer.requestStartTransition(transition.getToken(),
+                    new TransitionRequestInfo(transition.mType, info, remoteTransition,
+                            displayChange));
             transition.setRemoteTransition(remoteTransition);
         } catch (RemoteException e) {
             Slog.e(TAG, "Error requesting transition", e);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 6032f87..f6f825f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3847,6 +3847,11 @@
                     || displayContent.isInTouchMode() == inTouch)) {
                 return;
             }
+            final boolean displayHasOwnTouchMode =
+                    displayContent != null && displayContent.hasOwnFocus();
+            if (displayHasOwnTouchMode && displayContent.isInTouchMode() == inTouch) {
+                return;
+            }
             final int pid = Binder.getCallingPid();
             final int uid = Binder.getCallingUid();
             final boolean hasPermission =
@@ -3855,17 +3860,17 @@
                             /* printlog= */ false);
             final long token = Binder.clearCallingIdentity();
             try {
-                // If perDisplayFocusEnabled is set, then just update the display pointed by
-                // displayId
-                if (perDisplayFocusEnabled) {
+                // If perDisplayFocusEnabled is set or the display maintains its own touch mode,
+                // then just update the display pointed by displayId
+                if (perDisplayFocusEnabled || displayHasOwnTouchMode) {
                     if (mInputManager.setInTouchMode(inTouch, pid, uid, hasPermission, displayId)) {
                         displayContent.setInTouchMode(inTouch);
                     }
-                } else {  // Otherwise update all displays
+                } else {  // Otherwise update all displays that do not maintain their own touch mode
                     final int displayCount = mRoot.mChildren.size();
                     for (int i = 0; i < displayCount; ++i) {
                         DisplayContent dc = mRoot.mChildren.get(i);
-                        if (dc.isInTouchMode() == inTouch) {
+                        if (dc.isInTouchMode() == inTouch || dc.hasOwnFocus()) {
                             continue;
                         }
                         if (mInputManager.setInTouchMode(inTouch, pid, uid, hasPermission,
@@ -8935,14 +8940,14 @@
     }
 
     @Override
-    public List<DisplayInfo> getPossibleDisplayInfo(int displayId, String packageName) {
+    public List<DisplayInfo> getPossibleDisplayInfo(int displayId) {
         final int callingUid = Binder.getCallingUid();
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
-                if (packageName == null || !isRecentsComponent(packageName, callingUid)) {
-                    Slog.e(TAG, "Unable to verify uid for package " + packageName
-                            + " for getPossibleMaximumWindowMetrics");
+                if (!mAtmService.isCallerRecents(callingUid)) {
+                    Slog.e(TAG, "Unable to verify uid for getPossibleDisplayInfo"
+                            + " on uid " + callingUid);
                     return new ArrayList<>();
                 }
 
@@ -8960,31 +8965,6 @@
         return mPossibleDisplayInfoMapper.getPossibleDisplayInfos(displayId);
     }
 
-    /**
-     * Returns {@code true} when the calling package is the recents component.
-     */
-    boolean isRecentsComponent(@NonNull String callingPackageName, int callingUid) {
-        String recentsPackage;
-        try {
-            String recentsComponent = mContext.getResources().getString(
-                    R.string.config_recentsComponentName);
-            if (recentsComponent == null) {
-                return false;
-            }
-            recentsPackage = ComponentName.unflattenFromString(recentsComponent).getPackageName();
-        } catch (Resources.NotFoundException e) {
-            Slog.e(TAG, "Unable to verify if recents component", e);
-            return false;
-        }
-        try {
-            return callingUid == mContext.getPackageManager().getPackageUid(callingPackageName, 0)
-                    && callingPackageName.equals(recentsPackage);
-        } catch (PackageManager.NameNotFoundException e) {
-            Slog.e(TAG, "Unable to verify if recents component", e);
-            return false;
-        }
-    }
-
     void grantEmbeddedWindowFocus(Session session, IBinder focusToken, boolean grantFocus) {
         synchronized (mGlobalLock) {
             final EmbeddedWindowController.EmbeddedWindow embeddedWindow =
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 4c35178..aa1cf56 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -306,7 +306,7 @@
                                         nextTransition.setAllReady();
                                     }
                                 });
-                        return nextTransition;
+                        return nextTransition.getToken();
                     }
                     transition = mTransitionController.createTransition(type);
                 }
@@ -315,7 +315,7 @@
                 if (needsSetReady) {
                     transition.setAllReady();
                 }
-                return transition;
+                return transition.getToken();
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
diff --git a/services/core/jni/com_android_server_sensor_SensorService.cpp b/services/core/jni/com_android_server_sensor_SensorService.cpp
index 63b7dfb..10d8b42 100644
--- a/services/core/jni/com_android_server_sensor_SensorService.cpp
+++ b/services/core/jni/com_android_server_sensor_SensorService.cpp
@@ -22,6 +22,7 @@
 #include <cutils/properties.h>
 #include <jni.h>
 #include <sensorservice/SensorService.h>
+#include <string.h>
 #include <utils/Log.h>
 #include <utils/misc.h>
 
@@ -30,10 +31,14 @@
 #define PROXIMITY_ACTIVE_CLASS \
     "com/android/server/sensors/SensorManagerInternal$ProximityActiveListener"
 
+#define RUNTIME_SENSOR_CALLBACK_CLASS \
+    "com/android/server/sensors/SensorManagerInternal$RuntimeSensorStateChangeCallback"
+
 namespace android {
 
 static JavaVM* sJvm = nullptr;
 static jmethodID sMethodIdOnProximityActive;
+static jmethodID sMethodIdOnStateChanged;
 
 class NativeSensorService {
 public:
@@ -41,6 +46,11 @@
 
     void registerProximityActiveListener();
     void unregisterProximityActiveListener();
+    jint registerRuntimeSensor(JNIEnv* env, jint deviceId, jint type, jstring name, jstring vendor,
+                               jobject callback);
+    void unregisterRuntimeSensor(jint handle);
+    jboolean sendRuntimeSensorEvent(JNIEnv* env, jint handle, jint type, jlong timestamp,
+                                    jfloatArray values);
 
 private:
     sp<SensorService> mService;
@@ -56,6 +66,18 @@
         jobject mListener;
     };
     sp<ProximityActiveListenerDelegate> mProximityActiveListenerDelegate;
+
+    class RuntimeSensorCallbackDelegate : public SensorService::RuntimeSensorStateChangeCallback {
+    public:
+        RuntimeSensorCallbackDelegate(JNIEnv* env, jobject callback);
+        ~RuntimeSensorCallbackDelegate();
+
+        void onStateChanged(bool enabled, int64_t samplingPeriodNs,
+                            int64_t batchReportLatencyNs) override;
+
+    private:
+        jobject mCallback;
+    };
 };
 
 NativeSensorService::NativeSensorService(JNIEnv* env, jobject listener)
@@ -85,6 +107,109 @@
     mService->removeProximityActiveListener(mProximityActiveListenerDelegate);
 }
 
+jint NativeSensorService::registerRuntimeSensor(JNIEnv* env, jint deviceId, jint type, jstring name,
+                                                jstring vendor, jobject callback) {
+    if (mService == nullptr) {
+        ALOGD("Dropping registerRuntimeSensor, sensor service not available.");
+        return -1;
+    }
+
+    sensor_t sensor{
+            .name = env->GetStringUTFChars(name, 0),
+            .vendor = env->GetStringUTFChars(vendor, 0),
+            .version = sizeof(sensor_t),
+            .type = type,
+    };
+
+    sp<RuntimeSensorCallbackDelegate> callbackDelegate(
+            new RuntimeSensorCallbackDelegate(env, callback));
+    return mService->registerRuntimeSensor(sensor, deviceId, callbackDelegate);
+}
+
+void NativeSensorService::unregisterRuntimeSensor(jint handle) {
+    if (mService == nullptr) {
+        ALOGD("Dropping unregisterProximityActiveListener, sensor service not available.");
+        return;
+    }
+
+    mService->unregisterRuntimeSensor(handle);
+}
+
+jboolean NativeSensorService::sendRuntimeSensorEvent(JNIEnv* env, jint handle, jint type,
+                                                     jlong timestamp, jfloatArray values) {
+    if (mService == nullptr) {
+        ALOGD("Dropping sendRuntimeSensorEvent, sensor service not available.");
+        return false;
+    }
+    if (values == nullptr) {
+        ALOGD("Dropping sendRuntimeSensorEvent, no values.");
+        return false;
+    }
+
+    sensors_event_t event{
+            .version = sizeof(sensors_event_t),
+            .timestamp = timestamp,
+            .sensor = handle,
+            .type = type,
+    };
+
+    int valuesLength = env->GetArrayLength(values);
+    jfloat* sensorValues = env->GetFloatArrayElements(values, nullptr);
+
+    switch (type) {
+        case SENSOR_TYPE_ACCELEROMETER:
+        case SENSOR_TYPE_MAGNETIC_FIELD:
+        case SENSOR_TYPE_ORIENTATION:
+        case SENSOR_TYPE_GYROSCOPE:
+        case SENSOR_TYPE_GRAVITY:
+        case SENSOR_TYPE_LINEAR_ACCELERATION: {
+            if (valuesLength != 3) {
+                ALOGD("Dropping sendRuntimeSensorEvent, wrong number of values.");
+                return false;
+            }
+            event.acceleration.x = sensorValues[0];
+            event.acceleration.y = sensorValues[1];
+            event.acceleration.z = sensorValues[2];
+            break;
+        }
+        case SENSOR_TYPE_DEVICE_ORIENTATION:
+        case SENSOR_TYPE_LIGHT:
+        case SENSOR_TYPE_PRESSURE:
+        case SENSOR_TYPE_TEMPERATURE:
+        case SENSOR_TYPE_PROXIMITY:
+        case SENSOR_TYPE_RELATIVE_HUMIDITY:
+        case SENSOR_TYPE_AMBIENT_TEMPERATURE:
+        case SENSOR_TYPE_SIGNIFICANT_MOTION:
+        case SENSOR_TYPE_STEP_DETECTOR:
+        case SENSOR_TYPE_TILT_DETECTOR:
+        case SENSOR_TYPE_WAKE_GESTURE:
+        case SENSOR_TYPE_GLANCE_GESTURE:
+        case SENSOR_TYPE_PICK_UP_GESTURE:
+        case SENSOR_TYPE_WRIST_TILT_GESTURE:
+        case SENSOR_TYPE_STATIONARY_DETECT:
+        case SENSOR_TYPE_MOTION_DETECT:
+        case SENSOR_TYPE_HEART_BEAT:
+        case SENSOR_TYPE_LOW_LATENCY_OFFBODY_DETECT: {
+            if (valuesLength != 1) {
+                ALOGD("Dropping sendRuntimeSensorEvent, wrong number of values.");
+                return false;
+            }
+            event.data[0] = sensorValues[0];
+            break;
+        }
+        default: {
+            if (valuesLength > 16) {
+                ALOGD("Dropping sendRuntimeSensorEvent, number of values exceeds the maximum.");
+                return false;
+            }
+            memcpy(event.data, sensorValues, valuesLength * sizeof(float));
+        }
+    }
+
+    status_t err = mService->sendRuntimeSensorEvent(event);
+    return err == OK;
+}
+
 NativeSensorService::ProximityActiveListenerDelegate::ProximityActiveListenerDelegate(
         JNIEnv* env, jobject listener)
       : mListener(env->NewGlobalRef(listener)) {}
@@ -98,6 +223,22 @@
     jniEnv->CallVoidMethod(mListener, sMethodIdOnProximityActive, static_cast<jboolean>(isActive));
 }
 
+NativeSensorService::RuntimeSensorCallbackDelegate::RuntimeSensorCallbackDelegate(JNIEnv* env,
+                                                                                  jobject callback)
+      : mCallback(env->NewGlobalRef(callback)) {}
+
+NativeSensorService::RuntimeSensorCallbackDelegate::~RuntimeSensorCallbackDelegate() {
+    AndroidRuntime::getJNIEnv()->DeleteGlobalRef(mCallback);
+}
+
+void NativeSensorService::RuntimeSensorCallbackDelegate::onStateChanged(
+        bool enabled, int64_t samplingPeriodNs, int64_t batchReportLatencyNs) {
+    auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
+    jniEnv->CallVoidMethod(mCallback, sMethodIdOnStateChanged, static_cast<jboolean>(enabled),
+                           static_cast<jint>(ns2us(samplingPeriodNs)),
+                           static_cast<jint>(ns2us(batchReportLatencyNs)));
+}
+
 static jlong startSensorServiceNative(JNIEnv* env, jclass, jobject listener) {
     NativeSensorService* service = new NativeSensorService(env, listener);
     return reinterpret_cast<jlong>(service);
@@ -113,26 +254,46 @@
     service->unregisterProximityActiveListener();
 }
 
-static const JNINativeMethod methods[] = {
-        {
-                "startSensorServiceNative", "(L" PROXIMITY_ACTIVE_CLASS ";)J",
-                reinterpret_cast<void*>(startSensorServiceNative)
-        },
-        {
-                "registerProximityActiveListenerNative", "(J)V",
-                reinterpret_cast<void*>(registerProximityActiveListenerNative)
-        },
-        {
-                "unregisterProximityActiveListenerNative", "(J)V",
-                reinterpret_cast<void*>(unregisterProximityActiveListenerNative)
-         },
+static jint registerRuntimeSensorNative(JNIEnv* env, jclass, jlong ptr, jint deviceId, jint type,
+                                        jstring name, jstring vendor, jobject callback) {
+    auto* service = reinterpret_cast<NativeSensorService*>(ptr);
+    return service->registerRuntimeSensor(env, deviceId, type, name, vendor, callback);
+}
 
+static void unregisterRuntimeSensorNative(JNIEnv* env, jclass, jlong ptr, jint handle) {
+    auto* service = reinterpret_cast<NativeSensorService*>(ptr);
+    service->unregisterRuntimeSensor(handle);
+}
+
+static jboolean sendRuntimeSensorEventNative(JNIEnv* env, jclass, jlong ptr, jint handle, jint type,
+                                             jlong timestamp, jfloatArray values) {
+    auto* service = reinterpret_cast<NativeSensorService*>(ptr);
+    return service->sendRuntimeSensorEvent(env, handle, type, timestamp, values);
+}
+
+static const JNINativeMethod methods[] = {
+        {"startSensorServiceNative", "(L" PROXIMITY_ACTIVE_CLASS ";)J",
+         reinterpret_cast<void*>(startSensorServiceNative)},
+        {"registerProximityActiveListenerNative", "(J)V",
+         reinterpret_cast<void*>(registerProximityActiveListenerNative)},
+        {"unregisterProximityActiveListenerNative", "(J)V",
+         reinterpret_cast<void*>(unregisterProximityActiveListenerNative)},
+        {"registerRuntimeSensorNative",
+         "(JIILjava/lang/String;Ljava/lang/String;L" RUNTIME_SENSOR_CALLBACK_CLASS ";)I",
+         reinterpret_cast<void*>(registerRuntimeSensorNative)},
+        {"unregisterRuntimeSensorNative", "(JI)V",
+         reinterpret_cast<void*>(unregisterRuntimeSensorNative)},
+        {"sendRuntimeSensorEventNative", "(JIIJ[F)Z",
+         reinterpret_cast<void*>(sendRuntimeSensorEventNative)},
 };
 
 int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env) {
     sJvm = vm;
     jclass listenerClass = FindClassOrDie(env, PROXIMITY_ACTIVE_CLASS);
     sMethodIdOnProximityActive = GetMethodIDOrDie(env, listenerClass, "onProximityActive", "(Z)V");
+    jclass runtimeSensorCallbackClass = FindClassOrDie(env, RUNTIME_SENSOR_CALLBACK_CLASS);
+    sMethodIdOnStateChanged =
+            GetMethodIDOrDie(env, runtimeSensorCallbackClass, "onStateChanged", "(ZII)V");
     return jniRegisterNativeMethods(env, "com/android/server/sensors/SensorService", methods,
                                     NELEM(methods));
 }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 332a75e..8854453 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -25,7 +25,6 @@
 import android.credentials.ui.CreateCredentialProviderData;
 import android.credentials.ui.Entry;
 import android.credentials.ui.ProviderPendingIntentResponse;
-import android.os.Bundle;
 import android.service.credentials.BeginCreateCredentialRequest;
 import android.service.credentials.BeginCreateCredentialResponse;
 import android.service.credentials.CreateCredentialRequest;
@@ -68,12 +67,11 @@
                         createRequestSession.mClientRequest,
                         createRequestSession.mClientCallingPackage);
         if (providerCreateRequest != null) {
-            // TODO : Replace with proper splitting of request
             BeginCreateCredentialRequest providerBeginCreateRequest =
                     new BeginCreateCredentialRequest(
                             providerCreateRequest.getCallingPackage(),
                             providerCreateRequest.getType(),
-                            new Bundle());
+                            createRequestSession.mClientRequest.getCandidateQueryData());
             return new ProviderCreateSession(context, providerInfo, createRequestSession, userId,
                     remoteCredentialService, providerBeginCreateRequest, providerCreateRequest);
         }
@@ -88,7 +86,7 @@
         String capability = clientRequest.getType();
         if (providerCapabilities.contains(capability)) {
             return new CreateCredentialRequest(clientCallingPackage, capability,
-                    clientRequest.getData());
+                    clientRequest.getCredentialData());
         }
         Log.i(TAG, "Unable to create provider request - capabilities do not match");
         return null;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
index 8a8485a..9cb7533 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
@@ -16,24 +16,35 @@
 
 package com.android.server.devicepolicy;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.util.Objects;
 
 final class BooleanPolicySerializer extends PolicySerializer<Boolean> {
 
     @Override
-    void saveToXml(TypedXmlSerializer serializer, String attributeName, Boolean value)
+    void saveToXml(TypedXmlSerializer serializer, String attributeName, @NonNull Boolean value)
             throws IOException {
+        Objects.requireNonNull(value);
         serializer.attributeBoolean(/* namespace= */ null, attributeName, value);
     }
 
+    @Nullable
     @Override
-    Boolean readFromXml(TypedXmlPullParser parser, String attributeName)
-            throws XmlPullParserException {
-        return parser.getAttributeBoolean(/* namespace= */ null, attributeName);
+    Boolean readFromXml(TypedXmlPullParser parser, String attributeName) {
+        try {
+            return parser.getAttributeBoolean(/* namespace= */ null, attributeName);
+        } catch (XmlPullParserException e) {
+            Log.e(DevicePolicyEngine.TAG, "Error parsing Boolean policy value", e);
+            return null;
+        }
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 61d93c7..775e3d8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -81,6 +81,7 @@
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED;
 import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_NOT_SUSPENDED;
 import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_SUSPENDED_EXPLICITLY;
 import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT;
@@ -140,6 +141,7 @@
 import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
 import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
+import static android.provider.DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER;
 import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
 import static android.provider.Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED;
 import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
@@ -309,6 +311,7 @@
 import android.provider.CalendarContract;
 import android.provider.ContactsContract.QuickContact;
 import android.provider.ContactsInternal;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.provider.Telephony;
@@ -712,6 +715,17 @@
                     + "management app's authentication policy";
     private static final String NOT_SYSTEM_CALLER_MSG = "Only the system can %s";
 
+    private static final String ENABLE_COEXISTENCE_FLAG = "enable_coexistence";
+    private static final boolean DEFAULT_ENABLE_COEXISTENCE_FLAG = false;
+
+    /**
+     * For apps targeting U+
+     * Enable multiple admins to coexist on the same device.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    static final long ENABLE_COEXISTENCE_CHANGE = 260560985L;
+
     final Context mContext;
     final Injector mInjector;
     final PolicyPathProvider mPathProvider;
@@ -795,6 +809,8 @@
     private final DeviceManagementResourcesProvider mDeviceManagementResourcesProvider;
     private final DevicePolicyManagementRoleObserver mDevicePolicyManagementRoleObserver;
 
+    private final DevicePolicyEngine mDevicePolicyEngine;
+
     private static final boolean ENABLE_LOCK_GUARD = true;
 
     /**
@@ -1864,6 +1880,8 @@
         mUserData = new SparseArray<>();
         mOwners = makeOwners(injector, pathProvider);
 
+        mDevicePolicyEngine = new DevicePolicyEngine(mContext);
+
         if (!mHasFeature) {
             // Skip the rest of the initialization
             mSetupContentObserver = null;
@@ -1908,6 +1926,9 @@
         mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener());
 
         mDeviceManagementResourcesProvider.load();
+        if (isCoexistenceFlagEnabled()) {
+            mDevicePolicyEngine.load();
+        }
 
         // The binder caches are not enabled until the first invalidation.
         invalidateBinderCaches();
@@ -7951,8 +7972,17 @@
         Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
                 || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
 
-        mInjector.binderWithCleanCallingIdentity(() ->
-                mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0));
+        if (isCoexistenceEnabled(caller)) {
+            mDevicePolicyEngine.setGlobalPolicy(
+                    PolicyDefinition.AUTO_TIMEZONE,
+                    // TODO(b/260573124): add correct enforcing admin when permission changes are
+                    //  merged.
+                    EnforcingAdmin.createEnterpriseEnforcingAdmin(caller.getComponentName()),
+                    enabled);
+        } else {
+            mInjector.binderWithCleanCallingIdentity(() ->
+                    mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0));
+        }
 
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_AUTO_TIME_ZONE)
@@ -12245,8 +12275,38 @@
         synchronized (getLockObject()) {
             enforceCanCallLockTaskLocked(caller);
             checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOCK_TASK_PACKAGES);
-            final int userHandle = caller.getUserId();
-            setLockTaskPackagesLocked(userHandle, new ArrayList<>(Arrays.asList(packages)));
+        }
+
+        if (isCoexistenceEnabled(caller)) {
+            EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who);
+            if (packages.length == 0) {
+                mDevicePolicyEngine.removeLocalPolicy(
+                        PolicyDefinition.LOCK_TASK,
+                        admin,
+                        caller.getUserId());
+            } else {
+                LockTaskPolicy currentPolicy = mDevicePolicyEngine.getLocalPolicy(
+                        PolicyDefinition.LOCK_TASK,
+                        caller.getUserId()).getPoliciesSetByAdmins().get(admin);
+                LockTaskPolicy policy;
+                if (currentPolicy == null) {
+                    policy = new LockTaskPolicy(Set.of(packages));
+                } else {
+                    policy = currentPolicy.clone();
+                    policy.setPackages(Set.of(packages));
+                }
+
+                mDevicePolicyEngine.setLocalPolicy(
+                        PolicyDefinition.LOCK_TASK,
+                        EnforcingAdmin.createEnterpriseEnforcingAdmin(who),
+                        policy,
+                        caller.getUserId());
+            }
+        } else {
+            synchronized (getLockObject()) {
+                final int userHandle = caller.getUserId();
+                setLockTaskPackagesLocked(userHandle, new ArrayList<>(Arrays.asList(packages)));
+            }
         }
     }
 
@@ -12267,8 +12327,21 @@
 
         synchronized (getLockObject()) {
             enforceCanCallLockTaskLocked(caller);
-            final List<String> packages = getUserData(userHandle).mLockTaskPackages;
-            return packages.toArray(new String[packages.size()]);
+        }
+
+        if (isCoexistenceEnabled(caller)) {
+            LockTaskPolicy policy = mDevicePolicyEngine.getLocalPolicy(
+                    PolicyDefinition.LOCK_TASK, userHandle).getCurrentResolvedPolicy();
+            if (policy == null) {
+                return new String[0];
+            } else {
+                return policy.getPackages().toArray(new String[policy.getPackages().size()]);
+            }
+        } else {
+            synchronized (getLockObject()) {
+                final List<String> packages = getUserData(userHandle).mLockTaskPackages;
+                return packages.toArray(new String[packages.size()]);
+            }
         }
     }
 
@@ -12284,8 +12357,19 @@
         }
 
         final int userId = mInjector.userHandleGetCallingUserId();
-        synchronized (getLockObject()) {
-            return getUserData(userId).mLockTaskPackages.contains(pkg);
+        // TODO(b/260560985): This is not the right check, as the flag could be enabled but there
+        //  could be an admin that hasn't targeted U.
+        if (isCoexistenceFlagEnabled()) {
+            LockTaskPolicy policy = mDevicePolicyEngine.getLocalPolicy(
+                    PolicyDefinition.LOCK_TASK, userId).getCurrentResolvedPolicy();
+            if (policy == null) {
+                return false;
+            }
+            return policy.getPackages().contains(pkg);
+        } else {
+            synchronized (getLockObject()) {
+                return getUserData(userId).mLockTaskPackages.contains(pkg);
+            }
         }
     }
 
@@ -12308,7 +12392,28 @@
             enforceCanCallLockTaskLocked(caller);
             enforceCanSetLockTaskFeaturesOnFinancedDevice(caller, flags);
             checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOCK_TASK_FEATURES);
-            setLockTaskFeaturesLocked(userHandle, flags);
+        }
+        if (isCoexistenceEnabled(caller)) {
+            EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who);
+            LockTaskPolicy currentPolicy = mDevicePolicyEngine.getLocalPolicy(
+                    PolicyDefinition.LOCK_TASK,
+                    caller.getUserId()).getPoliciesSetByAdmins().get(admin);
+            if (currentPolicy == null) {
+                throw new IllegalArgumentException("Can't set a lock task flags without setting "
+                        + "lock task packages first.");
+            }
+            LockTaskPolicy policy = currentPolicy.clone();
+            policy.setFlags(flags);
+
+            mDevicePolicyEngine.setLocalPolicy(
+                    PolicyDefinition.LOCK_TASK,
+                    EnforcingAdmin.createEnterpriseEnforcingAdmin(who),
+                    policy,
+                    caller.getUserId());
+        } else {
+            synchronized (getLockObject()) {
+                setLockTaskFeaturesLocked(userHandle, flags);
+            }
         }
     }
 
@@ -12326,7 +12431,21 @@
         final int userHandle = caller.getUserId();
         synchronized (getLockObject()) {
             enforceCanCallLockTaskLocked(caller);
-            return getUserData(userHandle).mLockTaskFeatures;
+        }
+
+        if (isCoexistenceEnabled(caller)) {
+            LockTaskPolicy policy = mDevicePolicyEngine.getLocalPolicy(
+                    PolicyDefinition.LOCK_TASK, userHandle).getCurrentResolvedPolicy();
+            if (policy == null) {
+                // We default on the power button menu, in order to be consistent with pre-P
+                // behaviour.
+                return DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
+            }
+            return policy.getFlags();
+        } else {
+            synchronized (getLockObject()) {
+                return getUserData(userHandle).mLockTaskFeatures;
+            }
         }
     }
 
@@ -13905,6 +14024,20 @@
             if (isFinancedDeviceOwner(caller)) {
                 enforcePermissionGrantStateOnFinancedDevice(packageName, permission);
             }
+        }
+        if (isCoexistenceEnabled(caller)) {
+            mDevicePolicyEngine.setLocalPolicy(
+                    PolicyDefinition.PERMISSION_GRANT(packageName, permission),
+                    // TODO(b/260573124): Add correct enforcing admin when permission changes are
+                    //  merged, and don't forget to handle delegates! Enterprise admins assume
+                    //  component name isn't null.
+                    EnforcingAdmin.createEnterpriseEnforcingAdmin(caller.getComponentName()),
+                    grantState,
+                    caller.getUserId());
+            // TODO: update javadoc to reflect that callback no longer return success/failure
+            callback.sendResult(Bundle.EMPTY);
+        } else {
+            synchronized (getLockObject()) {
             long ident = mInjector.binderClearCallingIdentity();
             try {
                 boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId())
@@ -13921,14 +14054,16 @@
                     callback.sendResult(null);
                     return;
                 }
-                if (grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
+                if (grantState == PERMISSION_GRANT_STATE_GRANTED
                         || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
                         || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) {
                     AdminPermissionControlParams permissionParams =
-                            new AdminPermissionControlParams(packageName, permission, grantState,
+                            new AdminPermissionControlParams(packageName, permission,
+                                    grantState,
                                     canAdminGrantSensorsPermissionsForUser(caller.getUserId()));
                     mInjector.getPermissionControllerManager(caller.getUserHandle())
-                            .setRuntimePermissionGrantStateByDeviceAdmin(caller.getPackageName(),
+                            .setRuntimePermissionGrantStateByDeviceAdmin(
+                                    caller.getPackageName(),
                                     permissionParams, mContext.getMainExecutor(),
                                     (permissionWasSet) -> {
                                         if (isPostQAdmin && !permissionWasSet) {
@@ -13947,13 +14082,14 @@
 
                                         callback.sendResult(Bundle.EMPTY);
                                     });
-                }
-            } catch (SecurityException e) {
-                Slogf.e(LOG_TAG, "Could not set permission grant state", e);
+                    }
+                } catch (SecurityException e) {
+                    Slogf.e(LOG_TAG, "Could not set permission grant state", e);
 
-                callback.sendResult(null);
-            } finally {
-                mInjector.binderRestoreCallingIdentity(ident);
+                    callback.sendResult(null);
+                } finally {
+                    mInjector.binderRestoreCallingIdentity(ident);
+                }
             }
         }
     }
@@ -19017,4 +19153,18 @@
             return result;
         });
     }
+
+    // TODO(b/260560985): properly gate coexistence changes
+    private boolean isCoexistenceEnabled(CallerIdentity caller) {
+        return isCoexistenceFlagEnabled()
+                && mInjector.isChangeEnabled(
+                        ENABLE_COEXISTENCE_CHANGE, caller.getPackageName(), caller.getUserId());
+    }
+
+    private boolean isCoexistenceFlagEnabled() {
+        return DeviceConfig.getBoolean(
+                NAMESPACE_DEVICE_POLICY_MANAGER,
+                ENABLE_COEXISTENCE_FLAG,
+                DEFAULT_ENABLE_COEXISTENCE_FLAG);
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
index 3152f0b..d5949dd 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
@@ -16,24 +16,35 @@
 
 package com.android.server.devicepolicy;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.util.Objects;
 
 final class IntegerPolicySerializer extends PolicySerializer<Integer> {
 
     @Override
-    void saveToXml(TypedXmlSerializer serializer, String attributeName, Integer value)
+    void saveToXml(TypedXmlSerializer serializer, String attributeName, @NonNull Integer value)
             throws IOException {
+        Objects.requireNonNull(value);
         serializer.attributeInt(/* namespace= */ null, attributeName, value);
     }
 
+    @Nullable
     @Override
-    Integer readFromXml(TypedXmlPullParser parser, String attributeName)
-            throws XmlPullParserException {
-        return parser.getAttributeInt(/* namespace= */ null, attributeName);
+    Integer readFromXml(TypedXmlPullParser parser, String attributeName) {
+        try {
+            return parser.getAttributeInt(/* namespace= */ null, attributeName);
+        } catch (XmlPullParserException e) {
+            Log.e(DevicePolicyEngine.TAG, "Error parsing Integer policy value", e);
+            return null;
+        }
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java
index 9360fd7..d3e8de4 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java
@@ -16,7 +16,10 @@
 
 package com.android.server.devicepolicy;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.admin.DevicePolicyManager;
+import android.util.Log;
 
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
@@ -24,19 +27,16 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.util.HashSet;
 import java.util.Objects;
 import java.util.Set;
 
 final class LockTaskPolicy {
-    private Set<String> mPackages;
-    private int mFlags;
+    static final int DEFAULT_LOCK_TASK_FLAG = DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
+    private Set<String> mPackages = new HashSet<>();
+    private int mFlags = DEFAULT_LOCK_TASK_FLAG;
 
-    LockTaskPolicy(@Nullable Set<String> packages, int flags) {
-        mPackages = packages;
-        mFlags = flags;
-    }
-
-    @Nullable
+    @NonNull
     Set<String> getPackages() {
         return mPackages;
     }
@@ -45,8 +45,20 @@
         return mFlags;
     }
 
-    void setPackages(Set<String> packages) {
-        mPackages = packages;
+    LockTaskPolicy(Set<String> packages) {
+        Objects.requireNonNull(packages);
+        mPackages.addAll(packages);
+    }
+
+    private LockTaskPolicy(Set<String> packages, int flags) {
+        Objects.requireNonNull(packages);
+        mPackages = new HashSet<>(packages);
+        mFlags = flags;
+    }
+
+    void setPackages(@NonNull Set<String> packages) {
+        Objects.requireNonNull(packages);
+        mPackages = new HashSet<>(packages);
     }
 
     void setFlags(int flags) {
@@ -54,6 +66,13 @@
     }
 
     @Override
+    public LockTaskPolicy clone() {
+        LockTaskPolicy policy = new LockTaskPolicy(mPackages);
+        policy.setFlags(mFlags);
+        return policy;
+    }
+
+    @Override
     public boolean equals(@Nullable Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
@@ -67,6 +86,11 @@
         return Objects.hash(mPackages, mFlags);
     }
 
+    @Override
+    public String toString() {
+        return "mPackages= " + String.join(", ", mPackages) + "; mFlags= " + mFlags;
+    }
+
     static final class LockTaskPolicySerializer extends PolicySerializer<LockTaskPolicy> {
 
         private static final String ATTR_PACKAGES = ":packages";
@@ -74,15 +98,17 @@
         private static final String ATTR_FLAGS = ":flags";
 
         @Override
-        void saveToXml(
-                TypedXmlSerializer serializer, String attributeNamePrefix, LockTaskPolicy value)
-                throws IOException {
-            if (value.mPackages != null) {
-                serializer.attribute(
-                        /* namespace= */ null,
-                        attributeNamePrefix + ATTR_PACKAGES,
-                        String.join(ATTR_PACKAGES_SEPARATOR, value.mPackages));
+        void saveToXml(TypedXmlSerializer serializer, String attributeNamePrefix,
+                @NonNull LockTaskPolicy value) throws IOException {
+            Objects.requireNonNull(value);
+            if (value.mPackages == null || value.mPackages.isEmpty()) {
+                throw new IllegalArgumentException("Error saving LockTaskPolicy to file, lock task "
+                        + "packages must be present");
             }
+            serializer.attribute(
+                    /* namespace= */ null,
+                    attributeNamePrefix + ATTR_PACKAGES,
+                    String.join(ATTR_PACKAGES_SEPARATOR, value.mPackages));
             serializer.attributeInt(
                     /* namespace= */ null,
                     attributeNamePrefix + ATTR_FLAGS,
@@ -90,18 +116,24 @@
         }
 
         @Override
-        LockTaskPolicy readFromXml(TypedXmlPullParser parser, String attributeNamePrefix)
-                throws XmlPullParserException {
+        LockTaskPolicy readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) {
             String packagesStr = parser.getAttributeValue(
                     /* namespace= */ null,
                     attributeNamePrefix + ATTR_PACKAGES);
-            Set<String> packages = packagesStr == null
-                    ? null
-                    : Set.of(packagesStr.split(ATTR_PACKAGES_SEPARATOR));
-            int flags = parser.getAttributeInt(
-                    /* namespace= */ null,
-                    attributeNamePrefix + ATTR_FLAGS);
-            return new LockTaskPolicy(packages, flags);
+            if (packagesStr == null) {
+                Log.e(DevicePolicyEngine.TAG, "Error parsing LockTask policy value.");
+                return null;
+            }
+            Set<String> packages = Set.of(packagesStr.split(ATTR_PACKAGES_SEPARATOR));
+            try {
+                int flags = parser.getAttributeInt(
+                        /* namespace= */ null,
+                        attributeNamePrefix + ATTR_FLAGS);
+                return new LockTaskPolicy(packages, flags);
+            } catch (XmlPullParserException e) {
+                Log.e(DevicePolicyEngine.TAG, "Error parsing LockTask policy value", e);
+                return null;
+            }
         }
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 3a18cb9..a787a0b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -25,8 +25,6 @@
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 
-import org.xmlpull.v1.XmlPullParserException;
-
 import java.io.IOException;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -225,8 +223,8 @@
         mPolicySerializer.saveToXml(serializer, attributeName, value);
     }
 
-    V readPolicyValueFromXml(TypedXmlPullParser parser, String attributeName)
-            throws XmlPullParserException {
+    @Nullable
+    V readPolicyValueFromXml(TypedXmlPullParser parser, String attributeName) {
         return mPolicySerializer.readFromXml(parser, attributeName);
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index b645b97..74b6f9e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -29,6 +29,7 @@
 
 import com.android.server.utils.Slogf;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
@@ -53,7 +54,7 @@
     static boolean setPermissionGrantState(
             @Nullable Integer grantState, @NonNull Context context, int userId,
             @NonNull String[] args) {
-        Binder.withCleanCallingIdentity(() -> {
+        return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
             if (args == null || args.length < 2) {
                 throw new IllegalArgumentException("Package name and permission name must be "
                         + "provided as arguments");
@@ -84,8 +85,7 @@
                 // TODO: add logging
                 return false;
             }
-        });
-        return true;
+        }));
     }
 
     @NonNull
@@ -106,9 +106,14 @@
 
     static boolean setLockTask(
             @Nullable LockTaskPolicy policy, @NonNull Context context, int userId) {
-        DevicePolicyManagerService.updateLockTaskPackagesLocked(
-                context, List.copyOf(policy.getPackages()), userId);
-        DevicePolicyManagerService.updateLockTaskFeaturesLocked(policy.getFlags(), userId);
+        List<String> packages = Collections.emptyList();
+        int flags = LockTaskPolicy.DEFAULT_LOCK_TASK_FLAG;
+        if (policy != null) {
+            packages = List.copyOf(policy.getPackages());
+            flags = policy.getFlags();
+        }
+        DevicePolicyManagerService.updateLockTaskPackagesLocked(context, packages, userId);
+        DevicePolicyManagerService.updateLockTaskFeaturesLocked(flags, userId);
         return true;
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
index b3259d3..528d3b0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
@@ -16,16 +16,15 @@
 
 package com.android.server.devicepolicy;
 
+import android.annotation.NonNull;
+
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 
-import org.xmlpull.v1.XmlPullParserException;
-
 import java.io.IOException;
 
 abstract class PolicySerializer<V> {
-    abstract void saveToXml(TypedXmlSerializer serializer, String attributeName, V value)
+    abstract void saveToXml(TypedXmlSerializer serializer, String attributeName, @NonNull V value)
             throws IOException;
-    abstract V readFromXml(TypedXmlPullParser parser, String attributeName)
-            throws XmlPullParserException;
+    abstract V readFromXml(TypedXmlPullParser parser, String attributeName);
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
index 5fc3cb0..d3dee98 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
@@ -40,7 +40,7 @@
     private static final String ATTR_RESOLVED_POLICY = "resolved-policy";
 
     private final PolicyDefinition<V> mPolicyDefinition;
-    private final LinkedHashMap<EnforcingAdmin, V> mAdminsPolicy = new LinkedHashMap<>();
+    private final LinkedHashMap<EnforcingAdmin, V> mPoliciesSetByAdmins = new LinkedHashMap<>();
     private V mCurrentResolvedPolicy;
 
     PolicyState(@NonNull PolicyDefinition<V> policyDefinition) {
@@ -49,13 +49,13 @@
 
     private PolicyState(
             @NonNull PolicyDefinition<V> policyDefinition,
-            @NonNull LinkedHashMap<EnforcingAdmin, V> adminsPolicy,
+            @NonNull LinkedHashMap<EnforcingAdmin, V> policiesSetByAdmins,
             V currentEnforcedPolicy) {
         Objects.requireNonNull(policyDefinition);
-        Objects.requireNonNull(adminsPolicy);
+        Objects.requireNonNull(policiesSetByAdmins);
 
         mPolicyDefinition = policyDefinition;
-        mAdminsPolicy.putAll(adminsPolicy);
+        mPoliciesSetByAdmins.putAll(policiesSetByAdmins);
         mCurrentResolvedPolicy = currentEnforcedPolicy;
     }
 
@@ -63,7 +63,7 @@
      * Returns {@code true} if the resolved policy has changed, {@code false} otherwise.
      */
     boolean setPolicy(@NonNull EnforcingAdmin admin, @NonNull V value) {
-        mAdminsPolicy.put(Objects.requireNonNull(admin), Objects.requireNonNull(value));
+        mPoliciesSetByAdmins.put(Objects.requireNonNull(admin), Objects.requireNonNull(value));
 
         return resolvePolicy();
     }
@@ -71,15 +71,19 @@
     boolean removePolicy(@NonNull EnforcingAdmin admin) {
         Objects.requireNonNull(admin);
 
-        if (mAdminsPolicy.remove(admin) == null) {
+        if (mPoliciesSetByAdmins.remove(admin) == null) {
             return false;
         }
 
         return resolvePolicy();
     }
 
+    LinkedHashMap<EnforcingAdmin, V> getPoliciesSetByAdmins() {
+        return mPoliciesSetByAdmins;
+    }
+
     private boolean resolvePolicy() {
-        V resolvedPolicy = mPolicyDefinition.resolvePolicy(mAdminsPolicy);
+        V resolvedPolicy = mPolicyDefinition.resolvePolicy(mPoliciesSetByAdmins);
         boolean policyChanged = !Objects.equals(resolvedPolicy, mCurrentResolvedPolicy);
         mCurrentResolvedPolicy = resolvedPolicy;
 
@@ -94,14 +98,16 @@
     void saveToXml(TypedXmlSerializer serializer) throws IOException {
         mPolicyDefinition.saveToXml(serializer);
 
-        mPolicyDefinition.savePolicyValueToXml(
-                serializer, ATTR_RESOLVED_POLICY, mCurrentResolvedPolicy);
+        if (mCurrentResolvedPolicy != null) {
+            mPolicyDefinition.savePolicyValueToXml(
+                    serializer, ATTR_RESOLVED_POLICY, mCurrentResolvedPolicy);
+        }
 
-        for (EnforcingAdmin admin : mAdminsPolicy.keySet()) {
+        for (EnforcingAdmin admin : mPoliciesSetByAdmins.keySet()) {
             serializer.startTag(/* namespace= */ null, TAG_ADMIN_POLICY_ENTRY);
 
             mPolicyDefinition.savePolicyValueToXml(
-                    serializer, ATTR_POLICY_VALUE, mAdminsPolicy.get(admin));
+                    serializer, ATTR_POLICY_VALUE, mPoliciesSetByAdmins.get(admin));
 
             serializer.startTag(/* namespace= */ null, TAG_ENFORCING_ADMIN_ENTRY);
             admin.saveToXml(serializer);
diff --git a/services/proguard.flags b/services/proguard.flags
index 27fe505..6cdf11c 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -88,6 +88,7 @@
 -keep,allowoptimization,allowaccessmodification class com.android.server.location.gnss.GnssPowerStats { *; }
 -keep,allowoptimization,allowaccessmodification class com.android.server.location.gnss.hal.GnssNative { *; }
 -keep,allowoptimization,allowaccessmodification class com.android.server.pm.PackageManagerShellCommandDataLoader { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorManagerInternal$RuntimeSensorStateChangeCallback { *; }
 -keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorManagerInternal$ProximityActiveListener { *; }
 -keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorService { *; }
 -keep,allowoptimization,allowaccessmodification class com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareImpl$AudioSessionProvider$AudioSession { *; }
diff --git a/services/tests/InputMethodSystemServerTests/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/AndroidManifest.xml
index 12e7cfc..212ec14 100644
--- a/services/tests/InputMethodSystemServerTests/AndroidManifest.xml
+++ b/services/tests/InputMethodSystemServerTests/AndroidManifest.xml
@@ -18,6 +18,11 @@
           package="com.android.frameworks.inputmethodtests">
 
     <uses-sdk android:targetSdkVersion="31" />
+    <queries>
+        <intent>
+            <action android:name="android.view.InputMethod" />
+        </intent>
+    </queries>
 
     <!-- Permissions required for granting and logging -->
     <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/>
@@ -29,9 +34,23 @@
     <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
 
+    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS"/>
+    <uses-permission android:name="android.permission.BIND_INPUT_METHOD" />
+
     <application android:testOnly="true"
                  android:debuggable="true">
         <uses-library android:name="android.test.runner" />
+        <service android:name="com.android.server.inputmethod.InputMethodBindingControllerTest$EmptyInputMethodService"
+                 android:label="Empty IME"
+                 android:permission="android.permission.BIND_INPUT_METHOD"
+                 android:process=":service"
+                 android:exported="true">
+            <intent-filter>
+                <action android:name="android.view.InputMethod"/>
+            </intent-filter>
+            <meta-data android:name="android.view.im"
+                       android:resource="@xml/method"/>
+        </service>
     </application>
 
     <instrumentation
diff --git a/services/tests/InputMethodSystemServerTests/res/xml/method.xml b/services/tests/InputMethodSystemServerTests/res/xml/method.xml
new file mode 100644
index 0000000..89b06bb
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/res/xml/method.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<input-method xmlns:android="http://schemas.android.com/apk/res/android" />
\ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
new file mode 100644
index 0000000..42d373b
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2022 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.inputmethod;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.inputmethodservice.InputMethodService;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.view.inputmethod.InputMethodInfo;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.inputmethod.InputBindResult;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+@RunWith(AndroidJUnit4.class)
+public class InputMethodBindingControllerTest extends InputMethodManagerServiceTestBase {
+
+    private static final String PACKAGE_NAME = "com.android.frameworks.inputmethodtests";
+    private static final String TEST_SERVICE_NAME =
+            "com.android.server.inputmethod.InputMethodBindingControllerTest"
+                    + "$EmptyInputMethodService";
+    private static final String TEST_IME_ID = PACKAGE_NAME + "/" + TEST_SERVICE_NAME;
+    private static final long TIMEOUT_IN_SECONDS = 3;
+
+    private InputMethodBindingController mBindingController;
+    private Instrumentation mInstrumentation;
+    private final int mImeConnectionBindFlags =
+            InputMethodBindingController.IME_CONNECTION_BIND_FLAGS
+                    & ~Context.BIND_SCHEDULE_LIKE_TOP_APP;
+    private CountDownLatch mCountDownLatch;
+
+    public static class EmptyInputMethodService extends InputMethodService {}
+
+    @Before
+    public void setUp() throws RemoteException {
+        super.setUp();
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mCountDownLatch = new CountDownLatch(1);
+        // Remove flag Context.BIND_SCHEDULE_LIKE_TOP_APP because in tests we are not calling
+        // from system.
+        mBindingController =
+                new InputMethodBindingController(
+                        mInputMethodManagerService, mImeConnectionBindFlags, mCountDownLatch);
+    }
+
+    @Test
+    public void testBindCurrentMethod_noIme() {
+        synchronized (ImfLock.class) {
+            mBindingController.setSelectedMethodId(null);
+            InputBindResult result = mBindingController.bindCurrentMethod();
+            assertThat(result).isEqualTo(InputBindResult.NO_IME);
+        }
+    }
+
+    @Test
+    public void testBindCurrentMethod_unknownId() {
+        synchronized (ImfLock.class) {
+            mBindingController.setSelectedMethodId("unknown ime id");
+        }
+        assertThrows(IllegalArgumentException.class, () -> {
+            synchronized (ImfLock.class) {
+                mBindingController.bindCurrentMethod();
+            }
+        });
+    }
+
+    @Test
+    public void testBindCurrentMethod_notConnected() {
+        synchronized (ImfLock.class) {
+            mBindingController.setSelectedMethodId(TEST_IME_ID);
+            doReturn(false)
+                    .when(mContext)
+                    .bindServiceAsUser(
+                            any(Intent.class),
+                            any(ServiceConnection.class),
+                            anyInt(),
+                            any(UserHandle.class));
+
+            InputBindResult result = mBindingController.bindCurrentMethod();
+            assertThat(result).isEqualTo(InputBindResult.IME_NOT_CONNECTED);
+        }
+    }
+
+    @Test
+    public void testBindAndUnbindMethod() throws Exception {
+        // Bind with main connection
+        testBindCurrentMethodWithMainConnection();
+
+        // Bind with visible connection
+        testBindCurrentMethodWithVisibleConnection();
+
+        // Unbind both main and visible connections
+        testUnbindCurrentMethod();
+    }
+
+    private void testBindCurrentMethodWithMainConnection() throws Exception {
+        synchronized (ImfLock.class) {
+            mBindingController.setSelectedMethodId(TEST_IME_ID);
+        }
+        InputMethodInfo info = mInputMethodManagerService.mMethodMap.get(TEST_IME_ID);
+        assertThat(info).isNotNull();
+        assertThat(info.getId()).isEqualTo(TEST_IME_ID);
+        assertThat(info.getServiceName()).isEqualTo(TEST_SERVICE_NAME);
+
+        // Bind input method with main connection. It is called on another thread because we should
+        // wait for onServiceConnected() to finish.
+        InputBindResult result = callOnMainSync(() -> {
+            synchronized (ImfLock.class) {
+                return mBindingController.bindCurrentMethod();
+            }
+        });
+
+        verify(mContext, times(1))
+                .bindServiceAsUser(
+                        any(Intent.class),
+                        any(ServiceConnection.class),
+                        eq(mImeConnectionBindFlags),
+                        any(UserHandle.class));
+        assertThat(result.result).isEqualTo(InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING);
+        assertThat(result.id).isEqualTo(info.getId());
+        synchronized (ImfLock.class) {
+            assertThat(mBindingController.hasConnection()).isTrue();
+            assertThat(mBindingController.getCurId()).isEqualTo(info.getId());
+            assertThat(mBindingController.getCurToken()).isNotNull();
+        }
+        // Wait for onServiceConnected()
+        mCountDownLatch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+
+        // Verify onServiceConnected() is called and bound successfully.
+        synchronized (ImfLock.class) {
+            assertThat(mBindingController.getCurMethod()).isNotNull();
+            assertThat(mBindingController.getCurMethodUid()).isNotEqualTo(Process.INVALID_UID);
+        }
+    }
+
+    private void testBindCurrentMethodWithVisibleConnection() {
+        mInstrumentation.runOnMainSync(() -> {
+            synchronized (ImfLock.class) {
+                mBindingController.setCurrentMethodVisible();
+            }
+        });
+        // Bind input method with visible connection
+        verify(mContext, times(1))
+                .bindServiceAsUser(
+                        any(Intent.class),
+                        any(ServiceConnection.class),
+                        eq(InputMethodBindingController.IME_VISIBLE_BIND_FLAGS),
+                        any(UserHandle.class));
+        synchronized (ImfLock.class) {
+            assertThat(mBindingController.isVisibleBound()).isTrue();
+        }
+    }
+
+    private void testUnbindCurrentMethod() {
+        mInstrumentation.runOnMainSync(() -> {
+            synchronized (ImfLock.class) {
+                mBindingController.unbindCurrentMethod();
+            }
+        });
+
+        synchronized (ImfLock.class) {
+            // Unbind both main connection and visible connection
+            assertThat(mBindingController.hasConnection()).isFalse();
+            assertThat(mBindingController.isVisibleBound()).isFalse();
+            verify(mContext, times(2)).unbindService(any(ServiceConnection.class));
+            assertThat(mBindingController.getCurToken()).isNull();
+            assertThat(mBindingController.getCurId()).isNull();
+            assertThat(mBindingController.getCurMethod()).isNull();
+            assertThat(mBindingController.getCurMethodUid()).isEqualTo(Process.INVALID_UID);
+        }
+    }
+
+    private static <V> V callOnMainSync(Callable<V> callable) {
+        AtomicReference<V> result = new AtomicReference<>();
+        InstrumentationRegistry.getInstrumentation()
+                .runOnMainSync(
+                        () -> {
+                            try {
+                                result.set(callable.call());
+                            } catch (Exception e) {
+                                throw new RuntimeException("Exception was thrown", e);
+                            }
+                        });
+        return result.get();
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index 1f66a11..7bf9a9e 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -17,7 +17,12 @@
 package com.android.server.pm.test.parsing.parcelling
 
 import android.content.Intent
-import android.content.pm.*
+import android.content.pm.ApplicationInfo
+import android.content.pm.ConfigurationInfo
+import android.content.pm.FeatureGroupInfo
+import android.content.pm.FeatureInfo
+import android.content.pm.PackageManager
+import android.content.pm.SigningDetails
 import android.net.Uri
 import android.os.Bundle
 import android.os.Parcelable
@@ -27,16 +32,32 @@
 import com.android.internal.R
 import com.android.server.pm.parsing.pkg.PackageImpl
 import com.android.server.pm.pkg.AndroidPackage
-import com.android.server.pm.pkg.component.*
+import com.android.server.pm.pkg.component.ParsedActivityImpl
+import com.android.server.pm.pkg.component.ParsedApexSystemServiceImpl
+import com.android.server.pm.pkg.component.ParsedAttributionImpl
+import com.android.server.pm.pkg.component.ParsedComponentImpl
+import com.android.server.pm.pkg.component.ParsedInstrumentationImpl
+import com.android.server.pm.pkg.component.ParsedIntentInfoImpl
+import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl
+import com.android.server.pm.pkg.component.ParsedPermissionImpl
+import com.android.server.pm.pkg.component.ParsedProcessImpl
+import com.android.server.pm.pkg.component.ParsedProviderImpl
+import com.android.server.pm.pkg.component.ParsedServiceImpl
+import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl
 import com.android.server.testutils.mockThrowOnUnmocked
 import com.android.server.testutils.whenever
 import java.security.KeyPairGenerator
 import java.security.PublicKey
+import java.util.UUID
 import kotlin.contracts.ExperimentalContracts
 
 @ExperimentalContracts
 class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, PackageImpl::class) {
 
+    companion object {
+        private val TEST_UUID = UUID.fromString("57554103-df3e-4475-ae7a-8feba49353ac")
+    }
+
     override val defaultImpl = PackageImpl.forTesting("com.example.test")
     override val creator = PackageImpl.CREATOR
 
@@ -72,8 +93,6 @@
         "getLongVersionCode",
         // Tested through constructor
         "getManifestPackageName",
-        // Utility methods
-        "getStorageUuid",
         // Removal not tested, irrelevant for parcelling concerns
         "removeUsesOptionalLibrary",
         "clearAdoptPermissions",
@@ -85,6 +104,7 @@
         // Tested manually
         "getMimeGroups",
         "getRequestedPermissions",
+        "getStorageUuid",
         // Tested through asSplit
         "asSplit",
         "getSplits",
@@ -155,7 +175,6 @@
         AndroidPackage::getResizeableActivity,
         AndroidPackage::getRestrictedAccountType,
         AndroidPackage::getRoundIconRes,
-        PackageImpl::getSeInfo,
         PackageImpl::getSecondaryCpuAbi,
         AndroidPackage::getSecondaryNativeLibraryDir,
         AndroidPackage::getSharedUserId,
@@ -241,7 +260,7 @@
     )
 
     override fun extraParams() = listOf(
-        getter(AndroidPackage::getVolumeUuid, "57554103-df3e-4475-ae7a-8feba49353ac"),
+        getter(AndroidPackage::getVolumeUuid, TEST_UUID.toString()),
         getter(AndroidPackage::isProfileable, true),
         getter(PackageImpl::getVersionCode, 3),
         getter(PackageImpl::getVersionCodeMajor, 9),
@@ -602,6 +621,8 @@
         expect.that(after.usesStaticLibrariesCertDigests!!.size).isEqualTo(1)
         expect.that(after.usesStaticLibrariesCertDigests!![0]).asList()
                 .containsExactly("testCertDigest2")
+
+        expect.that(after.storageUuid).isEqualTo(TEST_UUID)
     }
 
     private fun testKey() = KeyPairGenerator.getInstance("RSA")
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index 51fa851..e7c384b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -252,6 +252,7 @@
         noteBoot(4);
         assertTrue(RescueParty.isRebootPropertySet());
 
+        SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
         noteBoot(5);
         assertTrue(RescueParty.isFactoryResetPropertySet());
     }
@@ -276,6 +277,7 @@
         noteAppCrash(4, true);
         assertTrue(RescueParty.isRebootPropertySet());
 
+        SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
         noteAppCrash(5, true);
         assertTrue(RescueParty.isFactoryResetPropertySet());
     }
@@ -429,6 +431,27 @@
         for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
             noteBoot(i + 1);
         }
+        assertFalse(RescueParty.isFactoryResetPropertySet());
+        SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
+        noteBoot(LEVEL_FACTORY_RESET + 1);
+        assertTrue(RescueParty.isAttemptingFactoryReset());
+        assertTrue(RescueParty.isFactoryResetPropertySet());
+    }
+
+    @Test
+    public void testIsAttemptingFactoryResetOnlyAfterRebootCompleted() {
+        for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
+            noteBoot(i + 1);
+        }
+        int mitigationCount = LEVEL_FACTORY_RESET + 1;
+        assertFalse(RescueParty.isFactoryResetPropertySet());
+        noteBoot(mitigationCount++);
+        assertFalse(RescueParty.isFactoryResetPropertySet());
+        noteBoot(mitigationCount++);
+        assertFalse(RescueParty.isFactoryResetPropertySet());
+        noteBoot(mitigationCount++);
+        SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
+        noteBoot(mitigationCount + 1);
         assertTrue(RescueParty.isAttemptingFactoryReset());
         assertTrue(RescueParty.isFactoryResetPropertySet());
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
new file mode 100644
index 0000000..ea14ffb
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2022 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.am;
+
+import static android.os.Process.myPid;
+import static android.os.Process.myUid;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManagerInternal;
+import android.app.IApplicationThread;
+import android.app.usage.UsageStatsManagerInternal;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.util.Log;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.DropBoxManagerInternal;
+import com.android.server.LocalServices;
+import com.android.server.am.ActivityManagerService.Injector;
+import com.android.server.appop.AppOpsService;
+import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.server.wm.ActivityTaskManagerService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.util.Arrays;
+
+
+/**
+ * Tests to verify process starts are completed or timeout correctly
+ */
+@MediumTest
+@SuppressWarnings("GuardedBy")
+public class AsyncProcessStartTest {
+    private static final String TAG = "AsyncProcessStartTest";
+
+    private static final String PACKAGE = "com.foo";
+
+    @Rule
+    public final ApplicationExitInfoTest.ServiceThreadRule
+            mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();
+
+    private Context mContext;
+    private HandlerThread mHandlerThread;
+
+    @Mock
+    private AppOpsService mAppOpsService;
+    @Mock
+    private DropBoxManagerInternal mDropBoxManagerInt;
+    @Mock
+    private PackageManagerInternal mPackageManagerInt;
+    @Mock
+    private UsageStatsManagerInternal mUsageStatsManagerInt;
+    @Mock
+    private ActivityManagerInternal mActivityManagerInt;
+    @Mock
+    private ActivityTaskManagerInternal mActivityTaskManagerInt;
+    @Mock
+    private BatteryStatsService mBatteryStatsService;
+
+    private ActivityManagerService mRealAms;
+    private ActivityManagerService mAms;
+
+    private ProcessList mRealProcessList = new ProcessList();
+    private ProcessList mProcessList;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+
+        LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
+        LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt);
+
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
+
+        LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+        LocalServices.addService(ActivityManagerInternal.class, mActivityManagerInt);
+
+        LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
+        LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInt);
+
+        doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
+        doReturn(true).when(mActivityTaskManagerInt).attachApplication(any());
+        doNothing().when(mActivityTaskManagerInt).onProcessMapped(anyInt(), any());
+
+        mRealAms = new ActivityManagerService(
+                new TestInjector(mContext), mServiceThreadRule.getThread());
+        mRealAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
+        mRealAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
+        mRealAms.mAtmInternal = mActivityTaskManagerInt;
+        mRealAms.mPackageManagerInt = mPackageManagerInt;
+        mRealAms.mUsageStatsService = mUsageStatsManagerInt;
+        mRealAms.mProcessesReady = true;
+        mAms = spy(mRealAms);
+        mRealProcessList.mService = mAms;
+        mProcessList = spy(mRealProcessList);
+
+        doAnswer((invocation) -> {
+            Log.v(TAG, "Intercepting isProcStartValidLocked() for "
+                    + Arrays.toString(invocation.getArguments()));
+            return null;
+        }).when(mProcessList).isProcStartValidLocked(any(), anyLong());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mHandlerThread.quit();
+    }
+
+    private class TestInjector extends Injector {
+        TestInjector(Context context) {
+            super(context);
+        }
+
+        @Override
+        public AppOpsService getAppOpsService(File file, Handler handler) {
+            return mAppOpsService;
+        }
+
+        @Override
+        public Handler getUiHandler(ActivityManagerService service) {
+            return mHandlerThread.getThreadHandler();
+        }
+
+        @Override
+        public ProcessList getProcessList(ActivityManagerService service) {
+            return mRealProcessList;
+        }
+
+        @Override
+        public BatteryStatsService getBatteryStatsService() {
+            return mBatteryStatsService;
+        }
+    }
+
+    private ProcessRecord makeActiveProcessRecord(String packageName, boolean wedge)
+            throws Exception {
+        final ApplicationInfo ai = makeApplicationInfo(packageName);
+        return makeActiveProcessRecord(ai, wedge);
+    }
+
+    private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai, boolean wedge)
+            throws Exception {
+        final IApplicationThread thread = mock(IApplicationThread.class);
+        final IBinder threadBinder = new Binder();
+        doReturn(threadBinder).when(thread).asBinder();
+        doAnswer((invocation) -> {
+            Log.v(TAG, "Intercepting bindApplication() for "
+                    + Arrays.toString(invocation.getArguments()));
+            if (!wedge) {
+                mRealAms.finishAttachApplication(0);
+            }
+            return null;
+        }).when(thread).bindApplication(
+                any(), any(),
+                any(), any(),
+                any(), any(),
+                any(), any(),
+                any(),
+                any(), anyInt(),
+                anyBoolean(), anyBoolean(),
+                anyBoolean(), anyBoolean(), any(),
+                any(), any(), any(),
+                any(), any(),
+                any(), any(),
+                any(),
+                anyLong(), anyLong());
+
+        final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid));
+        r.setPid(myPid());
+        r.setStartUid(myUid());
+        r.setHostingRecord(new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST));
+        r.makeActive(thread, mAms.mProcessStats);
+        doNothing().when(r).killLocked(any(), any(), anyInt(), anyInt(), anyBoolean(),
+                anyBoolean());
+
+        return r;
+    }
+
+    static ApplicationInfo makeApplicationInfo(String packageName) {
+        final ApplicationInfo ai = new ApplicationInfo();
+        ai.packageName = packageName;
+        ai.processName = packageName;
+        ai.uid = myUid();
+        return ai;
+    }
+
+    /**
+     * Verify that we don't kill a normal process
+     */
+    @Test
+    public void testNormal() throws Exception {
+        ProcessRecord app = startProcessAndWait(false);
+
+        verify(app, never()).killLocked(any(), anyInt(), anyBoolean());
+    }
+
+    /**
+     * Verify that we kill a wedged process after the process start timeout
+     */
+    @Test
+    public void testWedged() throws Exception {
+        ProcessRecord app = startProcessAndWait(true);
+
+        verify(app).killLocked(any(), anyInt(), anyBoolean());
+    }
+
+    private ProcessRecord startProcessAndWait(boolean wedge) throws Exception {
+        final ProcessRecord app = makeActiveProcessRecord(PACKAGE, wedge);
+        final ApplicationInfo appInfo = makeApplicationInfo(PACKAGE);
+
+        mProcessList.handleProcessStartedLocked(app, app.getPid(), /* usingWrapper */ false,
+                /* expectedStartSeq */ 0, /* procAttached */ false);
+
+        app.getThread().bindApplication(PACKAGE, appInfo,
+                null, null,
+                null,
+                null,
+                null, null,
+                null,
+                null, 0,
+                false, false,
+                true, false,
+                null,
+                null, null,
+                null,
+                null, null, null,
+                null, null,
+                0, 0);
+
+        // Sleep until timeout should have triggered
+        SystemClock.sleep(ActivityManagerService.PROC_START_TIMEOUT + 1000);
+
+        return app;
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 66e7ec0..c87fd26 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -49,6 +49,7 @@
 import android.app.Activity;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
+import android.appwidget.AppWidgetManager;
 import android.content.IIntentReceiver;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -538,6 +539,59 @@
     }
 
     /**
+     * Verify that we don't let urgent broadcasts starve delivery of non-urgent
+     */
+    @Test
+    public void testUrgentStarvation() {
+        final BroadcastOptions optInteractive = BroadcastOptions.makeBasic();
+        optInteractive.setInteractive(true);
+
+        mConstants.MAX_CONSECUTIVE_URGENT_DISPATCHES = 2;
+        BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+                PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+
+        // mix of broadcasts, with more than 2 fg/urgent
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_APPLICATION_PREFERENCES),
+                        optInteractive), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE),
+                        optInteractive), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_INPUT_METHOD_CHANGED)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_NEW_OUTGOING_CALL),
+                        optInteractive), 0);
+
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction());
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_APPLICATION_PREFERENCES, queue.getActive().intent.getAction());
+        // after MAX_CONSECUTIVE_URGENT_DISPATCHES expect an ordinary one next
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction());
+        // and then back to prioritizing urgent ones
+        queue.makeActiveNextPending();
+        assertEquals(AppWidgetManager.ACTION_APPWIDGET_UPDATE,
+                queue.getActive().intent.getAction());
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_INPUT_METHOD_CHANGED, queue.getActive().intent.getAction());
+        // verify the reset-count-then-resume worked too
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_ALARM_CHANGED, queue.getActive().intent.getAction());
+    }
+
+    /**
      * Verify that sending a broadcast that removes any matching pending
      * broadcasts is applied as expected.
      */
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index dc77762..9f0d759 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -17,6 +17,9 @@
 package com.android.server.app;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.server.app.GameManagerService.CANCEL_GAME_LOADING_MODE;
+import static com.android.server.app.GameManagerService.LOADING_BOOST_MAX_DURATION;
+import static com.android.server.app.GameManagerService.SET_GAME_STATE;
 import static com.android.server.app.GameManagerService.WRITE_SETTINGS;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -58,6 +61,7 @@
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.hardware.power.Mode;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.PowerManagerInternal;
@@ -103,7 +107,8 @@
     private static final String PACKAGE_NAME_INVALID = "com.android.app";
     private static final int USER_ID_1 = 1001;
     private static final int USER_ID_2 = 1002;
-    private static final int DEFAULT_PACKAGE_UID = 12345;
+    // to pass the valid package check in some of the server methods
+    private static final int DEFAULT_PACKAGE_UID = Binder.getCallingUid();
 
     private MockitoSession mMockingSession;
     private String mPackageName;
@@ -204,28 +209,33 @@
                 .startMocking();
         mMockContext = new MockContext(InstrumentationRegistry.getContext());
         mPackageName = mMockContext.getPackageName();
-        final ApplicationInfo applicationInfo = new ApplicationInfo();
-        applicationInfo.category = ApplicationInfo.CATEGORY_GAME;
-        applicationInfo.packageName = mPackageName;
-        final PackageInfo pi = new PackageInfo();
-        pi.packageName = mPackageName;
-        pi.applicationInfo = applicationInfo;
-        final List<PackageInfo> packages = new ArrayList<>();
-        packages.add(pi);
-
+        mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_GAME);
         final Resources resources =
                 InstrumentationRegistry.getInstrumentation().getContext().getResources();
         when(mMockPackageManager.getResourcesForApplication(anyString()))
                 .thenReturn(resources);
-        when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), anyInt()))
-                .thenReturn(packages);
-        when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
-                .thenReturn(applicationInfo);
         when(mMockPackageManager.getPackageUidAsUser(mPackageName, USER_ID_1)).thenReturn(
                 DEFAULT_PACKAGE_UID);
         LocalServices.addService(PowerManagerInternal.class, mMockPowerManager);
     }
 
+    private void mockAppCategory(String packageName, @ApplicationInfo.Category int category)
+            throws Exception {
+        reset(mMockPackageManager);
+        final ApplicationInfo gameApplicationInfo = new ApplicationInfo();
+        gameApplicationInfo.category = category;
+        gameApplicationInfo.packageName = packageName;
+        final PackageInfo pi = new PackageInfo();
+        pi.packageName = packageName;
+        pi.applicationInfo = gameApplicationInfo;
+        final List<PackageInfo> packages = new ArrayList<>();
+        packages.add(pi);
+        when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), anyInt()))
+                .thenReturn(packages);
+        when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+                .thenReturn(gameApplicationInfo);
+    }
+
     @After
     public void tearDown() throws Exception {
         LocalServices.removeServiceForTest(PowerManagerInternal.class);
@@ -456,17 +466,22 @@
      * By default game mode is set to STANDARD
      */
     @Test
-    public void testGameModeDefaultValue() {
-        GameManagerService gameManagerService =
-                new GameManagerService(mMockContext, mTestLooper.getLooper());
-
-        startUser(gameManagerService, USER_ID_1);
+    public void testGetGameMode_defaultValue() {
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
         mockModifyGameModeGranted();
-
         assertEquals(GameManager.GAME_MODE_STANDARD,
                 gameManagerService.getGameMode(mPackageName, USER_ID_1));
     }
 
+    @Test
+    public void testGetGameMode_nonGame() throws Exception {
+        mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO);
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        mockModifyGameModeGranted();
+        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+                gameManagerService.getGameMode(mPackageName, USER_ID_1));
+    }
+
     /**
      * Test the default behaviour for a nonexistent user.
      */
@@ -613,16 +628,21 @@
             int... requiredAvailableModes) {
         Arrays.sort(requiredAvailableModes);
         // check getAvailableGameModes
-        int[] reportedAvailableModes = gameManagerService.getAvailableGameModes(mPackageName);
+        int[] reportedAvailableModes = gameManagerService.getAvailableGameModes(mPackageName,
+                USER_ID_1);
         Arrays.sort(reportedAvailableModes);
         assertArrayEquals(requiredAvailableModes, reportedAvailableModes);
 
         // check GetModeInfo.getAvailableGameModes
         GameModeInfo info = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
-        assertNotNull(info);
-        reportedAvailableModes = info.getAvailableGameModes();
-        Arrays.sort(reportedAvailableModes);
-        assertArrayEquals(requiredAvailableModes, reportedAvailableModes);
+        if (requiredAvailableModes.length == 0) {
+            assertNull(info);
+        } else {
+            assertNotNull(info);
+            reportedAvailableModes = info.getAvailableGameModes();
+            Arrays.sort(reportedAvailableModes);
+            assertArrayEquals(requiredAvailableModes, reportedAvailableModes);
+        }
     }
 
     private void checkReportedOptedInGameModes(GameManagerService gameManagerService,
@@ -725,6 +745,14 @@
                 GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
     }
 
+    @Test
+    public void testDeviceConfig_nonGame() throws Exception {
+        mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO);
+        mockDeviceConfigAll();
+        mockModifyGameModeGranted();
+        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1));
+    }
+
     /**
      * Override device config for performance mode exists and is valid.
      */
@@ -1442,47 +1470,98 @@
     }
 
     @Test
-    public void testGameStateLoadingRequiresPerformanceMode() {
+    public void testSetGameState_loadingRequiresPerformanceMode() {
         mockDeviceConfigNone();
         mockModifyGameModeGranted();
-        GameManagerService gameManagerService =
-                new GameManagerService(mMockContext, mTestLooper.getLooper());
-        startUser(gameManagerService, USER_ID_1);
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
         GameState gameState = new GameState(true, GameState.MODE_NONE);
         gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
         mTestLooper.dispatchAll();
         verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean());
     }
 
-    private void setGameState(boolean isLoading) {
+    @Test
+    public void testSetGameStateLoading_withNoDeviceConfig() {
         mockDeviceConfigNone();
         mockModifyGameModeGranted();
-        GameManagerService gameManagerService =
-                new GameManagerService(mMockContext, mTestLooper.getLooper());
-        startUser(gameManagerService, USER_ID_1);
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
         gameManagerService.setGameMode(
                 mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
-        int testMode = GameState.MODE_NONE;
+        assertEquals(gameManagerService.getGameMode(mPackageName, USER_ID_1),
+                GameManager.GAME_MODE_PERFORMANCE);
+        int testMode = GameState.MODE_GAMEPLAY_INTERRUPTIBLE;
         int testLabel = 99;
         int testQuality = 123;
-        GameState gameState = new GameState(isLoading, testMode, testLabel, testQuality);
-        assertEquals(isLoading, gameState.isLoading());
+        GameState gameState = new GameState(true, testMode, testLabel, testQuality);
         assertEquals(testMode, gameState.getMode());
         assertEquals(testLabel, gameState.getLabel());
         assertEquals(testQuality, gameState.getQuality());
         gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
         mTestLooper.dispatchAll();
-        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, isLoading);
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, true);
+        reset(mMockPowerManager);
+        assertTrue(
+                gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME_LOADING, false);
+        mTestLooper.moveTimeForward(GameManagerService.LOADING_BOOST_MAX_DURATION);
+        mTestLooper.dispatchAll();
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
     }
 
     @Test
-    public void testSetGameStateLoading() {
-        setGameState(true);
+    public void testSetGameStateLoading_withDeviceConfig() {
+        String configString = "mode=2,loadingBoost=2000";
+        when(DeviceConfig.getProperty(anyString(), anyString()))
+                .thenReturn(configString);
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        gameManagerService.setGameMode(
+                mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+        GameState gameState = new GameState(true, GameState.MODE_GAMEPLAY_INTERRUPTIBLE, 99, 123);
+        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+        mTestLooper.dispatchAll();
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, true);
+        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME_LOADING, false);
+        reset(mMockPowerManager);
+        assertTrue(
+                gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+        mTestLooper.moveTimeForward(2000);
+        mTestLooper.dispatchAll();
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
     }
 
     @Test
     public void testSetGameStateNotLoading() {
-        setGameState(false);
+        mockDeviceConfigNone();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        gameManagerService.setGameMode(
+                mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+        int testMode = GameState.MODE_GAMEPLAY_UNINTERRUPTIBLE;
+        int testLabel = 99;
+        int testQuality = 123;
+        GameState gameState = new GameState(false, testMode, testLabel, testQuality);
+        assertFalse(gameState.isLoading());
+        assertEquals(testMode, gameState.getMode());
+        assertEquals(testLabel, gameState.getLabel());
+        assertEquals(testQuality, gameState.getQuality());
+        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+        assertTrue(gameManagerService.mHandler.hasEqualMessages(SET_GAME_STATE, gameState));
+        mTestLooper.dispatchAll();
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
+        assertFalse(
+                gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+    }
+
+    @Test
+    public void testSetGameState_nonGame() throws Exception {
+        mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO);
+        mockDeviceConfigNone();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        GameState gameState = new GameState(true, GameState.MODE_NONE);
+        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+        assertFalse(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
     }
 
     private List<String> readGameModeInterventionList() throws Exception {
@@ -1780,6 +1859,21 @@
     }
 
     @Test
+    public void testUpdateCustomGameModeConfiguration_nonGame() throws Exception {
+        mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE);
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        gameManagerService.updateCustomGameModeConfiguration(mPackageName,
+                new GameModeConfiguration.Builder().setScalingFactor(0.35f).setFpsOverride(
+                        60).build(),
+                USER_ID_1);
+        assertFalse(gameManagerService.mHandler.hasMessages(WRITE_SETTINGS));
+        GameManagerService.GamePackageConfiguration pkgConfig = gameManagerService.getConfig(
+                mPackageName, USER_ID_1);
+        assertNull(pkgConfig);
+    }
+
+    @Test
     public void testWritingSettingFile_onShutdown() throws InterruptedException {
         mockModifyGameModeGranted();
         mockDeviceConfigAll();
@@ -1939,4 +2033,111 @@
         }
         folder.delete();
     }
+
+    @Test
+    public void testNotifyGraphicsEnvironmentSetup() {
+        String configString = "mode=2,loadingBoost=2000";
+        when(DeviceConfig.getProperty(anyString(), anyString()))
+                .thenReturn(configString);
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+        gameManagerService.notifyGraphicsEnvironmentSetup(mPackageName, USER_ID_1);
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, true);
+        reset(mMockPowerManager);
+        assertTrue(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+        mTestLooper.moveTimeForward(2000);
+        mTestLooper.dispatchAll();
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
+    }
+
+    @Test
+    public void testNotifyGraphicsEnvironmentSetup_outOfBoundBoostValue() {
+        String configString = "mode=2,loadingBoost=0:mode=3,loadingBoost=7000";
+        when(DeviceConfig.getProperty(anyString(), anyString()))
+                .thenReturn(configString);
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+        gameManagerService.notifyGraphicsEnvironmentSetup(mPackageName, USER_ID_1);
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, true);
+        reset(mMockPowerManager);
+        assertTrue(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+        mTestLooper.moveTimeForward(100);
+        mTestLooper.dispatchAll();
+        // 0 loading boost value should still trigger max timeout
+        verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean());
+        assertTrue(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+        mTestLooper.moveTimeForward(LOADING_BOOST_MAX_DURATION);
+        mTestLooper.dispatchAll();
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
+        reset(mMockPowerManager);
+        assertFalse(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+
+        // 7000 loading boost value should exceed the max timeout of 5s and be bounded
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
+        gameManagerService.notifyGraphicsEnvironmentSetup(mPackageName, USER_ID_1);
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, true);
+        reset(mMockPowerManager);
+        assertTrue(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+        mTestLooper.moveTimeForward(LOADING_BOOST_MAX_DURATION);
+        mTestLooper.dispatchAll();
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
+        assertFalse(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+    }
+
+    @Test
+    public void testNotifyGraphicsEnvironmentSetup_noDeviceConfig() {
+        mockDeviceConfigNone();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        gameManagerService.notifyGraphicsEnvironmentSetup(mPackageName, USER_ID_1);
+        verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean());
+        assertFalse(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+    }
+
+    @Test
+    public void testNotifyGraphicsEnvironmentSetup_noLoadingBoostValue() {
+        mockDeviceConfigAll();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        gameManagerService.notifyGraphicsEnvironmentSetup(mPackageName, USER_ID_1);
+        verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean());
+        assertFalse(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+    }
+
+    @Test
+    public void testNotifyGraphicsEnvironmentSetup_nonGame() throws Exception {
+        String configString = "mode=2,loadingBoost=2000";
+        when(DeviceConfig.getProperty(anyString(), anyString()))
+                .thenReturn(configString);
+        mockModifyGameModeGranted();
+        mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE);
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+                gameManagerService.getGameMode(mPackageName, USER_ID_1));
+        gameManagerService.notifyGraphicsEnvironmentSetup(mPackageName, USER_ID_1);
+        verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean());
+        assertFalse(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+    }
+
+    @Test
+    public void testNotifyGraphicsEnvironmentSetup_differentApp() throws Exception {
+        String configString = "mode=2,loadingBoost=2000";
+        when(DeviceConfig.getProperty(anyString(), anyString()))
+                .thenReturn(configString);
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        String someGamePkg = "some.game";
+        mockAppCategory(someGamePkg, ApplicationInfo.CATEGORY_GAME);
+        when(mMockPackageManager.getPackageUidAsUser(someGamePkg, USER_ID_1)).thenReturn(
+                DEFAULT_PACKAGE_UID + 1);
+        gameManagerService.setGameMode(someGamePkg, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_PERFORMANCE,
+                gameManagerService.getGameMode(someGamePkg, USER_ID_1));
+        gameManagerService.notifyGraphicsEnvironmentSetup(someGamePkg, USER_ID_1);
+        verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean());
+        assertFalse(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
index 5dc1251..be13bad 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
@@ -48,7 +48,7 @@
     StaticMockitoSession mSession;
 
     @Mock
-    AppOpsService.Constants mConstants;
+    AppOpsServiceImpl.Constants mConstants;
 
     @Mock
     Context mContext;
@@ -57,7 +57,7 @@
     Handler mHandler;
 
     @Mock
-    AppOpsServiceInterface mLegacyAppOpsService;
+    AppOpsCheckingServiceInterface mLegacyAppOpsService;
 
     AppOpsRestrictions mAppOpsRestrictions;
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
index c0688d1..7d4bc6f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
@@ -22,6 +22,8 @@
 import static android.app.AppOpsManager.OP_READ_SMS;
 import static android.app.AppOpsManager.OP_WIFI_SCAN;
 import static android.app.AppOpsManager.OP_WRITE_SMS;
+import static android.app.AppOpsManager.resolvePackageName;
+import static android.os.Process.INVALID_UID;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -39,6 +41,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
 
+import android.app.AppOpsManager;
 import android.app.AppOpsManager.OpEntry;
 import android.app.AppOpsManager.PackageOps;
 import android.content.ContentResolver;
@@ -86,13 +89,13 @@
 
     private File mAppOpsFile;
     private Handler mHandler;
-    private AppOpsService mAppOpsService;
+    private AppOpsServiceImpl mAppOpsService;
     private int mMyUid;
     private long mTestStartMillis;
     private StaticMockitoSession mMockingSession;
 
     private void setupAppOpsService() {
-        mAppOpsService = new AppOpsService(mAppOpsFile, mHandler, spy(sContext));
+        mAppOpsService = new AppOpsServiceImpl(mAppOpsFile, mHandler, spy(sContext));
         mAppOpsService.mHistoricalRegistry.systemReady(sContext.getContentResolver());
 
         // Always approve all permission checks
@@ -161,17 +164,20 @@
 
     @Test
     public void testNoteOperationAndGetOpsForPackage() {
-        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
-        mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED);
+        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+        mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED, null);
 
         // Note an op that's allowed.
-        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
+        mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
         List<PackageOps> loggedOps = getLoggedOps();
         assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
 
         // Note another op that's not allowed.
-        mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, sMyPackageName, null, false, null,
-                false);
+        mAppOpsService.noteOperationUnchecked(OP_WRITE_SMS, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
         loggedOps = getLoggedOps();
         assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
         assertContainsOp(loggedOps, OP_WRITE_SMS, -1, mTestStartMillis, MODE_ERRORED);
@@ -185,18 +191,20 @@
     @Test
     public void testNoteOperationAndGetOpsForPackage_controlledByDifferentOp() {
         // This op controls WIFI_SCAN
-        mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ALLOWED);
+        mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ALLOWED, null);
 
-        assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, sMyPackageName, null, false,
-                null, false).getOpMode()).isEqualTo(MODE_ALLOWED);
+        assertThat(mAppOpsService.noteOperationUnchecked(OP_WIFI_SCAN, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF)).isEqualTo(MODE_ALLOWED);
 
         assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, -1,
                 MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */);
 
         // Now set COARSE_LOCATION to ERRORED -> this will make WIFI_SCAN disabled as well.
-        mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ERRORED);
-        assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, sMyPackageName, null, false,
-                null, false).getOpMode()).isEqualTo(MODE_ERRORED);
+        mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ERRORED, null);
+        assertThat(mAppOpsService.noteOperationUnchecked(OP_WIFI_SCAN, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF)).isEqualTo(MODE_ERRORED);
 
         assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, mTestStartMillis,
                 MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */);
@@ -205,11 +213,14 @@
     // Tests the dumping and restoring of the in-memory state to/from XML.
     @Test
     public void testStatePersistence() {
-        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
-        mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED);
-        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
-        mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, sMyPackageName, null, false, null,
-                false);
+        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+        mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED, null);
+        mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
+        mAppOpsService.noteOperationUnchecked(OP_WRITE_SMS, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
         mAppOpsService.writeState();
 
         // Create a new app ops service which will initialize its state from XML.
@@ -224,8 +235,10 @@
     // Tests that ops are persisted during shutdown.
     @Test
     public void testShutdown() {
-        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
-        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
+        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+        mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
         mAppOpsService.shutdown();
 
         // Create a new app ops service which will initialize its state from XML.
@@ -238,8 +251,10 @@
 
     @Test
     public void testGetOpsForPackage() {
-        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
-        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
+        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+        mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
 
         // Query all ops
         List<PackageOps> loggedOps = mAppOpsService.getOpsForPackage(
@@ -267,8 +282,10 @@
 
     @Test
     public void testPackageRemoved() {
-        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
-        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
+        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+        mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
 
         List<PackageOps> loggedOps = getLoggedOps();
         assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
@@ -322,8 +339,10 @@
 
     @Test
     public void testUidRemoved() {
-        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
-        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
+        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+        mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
 
         List<PackageOps> loggedOps = getLoggedOps();
         assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
index 98e895a..3efd5e7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
@@ -76,7 +76,7 @@
     ActivityManagerInternal mAmi;
 
     @Mock
-    AppOpsService.Constants mConstants;
+    AppOpsServiceImpl.Constants mConstants;
 
     AppOpsUidStateTrackerTestExecutor mExecutor = new AppOpsUidStateTrackerTestExecutor();
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
index e08a715..298dbf4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
@@ -93,12 +93,13 @@
         }
     }
 
-    private void assertSameModes(SparseArray<AppOpsService.UidState> uidStates, int op1, int op2) {
+    private void assertSameModes(SparseArray<AppOpsServiceImpl.UidState> uidStates,
+            int op1, int op2) {
         int numberOfNonDefaultOps = 0;
         final int defaultModeOp1 = AppOpsManager.opToDefaultMode(op1);
         final int defaultModeOp2 = AppOpsManager.opToDefaultMode(op2);
         for(int i = 0; i < uidStates.size(); i++) {
-            final AppOpsService.UidState uidState = uidStates.valueAt(i);
+            final AppOpsServiceImpl.UidState uidState = uidStates.valueAt(i);
             SparseIntArray opModes = uidState.getNonDefaultUidModes();
             if (opModes != null) {
                 final int uidMode1 = opModes.get(op1, defaultModeOp1);
@@ -112,12 +113,12 @@
                 continue;
             }
             for (int j = 0; j < uidState.pkgOps.size(); j++) {
-                final AppOpsService.Ops ops = uidState.pkgOps.valueAt(j);
+                final AppOpsServiceImpl.Ops ops = uidState.pkgOps.valueAt(j);
                 if (ops == null) {
                     continue;
                 }
-                final AppOpsService.Op _op1 = ops.get(op1);
-                final AppOpsService.Op _op2 = ops.get(op2);
+                final AppOpsServiceImpl.Op _op1 = ops.get(op1);
+                final AppOpsServiceImpl.Op _op2 = ops.get(op2);
                 final int mode1 = (_op1 == null) ? defaultModeOp1 : _op1.getMode();
                 final int mode2 = (_op2 == null) ? defaultModeOp2 : _op2.getMode();
                 assertEquals(mode1, mode2);
@@ -158,8 +159,8 @@
         // Stub out package calls to disable AppOpsService#updatePermissionRevokedCompat
         when(testPM.getPackagesForUid(anyInt())).thenReturn(null);
 
-        AppOpsService testService = spy(
-                new AppOpsService(mAppOpsFile, mHandler, testContext)); // trigger upgrade
+        AppOpsServiceImpl testService = spy(
+                new AppOpsServiceImpl(mAppOpsFile, mHandler, testContext)); // trigger upgrade
         assertSameModes(testService.mUidStates, AppOpsManager.OP_RUN_IN_BACKGROUND,
                 AppOpsManager.OP_RUN_ANY_IN_BACKGROUND);
         mHandler.removeCallbacks(testService.mWriteRunner);
diff --git a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
new file mode 100644
index 0000000..7ab1363
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2022 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.cpu;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_ALL;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.Looper;
+import android.os.ServiceManager;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.LocalServices;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+public final class CpuMonitorServiceTest extends ExtendedMockitoTestCase {
+    private static final CpuAvailabilityMonitoringConfig TEST_CPU_AVAILABILITY_MONITORING_CONFIG =
+            new CpuAvailabilityMonitoringConfig.Builder(CPUSET_ALL)
+                    .addThreshold(30).addThreshold(70).build();
+
+    private static final CpuAvailabilityMonitoringConfig TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2 =
+            new CpuAvailabilityMonitoringConfig.Builder(CPUSET_ALL)
+                    .addThreshold(10).addThreshold(90).build();
+
+    @Mock
+    private Context mContext;
+    private CpuMonitorService mService;
+    private HandlerExecutor mHandlerExecutor;
+    private CpuMonitorInternal mLocalService;
+
+    @Override
+    protected void initializeSession(StaticMockitoSessionBuilder builder) {
+        builder.mockStatic(ServiceManager.class);
+    }
+
+    @Before
+    public void setUp() {
+        mService = new CpuMonitorService(mContext);
+        mHandlerExecutor = new HandlerExecutor(new Handler(Looper.getMainLooper()));
+        doNothing().when(() -> ServiceManager.addService(eq("cpu_monitor"), any(Binder.class),
+                anyBoolean(), anyInt()));
+        mService.onStart();
+        mLocalService = LocalServices.getService(CpuMonitorInternal.class);
+    }
+
+    @After
+    public void tearDown() {
+        // The CpuMonitorInternal.class service is added by the mService.onStart call.
+        // Remove the service to ensure the setUp procedure can add this service again.
+        LocalServices.removeServiceForTest(CpuMonitorInternal.class);
+    }
+
+    @Test
+    public void testAddRemoveCpuAvailabilityCallback() {
+        CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
+                CpuMonitorInternal.CpuAvailabilityCallback.class);
+
+        mLocalService.addCpuAvailabilityCallback(mHandlerExecutor,
+                TEST_CPU_AVAILABILITY_MONITORING_CONFIG, mockCallback);
+
+        // TODO(b/242722241): Verify that {@link mockCallback.onAvailabilityChanged} and
+        //  {@link mockCallback.onMonitoringIntervalChanged} are called when the callback is added.
+
+        mLocalService.removeCpuAvailabilityCallback(mockCallback);
+    }
+
+
+    @Test
+    public void testDuplicateAddCpuAvailabilityCallback() {
+        CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
+                CpuMonitorInternal.CpuAvailabilityCallback.class);
+
+        mLocalService.addCpuAvailabilityCallback(mHandlerExecutor,
+                TEST_CPU_AVAILABILITY_MONITORING_CONFIG, mockCallback);
+
+        mLocalService.addCpuAvailabilityCallback(mHandlerExecutor,
+                TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2, mockCallback);
+
+        // TODO(b/242722241): Verify that {@link mockCallback} is called only when CPU availability
+        //  thresholds cross the bounds specified in the
+        //  {@link TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2} config.
+
+        mLocalService.removeCpuAvailabilityCallback(mockCallback);
+    }
+
+    @Test
+    public void testRemoveInvalidCpuAvailabilityCallback() {
+        CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
+                CpuMonitorInternal.CpuAvailabilityCallback.class);
+
+        mLocalService.removeCpuAvailabilityCallback(mockCallback);
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index d41ac70..395e6ac 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -161,6 +161,10 @@
         when(mMockedResources.getIntArray(
                 com.android.internal.R.array.config_autoBrightnessLevels))
                 .thenReturn(new int[]{});
+        when(mMockedResources.obtainTypedArray(R.array.config_displayShapeArray))
+                .thenReturn(mockArray);
+        when(mMockedResources.obtainTypedArray(R.array.config_builtInDisplayIsRoundArray))
+                .thenReturn(mockArray);
     }
 
     @After
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/AsyncUserVisibilityListener.java b/services/tests/mockingservicestests/src/com/android/server/pm/AsyncUserVisibilityListener.java
index a97491d..ddfbf16 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/AsyncUserVisibilityListener.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/AsyncUserVisibilityListener.java
@@ -30,7 +30,6 @@
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
-
 /**
  * {@link UserVisibilityListener} implementation that expects callback events to be asynchronously
  * received.
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
index 5c3d695..01674bb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.timeout;
@@ -107,12 +108,16 @@
     @Mock
     private BackgroundDexOptJobService mJobServiceForIdle;
 
-    private final JobParameters mJobParametersForPostBoot = new JobParameters(null,
-            BackgroundDexOptService.JOB_POST_BOOT_UPDATE, null, null, null,
-            0, false, false, null, null, null);
-    private final JobParameters mJobParametersForIdle = new JobParameters(null,
-            BackgroundDexOptService.JOB_IDLE_OPTIMIZE, null, null, null,
-            0, false, false, null, null, null);
+    private final JobParameters mJobParametersForPostBoot =
+            createJobParameters(BackgroundDexOptService.JOB_POST_BOOT_UPDATE);
+    private final JobParameters mJobParametersForIdle =
+            createJobParameters(BackgroundDexOptService.JOB_IDLE_OPTIMIZE);
+
+    private static JobParameters createJobParameters(int jobId) {
+        JobParameters params = mock(JobParameters.class);
+        when(params.getJobId()).thenReturn(jobId);
+        return params;
+    }
 
     private BackgroundDexOptService mService;
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index dd6c733..27d0662 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -139,7 +139,7 @@
                 .strictness(Strictness.LENIENT)
                 .mockStatic(SystemProperties::class.java)
                 .mockStatic(SystemConfig::class.java)
-                .mockStatic(SELinuxMMAC::class.java)
+                .mockStatic(SELinuxMMAC::class.java, Mockito.CALLS_REAL_METHODS)
                 .mockStatic(FallbackCategoryProvider::class.java)
                 .mockStatic(PackageManagerServiceUtils::class.java)
                 .mockStatic(Environment::class.java)
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
index c203831..4487d13 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
@@ -329,6 +329,15 @@
         listener.verify();
     }
 
+    @Test
+    public final void testOnSystemUserVisibilityChanged() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(USER_SYSTEM));
+
+        mMediator.onSystemUserVisibilityChanged(/* visible= */ true);
+
+        listener.verify();
+    }
+
     /**
      * Starts a user in foreground on the default display, asserting it was properly started.
      *
diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java
index ae25c1b..cb59d37 100644
--- a/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java
@@ -44,7 +44,7 @@
 
     private MockitoSession mSession;
 
-    private final Exception mException = new Exception("D'OH!");
+    private final Throwable mThrowable = new Throwable("D'OH!");
 
     @Before
     public void setup() {
@@ -78,10 +78,10 @@
     }
 
     @Test
-    public void testV_msgAndException() {
-        Slogf.v(TAG, "msg", mException);
+    public void testV_msgAndThrowable() {
+        Slogf.v(TAG, "msg", mThrowable);
 
-        verify(()-> Slog.v(TAG, "msg", mException));
+        verify(()-> Slog.v(TAG, "msg", mThrowable));
     }
 
     @Test
@@ -103,12 +103,12 @@
     }
 
     @Test
-    public void testV_msgFormattedWithException_enabled() {
+    public void testV_msgFormattedWithThrowable_enabled() {
         enableLogging(Log.VERBOSE);
 
-        Slogf.v(TAG, mException, "msg in a %s", "bottle");
+        Slogf.v(TAG, mThrowable, "msg in a %s", "bottle");
 
-        verify(()-> Slog.v(TAG, "msg in a bottle", mException));
+        verify(()-> Slog.v(TAG, "msg in a bottle", mThrowable));
     }
 
     @Test
@@ -128,10 +128,10 @@
     }
 
     @Test
-    public void testD_msgAndException() {
-        Slogf.d(TAG, "msg", mException);
+    public void testD_msgAndThrowable() {
+        Slogf.d(TAG, "msg", mThrowable);
 
-        verify(()-> Slog.d(TAG, "msg", mException));
+        verify(()-> Slog.d(TAG, "msg", mThrowable));
     }
 
     @Test
@@ -153,19 +153,19 @@
     }
 
     @Test
-    public void testD_msgFormattedWithException_enabled() {
+    public void testD_msgFormattedWithThrowable_enabled() {
         enableLogging(Log.DEBUG);
 
-        Slogf.d(TAG, mException, "msg in a %s", "bottle");
+        Slogf.d(TAG, mThrowable, "msg in a %s", "bottle");
 
-        verify(()-> Slog.d(TAG, "msg in a bottle", mException));
+        verify(()-> Slog.d(TAG, "msg in a bottle", mThrowable));
     }
 
     @Test
     public void testD_msgFormattedWithException_disabled() {
         disableLogging(Log.DEBUG);
 
-        Slogf.d(TAG, mException, "msg in a %s", "bottle");
+        Slogf.d(TAG, mThrowable, "msg in a %s", "bottle");
 
         verify(()-> Slog.d(eq(TAG), any(String.class), any(Throwable.class)), never());
     }
@@ -178,10 +178,10 @@
     }
 
     @Test
-    public void testI_msgAndException() {
-        Slogf.i(TAG, "msg", mException);
+    public void testI_msgAndThrowable() {
+        Slogf.i(TAG, "msg", mThrowable);
 
-        verify(()-> Slog.i(TAG, "msg", mException));
+        verify(()-> Slog.i(TAG, "msg", mThrowable));
     }
 
     @Test
@@ -203,19 +203,19 @@
     }
 
     @Test
-    public void testI_msgFormattedWithException_enabled() {
+    public void testI_msgFormattedWithThrowable_enabled() {
         enableLogging(Log.INFO);
 
-        Slogf.i(TAG, mException, "msg in a %s", "bottle");
+        Slogf.i(TAG, mThrowable, "msg in a %s", "bottle");
 
-        verify(()-> Slog.i(TAG, "msg in a bottle", mException));
+        verify(()-> Slog.i(TAG, "msg in a bottle", mThrowable));
     }
 
     @Test
     public void testI_msgFormattedWithException_disabled() {
         disableLogging(Log.INFO);
 
-        Slogf.i(TAG, mException, "msg in a %s", "bottle");
+        Slogf.i(TAG, mThrowable, "msg in a %s", "bottle");
 
         verify(()-> Slog.i(eq(TAG), any(String.class), any(Throwable.class)), never());
     }
@@ -228,17 +228,17 @@
     }
 
     @Test
-    public void testW_msgAndException() {
-        Slogf.w(TAG, "msg", mException);
+    public void testW_msgAndThrowable() {
+        Slogf.w(TAG, "msg", mThrowable);
 
-        verify(()-> Slog.w(TAG, "msg", mException));
+        verify(()-> Slog.w(TAG, "msg", mThrowable));
     }
 
     @Test
-    public void testW_exception() {
-        Slogf.w(TAG, mException);
+    public void testW_Throwable() {
+        Slogf.w(TAG, mThrowable);
 
-        verify(()-> Slog.w(TAG, mException));
+        verify(()-> Slog.w(TAG, mThrowable));
     }
 
     @Test
@@ -260,19 +260,19 @@
     }
 
     @Test
-    public void testW_msgFormattedWithException_enabled() {
+    public void testW_msgFormattedWithThrowable_enabled() {
         enableLogging(Log.WARN);
 
-        Slogf.w(TAG, mException, "msg in a %s", "bottle");
+        Slogf.w(TAG, mThrowable, "msg in a %s", "bottle");
 
-        verify(()-> Slog.w(TAG, "msg in a bottle", mException));
+        verify(()-> Slog.w(TAG, "msg in a bottle", mThrowable));
     }
 
     @Test
     public void testW_msgFormattedWithException_disabled() {
         disableLogging(Log.WARN);
 
-        Slogf.w(TAG, mException, "msg in a %s", "bottle");
+        Slogf.w(TAG, mThrowable, "msg in a %s", "bottle");
 
         verify(()-> Slog.w(eq(TAG), any(String.class), any(Throwable.class)), never());
     }
@@ -285,10 +285,10 @@
     }
 
     @Test
-    public void testE_msgAndException() {
-        Slogf.e(TAG, "msg", mException);
+    public void testE_msgAndThrowable() {
+        Slogf.e(TAG, "msg", mThrowable);
 
-        verify(()-> Slog.e(TAG, "msg", mException));
+        verify(()-> Slog.e(TAG, "msg", mThrowable));
     }
 
     @Test
@@ -310,19 +310,19 @@
     }
 
     @Test
-    public void testE_msgFormattedWithException_enabled() {
+    public void testE_msgFormattedWithThrowable_enabled() {
         enableLogging(Log.ERROR);
 
-        Slogf.e(TAG, mException, "msg in a %s", "bottle");
+        Slogf.e(TAG, mThrowable, "msg in a %s", "bottle");
 
-        verify(()-> Slog.e(TAG, "msg in a bottle", mException));
+        verify(()-> Slog.e(TAG, "msg in a bottle", mThrowable));
     }
 
     @Test
     public void testE_msgFormattedWithException_disabled() {
         disableLogging(Log.ERROR);
 
-        Slogf.e(TAG, mException, "msg in a %s", "bottle");
+        Slogf.e(TAG, mThrowable, "msg in a %s", "bottle");
 
         verify(()-> Slog.e(eq(TAG), any(String.class), any(Throwable.class)), never());
     }
@@ -335,17 +335,17 @@
     }
 
     @Test
-    public void testWtf_msgAndException() {
-        Slogf.wtf(TAG, "msg", mException);
+    public void testWtf_msgAndThrowable() {
+        Slogf.wtf(TAG, "msg", mThrowable);
 
-        verify(()-> Slog.wtf(TAG, "msg", mException));
+        verify(()-> Slog.wtf(TAG, "msg", mThrowable));
     }
 
     @Test
-    public void testWtf_exception() {
-        Slogf.wtf(TAG, mException);
+    public void testWtf_Throwable() {
+        Slogf.wtf(TAG, mThrowable);
 
-        verify(()-> Slog.wtf(TAG, mException));
+        verify(()-> Slog.wtf(TAG, mThrowable));
     }
 
     @Test
@@ -377,10 +377,10 @@
     }
 
     @Test
-    public void testWtf_msgFormattedWithException() {
-        Slogf.wtf(TAG, mException, "msg in a %s", "bottle");
+    public void testWtf_msgFormattedWithThrowable() {
+        Slogf.wtf(TAG, mThrowable, "msg in a %s", "bottle");
 
-        verify(()-> Slog.wtf(TAG, "msg in a bottle", mException));
+        verify(()-> Slog.wtf(TAG, "msg in a bottle", mThrowable));
     }
 
     private void enableLogging(@Log.Level int level) {
diff --git a/services/tests/servicestests/src/com/android/server/DockObserverTest.java b/services/tests/servicestests/src/com/android/server/DockObserverTest.java
index c325778..ee09074 100644
--- a/services/tests/servicestests/src/com/android/server/DockObserverTest.java
+++ b/services/tests/servicestests/src/com/android/server/DockObserverTest.java
@@ -20,6 +20,7 @@
 
 import android.content.Intent;
 import android.os.Looper;
+import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableContext;
 import android.testing.TestableLooper;
@@ -74,6 +75,11 @@
                 .isEqualTo(Intent.EXTRA_DOCK_STATE_UNDOCKED);
     }
 
+    void setDeviceProvisioned(boolean provisioned) {
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED,
+                provisioned ? 1 : 0);
+    }
+
     @Before
     public void setUp() {
         if (Looper.myLooper() == null) {
@@ -131,4 +137,25 @@
         assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY5=5",
                 Intent.EXTRA_DOCK_STATE_HE_DESK);
     }
+
+    @Test
+    public void testDockIntentBroadcast_deviceNotProvisioned()
+            throws ExecutionException, InterruptedException {
+        DockObserver observer = new DockObserver(mInterceptingContext);
+        // Set the device as not provisioned.
+        setDeviceProvisioned(false);
+        observer.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
+
+        BroadcastInterceptingContext.FutureIntent futureIntent =
+                updateExtconDockState(observer, "DOCK=1");
+        TestableLooper.get(this).processAllMessages();
+        // Verify no broadcast was sent as device was not provisioned.
+        futureIntent.assertNotReceived();
+
+        // Ensure we send the broadcast when the device is provisioned.
+        setDeviceProvisioned(true);
+        TestableLooper.get(this).processAllMessages();
+        assertThat(futureIntent.get().getIntExtra(Intent.EXTRA_DOCK_STATE, -1))
+                .isEqualTo(Intent.EXTRA_DOCK_STATE_DESK);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index a49214f..e8b8253 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -38,9 +38,6 @@
 import static com.android.server.am.UserController.USER_CURRENT_MSG;
 import static com.android.server.am.UserController.USER_START_MSG;
 import static com.android.server.am.UserController.USER_SWITCH_TIMEOUT_MSG;
-import static com.android.server.am.UserController.USER_VISIBILITY_CHANGED_MSG;
-import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
-import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
 
 import static com.google.android.collect.Lists.newArrayList;
 import static com.google.android.collect.Sets.newHashSet;
@@ -102,7 +99,6 @@
 import com.android.server.SystemService;
 import com.android.server.am.UserState.KeyEvictedCallback;
 import com.android.server.pm.UserManagerInternal;
-import com.android.server.pm.UserManagerInternal.UserAssignmentResult;
 import com.android.server.pm.UserManagerService;
 import com.android.server.wm.WindowManagerService;
 
@@ -162,18 +158,12 @@
             REPORT_USER_SWITCH_MSG,
             USER_SWITCH_TIMEOUT_MSG,
             USER_START_MSG,
-            USER_VISIBILITY_CHANGED_MSG,
             USER_CURRENT_MSG);
 
-    private static final Set<Integer> START_INVISIBLE_BACKGROUND_USER_MESSAGE_CODES = newHashSet(
+    private static final Set<Integer> START_BACKGROUND_USER_MESSAGE_CODES = newHashSet(
             USER_START_MSG,
             REPORT_LOCKED_BOOT_COMPLETE_MSG);
 
-    private static final Set<Integer> START_VISIBLE_BACKGROUND_USER_MESSAGE_CODES = newHashSet(
-            USER_START_MSG,
-            USER_VISIBILITY_CHANGED_MSG,
-            REPORT_LOCKED_BOOT_COMPLETE_MSG);
-
     @Before
     public void setUp() throws Exception {
         runWithDexmakerShareClassLoader(() -> {
@@ -225,14 +215,12 @@
 
     @Test
     public void testStartUser_background() {
-        mockAssignUserToMainDisplay(TEST_USER_ID, /* foreground= */ false,
-                USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
         boolean started = mUserController.startUser(TEST_USER_ID, /* foreground= */ false);
         assertWithMessage("startUser(%s, foreground=false)", TEST_USER_ID).that(started).isTrue();
         verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
         verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
         verify(mInjector, never()).clearAllLockedTasks(anyString());
-        startBackgroundUserAssertions(/*visible= */ false);
+        startBackgroundUserAssertions();
         verifyUserAssignedToDisplay(TEST_USER_ID, Display.DEFAULT_DISPLAY);
     }
 
@@ -267,7 +255,7 @@
         verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
         verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
         verify(mInjector, never()).clearAllLockedTasks(anyString());
-        startBackgroundUserAssertions(/*visible= */ true);
+        startBackgroundUserAssertions();
     }
 
     @Test
@@ -293,8 +281,6 @@
 
     @Test
     public void testStartPreCreatedUser_background() throws Exception {
-        mockAssignUserToMainDisplay(TEST_PRE_CREATED_USER_ID, /* foreground= */ false,
-                USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
         assertTrue(mUserController.startUser(TEST_PRE_CREATED_USER_ID, /* foreground= */ false));
         // Make sure no intents have been fired for pre-created users.
         assertTrue(mInjector.mSentIntents.isEmpty());
@@ -322,10 +308,8 @@
         assertEquals("Unexpected message sent", expectedMessageCodes, actualCodes);
     }
 
-    private void startBackgroundUserAssertions(boolean visible) {
-        startUserAssertions(START_BACKGROUND_USER_ACTIONS,
-                visible ? START_VISIBLE_BACKGROUND_USER_MESSAGE_CODES
-                        : START_INVISIBLE_BACKGROUND_USER_MESSAGE_CODES);
+    private void startBackgroundUserAssertions() {
+        startUserAssertions(START_BACKGROUND_USER_ACTIONS, START_BACKGROUND_USER_MESSAGE_CODES);
     }
 
     private void startForegroundUserAssertions() {
@@ -433,7 +417,7 @@
         verify(mInjector, times(0)).dismissKeyguard(any(), anyString());
         verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
         continueUserSwitchAssertions(TEST_USER_ID, false);
-        verifySystemUserVisibilityChangedNotified(/* visible= */ false);
+        verifySystemUserVisibilityChangesNeverNotified();
     }
 
     @Test
@@ -454,7 +438,7 @@
         verify(mInjector, times(1)).dismissKeyguard(any(), anyString());
         verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
         continueUserSwitchAssertions(TEST_USER_ID, false);
-        verifySystemUserVisibilityChangedNotified(/* visible= */ false);
+        verifySystemUserVisibilityChangesNeverNotified();
     }
 
     @Test
@@ -561,7 +545,7 @@
         assertFalse(mUserController.canStartMoreUsers());
         assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID1, TEST_USER_ID2}),
                 mUserController.getRunningUsersLU());
-        verifySystemUserVisibilityChangedNotified(/* visible= */ false);
+        verifySystemUserVisibilityChangesNeverNotified();
     }
 
     /**
@@ -709,24 +693,19 @@
 
     @Test
     public void testStartProfile() throws Exception {
-        mockAssignUserToMainDisplay(TEST_PRE_CREATED_USER_ID, /* foreground= */ false,
-                USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
         setUpAndStartProfileInBackground(TEST_USER_ID1);
 
-        startBackgroundUserAssertions(/*visible= */ true);
+        startBackgroundUserAssertions();
         verifyUserAssignedToDisplay(TEST_USER_ID1, Display.DEFAULT_DISPLAY);
     }
 
     @Test
     public void testStartProfile_whenUsersOnSecondaryDisplaysIsEnabled() throws Exception {
-        mockAssignUserToMainDisplay(TEST_USER_ID1, /* foreground= */ false,
-                USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
-
         mockIsUsersOnSecondaryDisplaysEnabled(true);
 
         setUpAndStartProfileInBackground(TEST_USER_ID1);
 
-        startBackgroundUserAssertions(/*visible= */ true);
+        startBackgroundUserAssertions();
         verifyUserAssignedToDisplay(TEST_USER_ID1, Display.DEFAULT_DISPLAY);
     }
 
@@ -983,13 +962,6 @@
         when(mInjector.isUsersOnSecondaryDisplaysEnabled()).thenReturn(value);
     }
 
-    private void mockAssignUserToMainDisplay(@UserIdInt int userId, boolean foreground,
-            @UserAssignmentResult int result) {
-        when(mInjector.mUserManagerInternalMock.assignUserToDisplayOnStart(eq(userId),
-                /* profileGroupId= */ anyInt(), eq(foreground), eq(Display.DEFAULT_DISPLAY)))
-                        .thenReturn(result);
-    }
-
     private void verifyUserAssignedToDisplay(@UserIdInt int userId, int displayId) {
         verify(mInjector.getUserManagerInternal()).assignUserToDisplayOnStart(eq(userId), anyInt(),
                 anyBoolean(), eq(displayId));
@@ -1008,8 +980,8 @@
         verify(mInjector.getUserManagerInternal(), never()).unassignUserFromDisplayOnStop(userId);
     }
 
-    private void verifySystemUserVisibilityChangedNotified(boolean visible) {
-        verify(mInjector).onUserVisibilityChanged(UserHandle.USER_SYSTEM, visible);
+    private void verifySystemUserVisibilityChangesNeverNotified() {
+        verify(mInjector, never()).onSystemUserVisibilityChanged(anyBoolean());
     }
 
     // Should be public to allow mocking
@@ -1154,8 +1126,8 @@
         }
 
         @Override
-        void onUserVisibilityChanged(@UserIdInt int userId, boolean visible) {
-            Log.i(TAG, "onUserVisibilityChanged(" + userId + ", " + visible + ")");
+        void onSystemUserVisibilityChanged(boolean visible) {
+            Log.i(TAG, "onSystemUserVisibilityChanged(" + visible + ")");
         }
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
new file mode 100644
index 0000000..ef8a49f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2022 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.companion.virtual;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.companion.virtual.sensor.VirtualSensorConfig;
+import android.companion.virtual.sensor.VirtualSensorEvent;
+import android.hardware.Sensor;
+import android.os.Binder;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.server.LocalServices;
+import com.android.server.sensors.SensorManagerInternal;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@Presubmit
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class SensorControllerTest {
+
+    private static final int VIRTUAL_DEVICE_ID = 42;
+    private static final String VIRTUAL_SENSOR_NAME = "VirtualAccelerometer";
+    private static final int SENSOR_HANDLE = 7;
+
+    @Mock
+    private SensorManagerInternal mSensorManagerInternalMock;
+    private SensorController mSensorController;
+    private VirtualSensorEvent mSensorEvent;
+    private VirtualSensorConfig mVirtualSensorConfig;
+    private IBinder mSensorToken;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        LocalServices.removeServiceForTest(SensorManagerInternal.class);
+        LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock);
+
+        mSensorController = new SensorController(new Object(), VIRTUAL_DEVICE_ID);
+        mSensorEvent = new VirtualSensorEvent.Builder(new float[] { 1f, 2f, 3f}).build();
+        mVirtualSensorConfig =
+                new VirtualSensorConfig.Builder(Sensor.TYPE_ACCELEROMETER, VIRTUAL_SENSOR_NAME)
+                        .build();
+        mSensorToken = new Binder("sensorToken");
+    }
+
+    @Test
+    public void createSensor_invalidHandle_throwsException() {
+        doReturn(/* handle= */0).when(mSensorManagerInternalMock).createRuntimeSensor(
+                anyInt(), anyInt(), anyString(), anyString(), any());
+
+        Throwable thrown = assertThrows(
+                RuntimeException.class,
+                () -> mSensorController.createSensor(mSensorToken, mVirtualSensorConfig));
+
+        assertThat(thrown.getCause().getMessage())
+                .contains("Received an invalid virtual sensor handle");
+    }
+
+    @Test
+    public void createSensor_success() {
+        doCreateSensorSuccessfully();
+
+        assertThat(mSensorController.getSensorDescriptors()).isNotEmpty();
+    }
+
+    @Test
+    public void sendSensorEvent_invalidToken_throwsException() {
+        doCreateSensorSuccessfully();
+
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mSensorController.sendSensorEvent(
+                        new Binder("invalidSensorToken"), mSensorEvent));
+    }
+
+    @Test
+    public void sendSensorEvent_success() {
+        doCreateSensorSuccessfully();
+
+        mSensorController.sendSensorEvent(mSensorToken, mSensorEvent);
+        verify(mSensorManagerInternalMock).sendSensorEvent(
+                SENSOR_HANDLE, Sensor.TYPE_ACCELEROMETER, mSensorEvent.getTimestampNanos(),
+                mSensorEvent.getValues());
+    }
+
+    @Test
+    public void unregisterSensor_invalidToken_throwsException() {
+        doCreateSensorSuccessfully();
+
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mSensorController.unregisterSensor(new Binder("invalidSensorToken")));
+    }
+
+    @Test
+    public void unregisterSensor_success() {
+        doCreateSensorSuccessfully();
+
+        mSensorController.unregisterSensor(mSensorToken);
+        verify(mSensorManagerInternalMock).removeRuntimeSensor(SENSOR_HANDLE);
+        assertThat(mSensorController.getSensorDescriptors()).isEmpty();
+    }
+
+    private void doCreateSensorSuccessfully() {
+        doReturn(SENSOR_HANDLE).when(mSensorManagerInternalMock).createRuntimeSensor(
+                anyInt(), anyInt(), anyString(), anyString(), any());
+        mSensorController.createSensor(mSensorToken, mVirtualSensorConfig);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 0bd6f2c..3e8a070 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -51,6 +51,7 @@
 import android.companion.virtual.VirtualDeviceParams;
 import android.companion.virtual.audio.IAudioConfigChangedCallback;
 import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.companion.virtual.sensor.VirtualSensorConfig;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.ContextWrapper;
@@ -58,6 +59,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.graphics.Point;
+import android.hardware.Sensor;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.IInputManager;
 import android.hardware.input.VirtualKeyEvent;
@@ -88,6 +90,7 @@
 import com.android.internal.app.BlockedAppStreamingActivity;
 import com.android.server.LocalServices;
 import com.android.server.input.InputManagerInternal;
+import com.android.server.sensors.SensorManagerInternal;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -126,16 +129,19 @@
     private static final int VENDOR_ID = 5;
     private static final String UNIQUE_ID = "uniqueid";
     private static final String PHYS = "phys";
-    private static final int DEVICE_ID = 42;
+    private static final int DEVICE_ID = 53;
     private static final int HEIGHT = 1800;
     private static final int WIDTH = 900;
+    private static final int SENSOR_HANDLE = 64;
     private static final Binder BINDER = new Binder("binder");
     private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000;
+    private static final int VIRTUAL_DEVICE_ID =  42;
 
     private Context mContext;
     private InputManagerMockHelper mInputManagerMockHelper;
     private VirtualDeviceImpl mDeviceImpl;
     private InputController mInputController;
+    private SensorController mSensorController;
     private AssociationInfo mAssociationInfo;
     private VirtualDeviceManagerService mVdms;
     private VirtualDeviceManagerInternal mLocalService;
@@ -150,6 +156,8 @@
     @Mock
     private InputManagerInternal mInputManagerInternalMock;
     @Mock
+    private SensorManagerInternal mSensorManagerInternalMock;
+    @Mock
     private IVirtualDeviceActivityListener mActivityListener;
     @Mock
     private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
@@ -228,6 +236,9 @@
         LocalServices.removeServiceForTest(InputManagerInternal.class);
         LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
 
+        LocalServices.removeServiceForTest(SensorManagerInternal.class);
+        LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock);
+
         final DisplayInfo displayInfo = new DisplayInfo();
         displayInfo.uniqueId = UNIQUE_ID;
         doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt());
@@ -252,6 +263,7 @@
         mInputController = new InputController(new Object(), mNativeWrapperMock,
                 new Handler(TestableLooper.get(this).getLooper()),
                 mContext.getSystemService(WindowManager.class), threadVerifier);
+        mSensorController = new SensorController(new Object(), VIRTUAL_DEVICE_ID);
 
         mAssociationInfo = new AssociationInfo(1, 0, null,
                 MacAddress.BROADCAST_ADDRESS, "", null, null, true, false, false, 0, 0);
@@ -264,9 +276,9 @@
                 .setBlockedActivities(getBlockedActivities())
                 .build();
         mDeviceImpl = new VirtualDeviceImpl(mContext,
-                mAssociationInfo, new Binder(), /* ownerUid */ 0, /* uniqueId */ 1,
-                mInputController, (int associationId) -> {}, mPendingTrampolineCallback,
-                mActivityListener, mRunningAppsChangedCallback, params);
+                mAssociationInfo, new Binder(), /* ownerUid */ 0, VIRTUAL_DEVICE_ID,
+                mInputController, mSensorController, (int associationId) -> {},
+                mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
         mVdms.addVirtualDevice(mDeviceImpl);
     }
 
@@ -305,12 +317,12 @@
         VirtualDeviceParams params = new VirtualDeviceParams
                 .Builder()
                 .setBlockedActivities(getBlockedActivities())
-                .addDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
+                .setDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
                 .build();
         mDeviceImpl = new VirtualDeviceImpl(mContext,
-                mAssociationInfo, new Binder(), /* ownerUid */ 0, /* uniqueId */ 1,
-                mInputController, (int associationId) -> {}, mPendingTrampolineCallback,
-                mActivityListener, mRunningAppsChangedCallback, params);
+                mAssociationInfo, new Binder(), /* ownerUid */ 0, VIRTUAL_DEVICE_ID,
+                mInputController, mSensorController, (int associationId) -> {},
+                mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
         mVdms.addVirtualDevice(mDeviceImpl);
 
         assertThat(
@@ -576,6 +588,18 @@
     }
 
     @Test
+    public void createVirtualSensor_noPermission_failsSecurityException() {
+        doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
+                eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
+        assertThrows(
+                SecurityException.class,
+                () -> mDeviceImpl.createVirtualSensor(
+                        BINDER,
+                        new VirtualSensorConfig.Builder(
+                                Sensor.TYPE_ACCELEROMETER, DEVICE_NAME).build()));
+    }
+
+    @Test
     public void onAudioSessionStarting_noPermission_failsSecurityException() {
         mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
         doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
@@ -679,6 +703,17 @@
     }
 
     @Test
+    public void close_cleanSensorController() {
+        mSensorController.addSensorForTesting(
+                BINDER, SENSOR_HANDLE, Sensor.TYPE_ACCELEROMETER, DEVICE_NAME);
+
+        mDeviceImpl.close();
+
+        assertThat(mSensorController.getSensorDescriptors()).isEmpty();
+        verify(mSensorManagerInternalMock).removeRuntimeSensor(SENSOR_HANDLE);
+    }
+
+    @Test
     public void sendKeyEvent_noFd() {
         assertThrows(
                 IllegalArgumentException.class,
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
index 036b6df..aefe4b6 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
@@ -16,9 +16,14 @@
 
 package com.android.server.companion.virtual;
 
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
+import static android.hardware.Sensor.TYPE_ACCELEROMETER;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import android.companion.virtual.VirtualDeviceParams;
+import android.companion.virtual.sensor.VirtualSensorConfig;
 import android.os.Parcel;
 import android.os.UserHandle;
 
@@ -27,18 +32,25 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.List;
 import java.util.Set;
 
 @RunWith(AndroidJUnit4.class)
 public class VirtualDeviceParamsTest {
 
+    private static final String SENSOR_NAME = "VirtualSensorName";
+    private static final String SENSOR_VENDOR = "VirtualSensorVendor";
+
     @Test
     public void parcelable_shouldRecreateSuccessfully() {
         VirtualDeviceParams originalParams = new VirtualDeviceParams.Builder()
                 .setLockState(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED)
                 .setUsersWithMatchingAccounts(Set.of(UserHandle.of(123), UserHandle.of(456)))
-                .addDevicePolicy(VirtualDeviceParams.POLICY_TYPE_SENSORS,
-                        VirtualDeviceParams.DEVICE_POLICY_CUSTOM)
+                .setDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
+                .addVirtualSensorConfig(
+                        new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
+                                .setVendor(SENSOR_VENDOR)
+                                .build())
                 .build();
         Parcel parcel = Parcel.obtain();
         originalParams.writeToParcel(parcel, 0);
@@ -49,7 +61,14 @@
         assertThat(params.getLockState()).isEqualTo(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED);
         assertThat(params.getUsersWithMatchingAccounts())
                 .containsExactly(UserHandle.of(123), UserHandle.of(456));
-        assertThat(params.getDevicePolicy(VirtualDeviceParams.POLICY_TYPE_SENSORS))
-                .isEqualTo(VirtualDeviceParams.DEVICE_POLICY_CUSTOM);
+        assertThat(params.getDevicePolicy(POLICY_TYPE_SENSORS)).isEqualTo(DEVICE_POLICY_CUSTOM);
+
+        List<VirtualSensorConfig> sensorConfigs = params.getVirtualSensorConfigs();
+        assertThat(sensorConfigs).hasSize(1);
+        VirtualSensorConfig sensorConfig = sensorConfigs.get(0);
+        assertThat(sensorConfig.getType()).isEqualTo(TYPE_ACCELEROMETER);
+        assertThat(sensorConfig.getName()).isEqualTo(SENSOR_NAME);
+        assertThat(sensorConfig.getVendor()).isEqualTo(SENSOR_VENDOR);
+        assertThat(sensorConfig.getStateChangeCallback()).isNull();
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 062bde8..ce35626 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -171,22 +171,6 @@
 
     private final DisplayManagerService.Injector mBasicInjector = new BasicInjector();
 
-    private final DisplayManagerService.Injector mAllowNonNativeRefreshRateOverrideInjector =
-            new BasicInjector() {
-                @Override
-                boolean getAllowNonNativeRefreshRateOverride() {
-                    return true;
-                }
-            };
-
-    private final DisplayManagerService.Injector mDenyNonNativeRefreshRateOverrideInjector =
-            new BasicInjector() {
-                @Override
-                boolean getAllowNonNativeRefreshRateOverride() {
-                    return false;
-                }
-            };
-
     @Mock InputManagerInternal mMockInputManagerInternal;
     @Mock VirtualDeviceManagerInternal mMockVirtualDeviceManagerInternal;
     @Mock IVirtualDisplayCallback.Stub mMockAppToken;
@@ -408,6 +392,75 @@
         assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0);
     }
 
+    @Test
+    public void testCreateVirtualDisplayOwnFocus() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+
+        // This is effectively the DisplayManager service published to ServiceManager.
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+
+        String uniqueId = "uniqueId --- Own Focus Test";
+        int width = 600;
+        int height = 800;
+        int dpi = 320;
+        int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS
+                | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
+
+        when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn(
+                PackageManager.PERMISSION_GRANTED);
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi);
+        builder.setFlags(flags);
+        builder.setUniqueId(uniqueId);
+        int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken,
+                /* projection= */ null, PACKAGE_NAME);
+
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+        // flush the handler
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
+
+        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
+        assertNotNull(ddi);
+        assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) != 0);
+    }
+
+    @Test
+    public void testCreateVirtualDisplayOwnFocus_nonTrustedDisplay() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+
+        // This is effectively the DisplayManager service published to ServiceManager.
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+
+        String uniqueId = "uniqueId --- Own Focus Test -- nonTrustedDisplay";
+        int width = 600;
+        int height = 800;
+        int dpi = 320;
+        int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
+
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi);
+        builder.setFlags(flags);
+        builder.setUniqueId(uniqueId);
+        int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken,
+                /* projection= */ null, PACKAGE_NAME);
+
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+        // flush the handler
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
+
+        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
+        assertNotNull(ddi);
+        assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) == 0);
+    }
+
     /**
      * Tests that the virtual display is created along-side the default display.
      */
@@ -1044,13 +1097,32 @@
     }
 
     /**
-     * Tests that the frame rate override is updated accordingly to the
-     * allowNonNativeRefreshRateOverride policy.
+     * Tests that the frame rate override is returning the correct value from
+     * DisplayInfo#getRefreshRate
      */
     @Test
     public void testDisplayInfoNonNativeFrameRateOverride() throws Exception {
-        testDisplayInfoNonNativeFrameRateOverride(mDenyNonNativeRefreshRateOverrideInjector);
-        testDisplayInfoNonNativeFrameRateOverride(mAllowNonNativeRefreshRateOverrideInjector);
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{60f});
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+        updateFrameRateOverride(displayManager, displayDevice,
+                new DisplayEventReceiver.FrameRateOverride[]{
+                        new DisplayEventReceiver.FrameRateOverride(
+                                Process.myUid(), 20f)
+                });
+        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
     }
 
     /**
@@ -1078,10 +1150,7 @@
     @Test
     @DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
     public void testDisplayInfoNonNativeFrameRateOverrideModeCompat() throws Exception {
-        testDisplayInfoNonNativeFrameRateOverrideMode(mDenyNonNativeRefreshRateOverrideInjector,
-                /*compatChangeEnabled*/ false);
-        testDisplayInfoNonNativeFrameRateOverrideMode(mAllowNonNativeRefreshRateOverrideInjector,
-                /*compatChangeEnabled*/  false);
+        testDisplayInfoNonNativeFrameRateOverrideMode(/*compatChangeEnabled*/ false);
     }
 
     /**
@@ -1090,10 +1159,7 @@
     @Test
     @EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
     public void testDisplayInfoNonNativeFrameRateOverrideMode() throws Exception {
-        testDisplayInfoNonNativeFrameRateOverrideMode(mDenyNonNativeRefreshRateOverrideInjector,
-                /*compatChangeEnabled*/  true);
-        testDisplayInfoNonNativeFrameRateOverrideMode(mAllowNonNativeRefreshRateOverrideInjector,
-                /*compatChangeEnabled*/  true);
+        testDisplayInfoNonNativeFrameRateOverrideMode(/*compatChangeEnabled*/ true);
     }
 
     /**
@@ -1316,10 +1382,9 @@
         assertEquals(expectedMode, displayInfo.getMode());
     }
 
-    private void testDisplayInfoNonNativeFrameRateOverrideMode(
-            DisplayManagerService.Injector injector, boolean compatChangeEnabled) {
+    private void testDisplayInfoNonNativeFrameRateOverrideMode(boolean compatChangeEnabled) {
         DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, injector);
+                new DisplayManagerService(mContext, mBasicInjector);
         DisplayManagerService.BinderService displayManagerBinderService =
                 displayManager.new BinderService();
         registerDefaultDisplays(displayManager);
@@ -1341,40 +1406,12 @@
         Display.Mode expectedMode;
         if (compatChangeEnabled) {
             expectedMode = new Display.Mode(1, 100, 200, 60f);
-        } else if (injector.getAllowNonNativeRefreshRateOverride()) {
-            expectedMode = new Display.Mode(255, 100, 200, 20f);
         } else {
-            expectedMode = new Display.Mode(1, 100, 200, 60f);
+            expectedMode = new Display.Mode(255, 100, 200, 20f);
         }
         assertEquals(expectedMode, displayInfo.getMode());
     }
 
-    private void testDisplayInfoNonNativeFrameRateOverride(
-            DisplayManagerService.Injector injector) {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, injector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-        registerDefaultDisplays(displayManager);
-        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-
-        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
-                new float[]{60f});
-        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
-                displayDevice);
-        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
-
-        updateFrameRateOverride(displayManager, displayDevice,
-                new DisplayEventReceiver.FrameRateOverride[]{
-                        new DisplayEventReceiver.FrameRateOverride(
-                                Process.myUid(), 20f)
-                });
-        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        float expectedRefreshRate = injector.getAllowNonNativeRefreshRateOverride() ? 20f : 60f;
-        assertEquals(expectedRefreshRate, displayInfo.getRefreshRate(), 0.01f);
-    }
-
     private int getDisplayIdForDisplayDevice(
             DisplayManagerService displayManager,
             DisplayManagerService.BinderService displayManagerBinderService,
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index c81db92..6258d6d 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -2014,6 +2014,50 @@
     }
 
     @Test
+    public void testMultiDisplay_defaultDozing_addNewDisplayDefaultGoesBackToDoze() {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = nonDefaultDisplayGroupId;
+        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
+
+        doAnswer(inv -> {
+            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+            return null;
+        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+        createService();
+        startSystem();
+
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+
+        forceDozing();
+        advanceTime(500);
+
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_DOZING);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+        verify(mDreamManagerInternalMock).startDream(eq(true), anyString());
+
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+        advanceTime(500);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_DOZING);
+        verify(mDreamManagerInternalMock, times(2)).startDream(eq(true), anyString());
+    }
+
+    @Test
     public void testLastSleepTime_notUpdatedWhenDreaming() {
         createService();
         startSystem();
diff --git a/services/tests/servicestests/src/com/android/server/utils/EventLoggerTest.java b/services/tests/servicestests/src/com/android/server/utils/EventLoggerTest.java
index aafc16d..febbffe 100644
--- a/services/tests/servicestests/src/com/android/server/utils/EventLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/utils/EventLoggerTest.java
@@ -71,9 +71,10 @@
         }
 
         @Test
-        public void testThatPrintWriterProducesEmptyListFromEmptyLog() {
+        public void testThatPrintWriterProducesOnlyTitleFromEmptyLog() {
             mEventLogger.dump(mTestPrintWriter);
-            assertThat(mTestStringWriter.toString()).isEmpty();
+            assertThat(mTestStringWriter.toString())
+                    .isEqualTo(mEventLogger.getDumpTitle() + "\n");
         }
     }
 
@@ -87,27 +88,27 @@
                         // insertion order, max size is 3
                         new EventLogger.Event[] { TEST_EVENT_1, TEST_EVENT_2 },
                         // expected events
-                        new EventLogger.Event[] { TEST_EVENT_2, TEST_EVENT_1 }
+                        new EventLogger.Event[] { TEST_EVENT_1, TEST_EVENT_2 }
                     },
                     {
                         // insertion order, max size is 3
                         new EventLogger.Event[] { TEST_EVENT_1, TEST_EVENT_3, TEST_EVENT_2 },
                         // expected events
-                        new EventLogger.Event[] { TEST_EVENT_2, TEST_EVENT_3, TEST_EVENT_1 }
+                        new EventLogger.Event[] { TEST_EVENT_1, TEST_EVENT_3, TEST_EVENT_2 }
                     },
                     {
                         // insertion order, max size is 3
                         new EventLogger.Event[] { TEST_EVENT_1, TEST_EVENT_2, TEST_EVENT_3,
                             TEST_EVENT_4 },
                         // expected events
-                        new EventLogger.Event[] { TEST_EVENT_4, TEST_EVENT_3, TEST_EVENT_2 }
+                        new EventLogger.Event[] { TEST_EVENT_2, TEST_EVENT_3, TEST_EVENT_4 }
                     },
                     {
                         // insertion order, max size is 3
                         new EventLogger.Event[] { TEST_EVENT_1, TEST_EVENT_2, TEST_EVENT_3,
                             TEST_EVENT_4, TEST_EVENT_5 },
                         // expected events
-                        new EventLogger.Event[] { TEST_EVENT_5, TEST_EVENT_4, TEST_EVENT_3 }
+                        new EventLogger.Event[] { TEST_EVENT_3, TEST_EVENT_4, TEST_EVENT_5 }
                     }
             });
         }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
index af10b9d..d758e71 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
@@ -52,9 +52,9 @@
 @RunWith(AndroidTestingRunner.class)
 public class NotificationHistoryJobServiceTest extends UiServiceTestCase {
     private NotificationHistoryJobService mJobService;
-    private JobParameters mJobParams = new JobParameters(null,
-            NotificationHistoryJobService.BASE_JOB_ID, null, null, null,
-            0, false, false, null, null, null);
+
+    @Mock
+    private JobParameters mJobParams;
 
     @Captor
     ArgumentCaptor<JobInfo> mJobInfoCaptor;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
index 3a6c0eb..a83eb00 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
@@ -44,9 +44,9 @@
 @RunWith(AndroidTestingRunner.class)
 public class ReviewNotificationPermissionsJobServiceTest extends UiServiceTestCase {
     private ReviewNotificationPermissionsJobService mJobService;
-    private JobParameters mJobParams = new JobParameters(null,
-            ReviewNotificationPermissionsJobService.JOB_ID, null, null, null,
-            0, false, false, null, null, null);
+
+    @Mock
+    private JobParameters mJobParams;
 
     @Captor
     ArgumentCaptor<JobInfo> mJobInfoCaptor;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 1ab7d7e..a410eed 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -1206,6 +1206,25 @@
         }
     }
 
+    @Test
+    public void testFinishActivityIfPossible_sendResultImmediatelyIfResumed() {
+        final Task task = new TaskBuilder(mSupervisor).build();
+        final TaskFragment taskFragment1 = createTaskFragmentWithActivity(task);
+        final TaskFragment taskFragment2 = createTaskFragmentWithActivity(task);
+        final ActivityRecord resultToActivity = taskFragment1.getTopMostActivity();
+        final ActivityRecord targetActivity = taskFragment2.getTopMostActivity();
+        resultToActivity.setState(RESUMED, "test");
+        targetActivity.setState(RESUMED, "test");
+        targetActivity.resultTo = resultToActivity;
+
+        clearInvocations(mAtm.getLifecycleManager());
+        targetActivity.finishIfPossible(0, new Intent(), null, "test", false /* oomAdj */);
+        waitUntilHandlersIdle();
+
+        verify(resultToActivity).sendResult(anyInt(), eq(null), anyInt(), anyInt(), any(), eq(null),
+                anyBoolean());
+    }
+
     /**
      * Verify that complete finish request for non-finishing activity is invalid.
      */
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index 1575336..8a15c30 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -28,6 +28,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
@@ -275,11 +276,60 @@
         // THEN calling intercept returns true
         mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null);
 
-        // THEN the returned intent is the quiet mode intent
+        // THEN the returned intent is the confirm credentials intent
         assertTrue(CONFIRM_CREDENTIALS_INTENT.filterEquals(mInterceptor.mIntent));
     }
 
     @Test
+    public void testLockedManagedProfileShowWhenLocked() {
+        Intent originalIntent = new Intent();
+        // GIVEN that the user is locked but its storage is unlocked and the activity has
+        // showWhenLocked flag
+        when(mAmInternal.shouldConfirmCredentials(TEST_USER_ID)).thenReturn(true);
+        when(mUserManager.isUserUnlocked(eq(TEST_USER_ID))).thenReturn(true);
+        mAInfo.flags |= ActivityInfo.FLAG_SHOW_WHEN_LOCKED;
+
+        // THEN calling intercept returns true
+        mInterceptor.intercept(originalIntent, null, mAInfo, null, null, null, 0, 0, null);
+
+        // THEN the returned intent is original intent
+        assertSame(originalIntent, mInterceptor.mIntent);
+    }
+
+    @Test
+    public void testLockedManagedProfileShowWhenLockedEncryptedStorage() {
+        // GIVEN that the user storage is locked, activity has showWhenLocked flag but no
+        // directBootAware flag
+        when(mAmInternal.shouldConfirmCredentials(TEST_USER_ID)).thenReturn(true);
+        when(mUserManager.isUserUnlocked(eq(TEST_USER_ID))).thenReturn(false);
+        mAInfo.flags |= ActivityInfo.FLAG_SHOW_WHEN_LOCKED;
+        mAInfo.directBootAware = false;
+
+        // THEN calling intercept returns true
+        mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null);
+
+        // THEN the returned intent is the confirm credentials intent
+        assertTrue(CONFIRM_CREDENTIALS_INTENT.filterEquals(mInterceptor.mIntent));
+    }
+
+    @Test
+    public void testLockedManagedProfileShowWhenLockedEncryptedStorageDirectBootAware() {
+        Intent originalIntent = new Intent();
+        // GIVEN that the user storage is locked, activity has showWhenLocked flag and
+        // directBootAware flag
+        when(mAmInternal.shouldConfirmCredentials(TEST_USER_ID)).thenReturn(true);
+        when(mUserManager.isUserUnlocked(eq(TEST_USER_ID))).thenReturn(false);
+        mAInfo.flags |= ActivityInfo.FLAG_SHOW_WHEN_LOCKED;
+        mAInfo.directBootAware = true;
+
+        // THEN calling intercept returns true
+        mInterceptor.intercept(originalIntent, null, mAInfo, null, null, null, 0, 0, null);
+
+        // THEN the returned intent is original intent
+        assertSame(originalIntent, mInterceptor.mIntent);
+    }
+
+    @Test
     public void testHarmfulAppWarning() throws RemoteException {
         // GIVEN the package we're about to launch has a harmful app warning set
         when(mPackageManager.getHarmfulAppWarning(TEST_PACKAGE_NAME, TEST_USER_ID))
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 70b68c7..6733470 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -133,8 +133,8 @@
         final RoundedCorners roundedCorners = mHasRoundedCorners
                 ? mDisplayContent.calculateRoundedCornersForRotation(mRotation)
                 : RoundedCorners.NO_ROUNDED_CORNERS;
-        return new DisplayFrames(insetsState, info,
-                info.displayCutout, roundedCorners, new PrivacyIndicatorBounds());
+        return new DisplayFrames(insetsState, info, info.displayCutout, roundedCorners,
+                new PrivacyIndicatorBounds(), info.displayShape);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index d99946f..10f2270 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -58,6 +58,7 @@
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.view.DisplayInfo;
+import android.view.DisplayShape;
 import android.view.InsetsSource;
 import android.view.InsetsState;
 import android.view.PrivacyIndicatorBounds;
@@ -321,7 +322,8 @@
         final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState();
         mImeWindow.mAboveInsetsState.set(state);
         mDisplayContent.mDisplayFrames = new DisplayFrames(
-                state, displayInfo, NO_CUTOUT, NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds());
+                state, displayInfo, NO_CUTOUT, NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds(),
+                DisplayShape.NONE);
 
         mDisplayContent.setInputMethodWindowLocked(mImeWindow);
         mImeWindow.mAttrs.setFitInsetsSides(Side.all() & ~Side.BOTTOM);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 4e796c5..8fda191 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -24,7 +24,6 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.os.Process.FIRST_APPLICATION_UID;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
@@ -33,9 +32,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
-import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
 import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
-import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT;
 import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
@@ -475,23 +472,6 @@
         doReturn(true).when(taskFragment).smallerThanMinDimension(any());
         assertEquals(EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION,
                 taskFragment.isAllowedToEmbedActivity(activity));
-
-        // Not allow to start activity across TaskFragments for result.
-        final TaskFragment newTaskFragment = new TaskFragmentBuilder(mAtm)
-                .setParentTask(taskFragment.getTask())
-                .build();
-        final ActivityRecord newActivity = new ActivityBuilder(mAtm)
-                .setUid(FIRST_APPLICATION_UID)
-                .build();
-        doReturn(true).when(newTaskFragment).isAllowedToEmbedActivityInTrustedMode(any(), anyInt());
-        doReturn(false).when(newTaskFragment).smallerThanMinDimension(any());
-        newActivity.resultTo = activity;
-        assertEquals(EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT,
-                newTaskFragment.isAllowedToEmbedActivity(newActivity));
-
-        // Allow embedding if the resultTo activity is finishing.
-        activity.finishing = true;
-        assertEquals(EMBEDDING_ALLOWED, newTaskFragment.isAllowedToEmbedActivity(newActivity));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 9090c55..94b5b93 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -54,6 +54,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
+import android.view.DisplayShape;
 import android.view.Gravity;
 import android.view.InsetsState;
 import android.view.PrivacyIndicatorBounds;
@@ -165,7 +166,8 @@
         final DisplayInfo info = dc.computeScreenConfiguration(config, Surface.ROTATION_0);
         final DisplayCutout cutout = dc.calculateDisplayCutoutForRotation(Surface.ROTATION_0);
         final DisplayFrames displayFrames = new DisplayFrames(new InsetsState(),
-                info, cutout, RoundedCorners.NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds());
+                info, cutout, RoundedCorners.NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds(),
+                DisplayShape.NONE);
         wallpaperWindow.mToken.applyFixedRotationTransform(info, displayFrames, config);
 
         // Check that the wallpaper has the same frame in landscape than in portrait
@@ -369,7 +371,7 @@
         final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
         token.finishSync(t, false /* cancel */);
         transit.onTransactionReady(transit.getSyncId(), t);
-        dc.mTransitionController.finishTransition(transit);
+        dc.mTransitionController.finishTransition(transit.getToken());
         assertFalse(wallpaperWindow.isVisible());
         assertFalse(token.isVisible());
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 4429aef..871030f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -16,12 +16,16 @@
 
 package com.android.server.wm;
 
+import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.FLAG_OWN_FOCUS;
 import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
@@ -81,6 +85,9 @@
 import android.window.WindowContainerToken;
 
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -99,6 +106,11 @@
     @Rule
     public ExpectedException mExpectedException = ExpectedException.none();
 
+    @Rule
+    public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
+            InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+            ADD_TRUSTED_DISPLAY);
+
     @Test
     public void testAddWindowToken() {
         IBinder token = mock(IBinder.class);
@@ -396,9 +408,15 @@
     @Test
     public void testSetInTouchMode_multiDisplay_globalTouchModeUpdate() {
         // Create one extra display
-        final VirtualDisplay virtualDisplay = createVirtualDisplay();
+        final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false);
+        final VirtualDisplay virtualDisplayOwnTouchMode =
+                createVirtualDisplay(/* ownFocus= */ true);
         final int numberOfDisplays = mWm.mRoot.mChildren.size();
-        assertThat(numberOfDisplays).isAtLeast(2);
+        assertThat(numberOfDisplays).isAtLeast(3);
+        final int numberOfGlobalTouchModeDisplays = (int) mWm.mRoot.mChildren.stream()
+                        .filter(d -> (d.getDisplay().getFlags() & FLAG_OWN_FOCUS) == 0)
+                        .count();
+        assertThat(numberOfGlobalTouchModeDisplays).isAtLeast(2);
 
         // Enable global touch mode (config_perDisplayFocusEnabled set to false)
         Resources mockResources = mock(Resources.class);
@@ -417,15 +435,15 @@
 
         mWm.setInTouchMode(!currentTouchMode, DEFAULT_DISPLAY);
 
-        verify(mWm.mInputManager, times(numberOfDisplays)).setInTouchMode(
+        verify(mWm.mInputManager, times(numberOfGlobalTouchModeDisplays)).setInTouchMode(
                 eq(!currentTouchMode), eq(callingPid), eq(callingUid),
                 /* hasPermission= */ eq(true), /* displayId= */ anyInt());
     }
 
     @Test
-    public void testSetInTouchMode_multiDisplay_singleDisplayTouchModeUpdate() {
+    public void testSetInTouchMode_multiDisplay_perDisplayFocus_singleDisplayTouchModeUpdate() {
         // Create one extra display
-        final VirtualDisplay virtualDisplay = createVirtualDisplay();
+        final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false);
         final int numberOfDisplays = mWm.mRoot.mChildren.size();
         assertThat(numberOfDisplays).isAtLeast(2);
 
@@ -452,14 +470,47 @@
                 virtualDisplay.getDisplay().getDisplayId());
     }
 
-    private VirtualDisplay createVirtualDisplay() {
+    @Test
+    public void testSetInTouchMode_multiDisplay_ownTouchMode_singleDisplayTouchModeUpdate() {
+        // Create one extra display
+        final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ true);
+        final int numberOfDisplays = mWm.mRoot.mChildren.size();
+        assertThat(numberOfDisplays).isAtLeast(2);
+
+        // Enable global touch mode (config_perDisplayFocusEnabled set to false)
+        Resources mockResources = mock(Resources.class);
+        spyOn(mContext);
+        when(mContext.getResources()).thenReturn(mockResources);
+        doReturn(false).when(mockResources).getBoolean(
+                com.android.internal.R.bool.config_perDisplayFocusEnabled);
+
+        // Get current touch mode state and setup WMS to run setInTouchMode
+        boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY);
+        int callingPid = Binder.getCallingPid();
+        int callingUid = Binder.getCallingUid();
+        doReturn(false).when(mWm).checkCallingPermission(anyString(), anyString(), anyBoolean());
+        when(mWm.mAtmService.instrumentationSourceHasPermission(callingPid,
+                android.Manifest.permission.MODIFY_TOUCH_MODE_STATE)).thenReturn(true);
+
+        mWm.setInTouchMode(!currentTouchMode, virtualDisplay.getDisplay().getDisplayId());
+
+        // Ensure that new display touch mode state has changed.
+        verify(mWm.mInputManager).setInTouchMode(
+                !currentTouchMode, callingPid, callingUid, /* hasPermission= */ true,
+                virtualDisplay.getDisplay().getDisplayId());
+    }
+
+    private VirtualDisplay createVirtualDisplay(boolean ownFocus) {
         // Create virtual display
         Point surfaceSize = new Point(
                 mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
                 mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
+        int flags = VIRTUAL_DISPLAY_FLAG_PUBLIC;
+        if (ownFocus) {
+            flags |= VIRTUAL_DISPLAY_FLAG_OWN_FOCUS | VIRTUAL_DISPLAY_FLAG_TRUSTED;
+        }
         VirtualDisplay virtualDisplay = mWm.mDisplayManager.createVirtualDisplay("VirtualDisplay",
-                surfaceSize.x, surfaceSize.y,
-                DisplayMetrics.DENSITY_140, new Surface(), VIRTUAL_DISPLAY_FLAG_PUBLIC);
+                surfaceSize.x, surfaceSize.y, DisplayMetrics.DENSITY_140, new Surface(), flags);
         final int displayId = virtualDisplay.getDisplay().getDisplayId();
         mWm.mRoot.onDisplayAdded(displayId);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 1348770..5a261bc65 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1746,7 +1746,7 @@
         }
 
         void startTransition() {
-            mOrganizer.startTransition(mLastTransit, null);
+            mOrganizer.startTransition(mLastTransit.getToken(), null);
         }
 
         void onTransactionReady(SurfaceControl.Transaction t) {
@@ -1759,7 +1759,7 @@
         }
 
         public void finish() {
-            mController.finishTransition(mLastTransit);
+            mController.finishTransition(mLastTransit.getToken());
         }
     }
 }
diff --git a/services/usb/Android.bp b/services/usb/Android.bp
index 52cfe25..133f924 100644
--- a/services/usb/Android.bp
+++ b/services/usb/Android.bp
@@ -33,5 +33,6 @@
         "android.hardware.usb.gadget-V1.0-java",
         "android.hardware.usb.gadget-V1.1-java",
         "android.hardware.usb.gadget-V1.2-java",
+        "android.hardware.usb.gadget-V1-java",
     ],
 }
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 1c081c1..ffdb07b 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -44,6 +44,7 @@
 import android.debug.AdbNotifications;
 import android.debug.AdbTransportType;
 import android.debug.IAdbTransport;
+import android.hardware.usb.IUsbOperationInternal;
 import android.hardware.usb.ParcelableUsbPort;
 import android.hardware.usb.UsbAccessory;
 import android.hardware.usb.UsbConfiguration;
@@ -54,9 +55,7 @@
 import android.hardware.usb.UsbPort;
 import android.hardware.usb.UsbPortStatus;
 import android.hardware.usb.gadget.V1_0.GadgetFunction;
-import android.hardware.usb.gadget.V1_0.IUsbGadget;
 import android.hardware.usb.gadget.V1_0.Status;
-import android.hardware.usb.gadget.V1_2.IUsbGadgetCallback;
 import android.hardware.usb.gadget.V1_2.UsbSpeed;
 import android.hidl.manager.V1_0.IServiceManager;
 import android.hidl.manager.V1_0.IServiceNotification;
@@ -88,9 +87,12 @@
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.dump.DualDumpOutputStream;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
+import com.android.server.usb.hal.gadget.UsbGadgetHal;
+import com.android.server.usb.hal.gadget.UsbGadgetHalInstance;
 import com.android.server.utils.EventLogger;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
@@ -106,6 +108,7 @@
 import java.util.NoSuchElementException;
 import java.util.Scanner;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * UsbDeviceManager manages USB state in device mode.
@@ -216,6 +219,13 @@
 
     private static EventLogger sEventLogger;
 
+    private UsbGadgetHal mUsbGadgetHal;
+
+    /**
+     * Counter for tracking UsbOperation operations.
+     */
+    private static final AtomicInteger sUsbOperationCount = new AtomicInteger();
+
     static {
         sDenyInterfaces = new HashSet<>();
         sDenyInterfaces.add(UsbConstants.USB_CLASS_AUDIO);
@@ -298,15 +308,11 @@
         mHasUsbAccessory = pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY);
         initRndisAddress();
 
+        int operationId = sUsbOperationCount.incrementAndGet();
         boolean halNotPresent = false;
-        try {
-            IUsbGadget.getService(true);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "USB GADGET HAL present but exception thrown", e);
-        } catch (NoSuchElementException e) {
-            halNotPresent = true;
-            Slog.i(TAG, "USB GADGET HAL not present in the device", e);
-        }
+
+        mUsbGadgetHal = UsbGadgetHalInstance.getInstance(this, null);
+        Slog.d(TAG, "getInstance done");
 
         mControlFds = new HashMap<>();
         FileDescriptor mtpFd = nativeOpenControl(UsbManager.USB_FUNCTION_MTP);
@@ -320,7 +326,7 @@
         }
         mControlFds.put(UsbManager.FUNCTION_PTP, ptpFd);
 
-        if (halNotPresent) {
+        if (mUsbGadgetHal == null) {
             /**
              * Initialze the legacy UsbHandler
              */
@@ -334,6 +340,8 @@
                     alsaManager, permissionManager);
         }
 
+        mHandler.handlerInitDone(operationId);
+
         if (nativeIsStartRequested()) {
             if (DEBUG) Slog.d(TAG, "accessory attached at boot");
             startAccessoryMode();
@@ -455,6 +463,8 @@
     private void startAccessoryMode() {
         if (!mHasUsbAccessory) return;
 
+        int operationId = sUsbOperationCount.incrementAndGet();
+
         mAccessoryStrings = nativeGetAccessoryStrings();
         boolean enableAudio = (nativeGetAudioMode() == AUDIO_MODE_SOURCE);
         // don't start accessory mode if our mandatory strings have not been set
@@ -475,7 +485,7 @@
                     ACCESSORY_REQUEST_TIMEOUT);
             mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_ACCESSORY_HANDSHAKE_TIMEOUT),
                     ACCESSORY_HANDSHAKE_TIMEOUT);
-            setCurrentFunctions(functions);
+            setCurrentFunctions(functions, operationId);
         }
     }
 
@@ -504,6 +514,20 @@
         }
     }
 
+    public static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) {
+        Slog.println(priority, TAG, msg);
+        if (pw != null) {
+            pw.println(msg);
+        }
+    }
+
+    public static void logAndPrintException(IndentingPrintWriter pw, String msg, Exception e) {
+        Slog.e(TAG, msg, e);
+        if (pw != null) {
+            pw.println(msg + e);
+        }
+    }
+
     abstract static class UsbHandler extends Handler {
 
         // current USB state
@@ -608,6 +632,19 @@
             sendMessage(m);
         }
 
+        public boolean sendMessage(int what) {
+            removeMessages(what);
+            Message m = Message.obtain(this, what);
+            return sendMessageDelayed(m,0);
+        }
+
+        public void sendMessage(int what, int operationId) {
+            removeMessages(what);
+            Message m = Message.obtain(this, what);
+            m.arg1 = operationId;
+            sendMessage(m);
+        }
+
         public void sendMessage(int what, Object arg) {
             removeMessages(what);
             Message m = Message.obtain(this, what);
@@ -615,6 +652,22 @@
             sendMessage(m);
         }
 
+        public void sendMessage(int what, Object arg, int operationId) {
+            removeMessages(what);
+            Message m = Message.obtain(this, what);
+            m.obj = arg;
+            m.arg1 = operationId;
+            sendMessage(m);
+        }
+
+        public void sendMessage(int what, boolean arg, int operationId) {
+            removeMessages(what);
+            Message m = Message.obtain(this, what);
+            m.arg1 = (arg ? 1 : 0);
+            m.arg2 = operationId;
+            sendMessage(m);
+        }
+
         public void sendMessage(int what, Object arg, boolean arg1) {
             removeMessages(what);
             Message m = Message.obtain(this, what);
@@ -623,6 +676,15 @@
             sendMessage(m);
         }
 
+        public void sendMessage(int what, long arg, boolean arg1, int operationId) {
+            removeMessages(what);
+            Message m = Message.obtain(this, what);
+            m.obj = arg;
+            m.arg1 = (arg1 ? 1 : 0);
+            m.arg2 = operationId;
+            sendMessage(m);
+        }
+
         public void sendMessage(int what, boolean arg1, boolean arg2) {
             removeMessages(what);
             Message m = Message.obtain(this, what);
@@ -680,7 +742,7 @@
             sendMessageDelayed(msg, HOST_STATE_UPDATE_DELAY);
         }
 
-        private void setAdbEnabled(boolean enable) {
+        private void setAdbEnabled(boolean enable, int operationId) {
             if (DEBUG) Slog.d(TAG, "setAdbEnabled: " + enable);
 
             if (enable) {
@@ -689,7 +751,7 @@
                 setSystemProperty(USB_PERSISTENT_CONFIG_PROPERTY, "");
             }
 
-            setEnabledFunctions(mCurrentFunctions, true);
+            setEnabledFunctions(mCurrentFunctions, true, operationId);
             updateAdbNotification(false);
         }
 
@@ -701,6 +763,8 @@
         private void updateCurrentAccessory() {
             // We are entering accessory mode if we have received a request from the host
             // and the request has not timed out yet.
+            int operationId = sUsbOperationCount.incrementAndGet();
+
             boolean enteringAccessoryMode = hasMessages(MSG_ACCESSORY_MODE_ENTER_TIMEOUT);
 
             if (mConfigured && enteringAccessoryMode) {
@@ -732,18 +796,18 @@
                 }
             } else {
                 if (!enteringAccessoryMode) {
-                    notifyAccessoryModeExit();
+                    notifyAccessoryModeExit(operationId);
                 } else if (DEBUG) {
                     Slog.v(TAG, "Debouncing accessory mode exit");
                 }
             }
         }
 
-        private void notifyAccessoryModeExit() {
+        private void notifyAccessoryModeExit(int operationId) {
             // make sure accessory mode is off
             // and restore default functions
             Slog.d(TAG, "exited USB accessory mode");
-            setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+            setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
 
             if (mCurrentAccessory != null) {
                 if (mBootCompleted) {
@@ -869,8 +933,8 @@
                     mMidiEnabled && mConfigured, mMidiCard, mMidiDevice);
         }
 
-        private void setScreenUnlockedFunctions() {
-            setEnabledFunctions(mScreenUnlockedFunctions, false);
+        private void setScreenUnlockedFunctions(int operationId) {
+            setEnabledFunctions(mScreenUnlockedFunctions, false, operationId);
         }
 
         private static class AdbTransport extends IAdbTransport.Stub {
@@ -883,7 +947,8 @@
             @Override
             public void onAdbEnabled(boolean enabled, byte transportType) {
                 if (transportType == AdbTransportType.USB) {
-                    mHandler.sendMessage(MSG_ENABLE_ADB, enabled);
+                    int operationId = sUsbOperationCount.incrementAndGet();
+                    mHandler.sendMessage(MSG_ENABLE_ADB, enabled, operationId);
                 }
             }
         }
@@ -906,6 +971,7 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_UPDATE_STATE:
+                    int operationId = sUsbOperationCount.incrementAndGet();
                     mConnected = (msg.arg1 == 1);
                     mConfigured = (msg.arg2 == 1);
 
@@ -923,9 +989,9 @@
                             // restore defaults when USB is disconnected
                             if (!mScreenLocked
                                     && mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) {
-                                setScreenUnlockedFunctions();
+                                setScreenUnlockedFunctions(operationId);
                             } else {
-                                setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+                                setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
                             }
                         }
                         updateUsbFunctions();
@@ -1036,13 +1102,15 @@
                     updateUsbNotification(false);
                     break;
                 case MSG_ENABLE_ADB:
-                    setAdbEnabled(msg.arg1 == 1);
+                    setAdbEnabled(msg.arg1 == 1, msg.arg2);
                     break;
                 case MSG_SET_CURRENT_FUNCTIONS:
                     long functions = (Long) msg.obj;
-                    setEnabledFunctions(functions, false);
+                    operationId = (int) msg.arg1;
+                    setEnabledFunctions(functions, false, operationId);
                     break;
                 case MSG_SET_SCREEN_UNLOCKED_FUNCTIONS:
+                    operationId = sUsbOperationCount.incrementAndGet();
                     mScreenUnlockedFunctions = (Long) msg.obj;
                     if (mSettings != null) {
                         SharedPreferences.Editor editor = mSettings.edit();
@@ -1053,12 +1121,13 @@
                     }
                     if (!mScreenLocked && mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) {
                         // If the screen is unlocked, also set current functions.
-                        setScreenUnlockedFunctions();
+                        setScreenUnlockedFunctions(operationId);
                     } else {
-                        setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+                        setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
                     }
                     break;
                 case MSG_UPDATE_SCREEN_LOCK:
+                    operationId = sUsbOperationCount.incrementAndGet();
                     if (msg.arg1 == 1 == mScreenLocked) {
                         break;
                     }
@@ -1068,23 +1137,25 @@
                     }
                     if (mScreenLocked) {
                         if (!mConnected) {
-                            setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+                            setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
                         }
                     } else {
                         if (mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE
                                 && mCurrentFunctions == UsbManager.FUNCTION_NONE) {
                             // Set the screen unlocked functions if current function is charging.
-                            setScreenUnlockedFunctions();
+                            setScreenUnlockedFunctions(operationId);
                         }
                     }
                     break;
                 case MSG_UPDATE_USER_RESTRICTIONS:
+                    operationId = sUsbOperationCount.incrementAndGet();
                     // Restart the USB stack if USB transfer is enabled but no longer allowed.
                     if (isUsbDataTransferActive(mCurrentFunctions) && !isUsbTransferAllowed()) {
-                        setEnabledFunctions(UsbManager.FUNCTION_NONE, true);
+                        setEnabledFunctions(UsbManager.FUNCTION_NONE, true, operationId);
                     }
                     break;
                 case MSG_SYSTEM_READY:
+                    operationId = sUsbOperationCount.incrementAndGet();
                     mNotificationManager = (NotificationManager)
                             mContext.getSystemService(Context.NOTIFICATION_SERVICE);
 
@@ -1102,17 +1173,19 @@
                                         NotificationManager.IMPORTANCE_HIGH));
                     }
                     mSystemReady = true;
-                    finishBoot();
+                    finishBoot(operationId);
                     break;
                 case MSG_LOCALE_CHANGED:
                     updateAdbNotification(true);
                     updateUsbNotification(true);
                     break;
                 case MSG_BOOT_COMPLETED:
+                    operationId = sUsbOperationCount.incrementAndGet();
                     mBootCompleted = true;
-                    finishBoot();
+                    finishBoot(operationId);
                     break;
                 case MSG_USER_SWITCHED: {
+                    operationId = sUsbOperationCount.incrementAndGet();
                     if (mCurrentUser != msg.arg1) {
                         if (DEBUG) {
                             Slog.v(TAG, "Current user switched to " + msg.arg1);
@@ -1125,16 +1198,18 @@
                                     mSettings.getString(String.format(Locale.ENGLISH,
                                             UNLOCKED_CONFIG_PREF, mCurrentUser), ""));
                         }
-                        setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+                        setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
                     }
                     break;
                 }
                 case MSG_ACCESSORY_MODE_ENTER_TIMEOUT: {
+                    operationId = sUsbOperationCount.incrementAndGet();
                     if (DEBUG) {
-                        Slog.v(TAG, "Accessory mode enter timeout: " + mConnected);
+                        Slog.v(TAG, "Accessory mode enter timeout: " + mConnected
+                                    + " ,operationId: " + operationId);
                     }
                     if (!mConnected || (mCurrentFunctions & UsbManager.FUNCTION_ACCESSORY) == 0) {
-                        notifyAccessoryModeExit();
+                        notifyAccessoryModeExit(operationId);
                     }
                     break;
                 }
@@ -1157,7 +1232,9 @@
             }
         }
 
-        protected void finishBoot() {
+        public abstract void handlerInitDone(int operationId);
+
+        protected void finishBoot(int operationId) {
             if (mBootCompleted && mCurrentUsbFunctionsReceived && mSystemReady) {
                 if (mPendingBootBroadcast) {
                     updateUsbStateBroadcastIfNeeded(getAppliedFunctions(mCurrentFunctions));
@@ -1165,9 +1242,9 @@
                 }
                 if (!mScreenLocked
                         && mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) {
-                    setScreenUnlockedFunctions();
+                    setScreenUnlockedFunctions(operationId);
                 } else {
-                    setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+                    setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
                 }
                 if (mCurrentAccessory != null) {
                     mUsbDeviceManager.getCurrentSettings().accessoryAttached(mCurrentAccessory);
@@ -1507,7 +1584,8 @@
         /**
          * Evaluates USB function policies and applies the change accordingly.
          */
-        protected abstract void setEnabledFunctions(long functions, boolean forceRestart);
+        protected abstract void setEnabledFunctions(long functions,
+                boolean forceRestart, int operationId);
 
         public void setAccessoryUEventTime(long accessoryConnectionStartTime) {
             mAccessoryConnectionStartTime = accessoryConnectionStartTime;
@@ -1522,6 +1600,11 @@
             mSendStringCount = 0;
             mStartAccessory = false;
         }
+
+        public abstract void setCurrentUsbFunctionsCb(long functions,
+                    int status, int mRequest, long mFunctions, boolean mChargingFunctions);
+
+        public abstract void getUsbSpeedCb(int speed);
     }
 
     private static final class UsbHandlerLegacy extends UsbHandler {
@@ -1540,6 +1623,11 @@
         private String mCurrentFunctionsStr;
         private boolean mUsbDataUnlocked;
 
+        /**
+         * Keeps track of the latest setCurrentUsbFunctions request number.
+         */
+        private int mCurrentRequest = 0;
+
         UsbHandlerLegacy(Looper looper, Context context, UsbDeviceManager deviceManager,
                 UsbAlsaManager alsaManager, UsbPermissionManager permissionManager) {
             super(looper, context, deviceManager, alsaManager, permissionManager);
@@ -1573,6 +1661,10 @@
             }
         }
 
+        @Override
+        public void handlerInitDone(int operationId) {
+        }
+
         private void readOemUsbOverrideConfig(Context context) {
             String[] configList = context.getResources().getStringArray(
                     com.android.internal.R.array.config_oemUsbModeOverride);
@@ -1675,11 +1767,14 @@
         }
 
         @Override
-        protected void setEnabledFunctions(long usbFunctions, boolean forceRestart) {
+        protected void setEnabledFunctions(long usbFunctions,
+                boolean forceRestart, int operationId) {
             boolean usbDataUnlocked = isUsbDataTransferActive(usbFunctions);
             if (DEBUG) {
-                Slog.d(TAG, "setEnabledFunctions functions=" + usbFunctions + ", "
-                        + "forceRestart=" + forceRestart + ", usbDataUnlocked=" + usbDataUnlocked);
+                Slog.d(TAG, "setEnabledFunctions functions=" + usbFunctions +
+                        " ,forceRestart=" + forceRestart +
+                        " ,usbDataUnlocked=" + usbDataUnlocked +
+                        " ,operationId=" + operationId);
             }
 
             if (usbDataUnlocked != mUsbDataUnlocked) {
@@ -1775,7 +1870,6 @@
                     || !mCurrentFunctionsStr.equals(functions)
                     || !mCurrentFunctionsApplied
                     || forceRestart) {
-                Slog.i(TAG, "Setting USB config to " + functions);
                 mCurrentFunctionsStr = functions;
                 mCurrentOemFunctions = oemFunctions;
                 mCurrentFunctionsApplied = false;
@@ -1871,15 +1965,18 @@
             if (charAfter < functions.length() && functions.charAt(charAfter) != ',') return false;
             return true;
         }
+
+        @Override
+        public void setCurrentUsbFunctionsCb(long functions,
+                    int status, int mRequest, long mFunctions, boolean mChargingFunctions){
+        }
+
+        @Override
+        public void getUsbSpeedCb(int speed){
+        }
     }
 
-    private static final class UsbHandlerHal extends UsbHandler {
-
-        /**
-         * Proxy object for the usb gadget hal daemon.
-         */
-        @GuardedBy("mGadgetProxyLock")
-        private IUsbGadget mGadgetProxy;
+    private final class UsbHandlerHal extends UsbHandler {
 
         private final Object mGadgetProxyLock = new Object();
 
@@ -1926,33 +2023,20 @@
         UsbHandlerHal(Looper looper, Context context, UsbDeviceManager deviceManager,
                 UsbAlsaManager alsaManager, UsbPermissionManager permissionManager) {
             super(looper, context, deviceManager, alsaManager, permissionManager);
+            int operationId = sUsbOperationCount.incrementAndGet();
             try {
-                ServiceNotification serviceNotification = new ServiceNotification();
-
-                boolean ret = IServiceManager.getService()
-                        .registerForNotifications(GADGET_HAL_FQ_NAME, "", serviceNotification);
-                if (!ret) {
-                    Slog.e(TAG, "Failed to register usb gadget service start notification");
-                    return;
-                }
 
                 synchronized (mGadgetProxyLock) {
-                    mGadgetProxy = IUsbGadget.getService(true);
-                    mGadgetProxy.linkToDeath(new UsbGadgetDeathRecipient(),
-                            USB_GADGET_HAL_DEATH_COOKIE);
                     mCurrentFunctions = UsbManager.FUNCTION_NONE;
                     mCurrentUsbFunctionsRequested = true;
                     mUsbSpeed = UsbSpeed.UNKNOWN;
                     mCurrentGadgetHalVersion = UsbManager.GADGET_HAL_V1_0;
-                    mGadgetProxy.getCurrentUsbFunctions(new UsbGadgetCallback());
+                    updateUsbGadgetHalVersion();
                 }
                 String state = FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim();
                 updateState(state);
-                updateUsbGadgetHalVersion();
             } catch (NoSuchElementException e) {
                 Slog.e(TAG, "Usb gadget hal not found", e);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Usb Gadget hal not responding", e);
             } catch (Exception e) {
                 Slog.e(TAG, "Error initializing UsbHandler", e);
             }
@@ -1965,7 +2049,7 @@
                 if (cookie == USB_GADGET_HAL_DEATH_COOKIE) {
                     Slog.e(TAG, "Usb Gadget hal service died cookie: " + cookie);
                     synchronized (mGadgetProxyLock) {
-                        mGadgetProxy = null;
+                        mUsbGadgetHal = null;
                     }
                 }
             }
@@ -1988,18 +2072,22 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_SET_CHARGING_FUNCTIONS:
-                    setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+                    int operationId = sUsbOperationCount.incrementAndGet();
+                    setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
                     break;
                 case MSG_SET_FUNCTIONS_TIMEOUT:
-                    Slog.e(TAG, "Set functions timed out! no reply from usb hal");
+                    operationId = sUsbOperationCount.incrementAndGet();
+                    Slog.e(TAG, "Set functions timed out! no reply from usb hal"
+                                + " ,operationId:" + operationId);
                     if (msg.arg1 != 1) {
                         // Set this since default function may be selected from Developer options
-                        setEnabledFunctions(mScreenUnlockedFunctions, false);
+                        setEnabledFunctions(mScreenUnlockedFunctions, false, operationId);
                     }
                     break;
                 case MSG_GET_CURRENT_USB_FUNCTIONS:
                     Slog.i(TAG, "processing MSG_GET_CURRENT_USB_FUNCTIONS");
                     mCurrentUsbFunctionsReceived = true;
+                    operationId = msg.arg2;
 
                     if (mCurrentUsbFunctionsRequested) {
                         Slog.i(TAG, "updating mCurrentFunctions");
@@ -2009,91 +2097,71 @@
                                 "mCurrentFunctions:" + mCurrentFunctions + "applied:" + msg.arg1);
                         mCurrentFunctionsApplied = msg.arg1 == 1;
                     }
-                    finishBoot();
+                    finishBoot(operationId);
                     break;
                 case MSG_FUNCTION_SWITCH_TIMEOUT:
                     /**
                      * Dont force to default when the configuration is already set to default.
                      */
+                    operationId = sUsbOperationCount.incrementAndGet();
                     if (msg.arg1 != 1) {
                         // Set this since default function may be selected from Developer options
-                        setEnabledFunctions(mScreenUnlockedFunctions, false);
+                        setEnabledFunctions(mScreenUnlockedFunctions, false, operationId);
                     }
                     break;
                 case MSG_GADGET_HAL_REGISTERED:
                     boolean preexisting = msg.arg1 == 1;
+                    operationId = sUsbOperationCount.incrementAndGet();
                     synchronized (mGadgetProxyLock) {
                         try {
-                            mGadgetProxy = IUsbGadget.getService();
-                            mGadgetProxy.linkToDeath(new UsbGadgetDeathRecipient(),
-                                    USB_GADGET_HAL_DEATH_COOKIE);
+                            mUsbGadgetHal = UsbGadgetHalInstance.getInstance(mUsbDeviceManager,
+                                    null);
                             if (!mCurrentFunctionsApplied && !preexisting) {
-                                setEnabledFunctions(mCurrentFunctions, false);
+                                setEnabledFunctions(mCurrentFunctions, false, operationId);
                             }
                         } catch (NoSuchElementException e) {
                             Slog.e(TAG, "Usb gadget hal not found", e);
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "Usb Gadget hal not responding", e);
                         }
                     }
                     break;
                 case MSG_RESET_USB_GADGET:
                     synchronized (mGadgetProxyLock) {
-                        if (mGadgetProxy == null) {
-                            Slog.e(TAG, "reset Usb Gadget mGadgetProxy is null");
+                        if (mUsbGadgetHal == null) {
+                            Slog.e(TAG, "reset Usb Gadget mUsbGadgetHal is null");
                             break;
                         }
 
                         try {
-                            android.hardware.usb.gadget.V1_1.IUsbGadget gadgetProxy =
-                                    android.hardware.usb.gadget.V1_1.IUsbGadget
-                                            .castFrom(mGadgetProxy);
-                            gadgetProxy.reset();
-                        } catch (RemoteException e) {
+                            mUsbGadgetHal.reset();
+                        } catch (Exception e) {
                             Slog.e(TAG, "reset Usb Gadget failed", e);
                         }
                     }
                     break;
                 case MSG_UPDATE_USB_SPEED:
-                    synchronized (mGadgetProxyLock) {
-                        if (mGadgetProxy == null) {
-                            Slog.e(TAG, "mGadgetProxy is null");
-                            break;
-                        }
+                    operationId = sUsbOperationCount.incrementAndGet();
+                    if (mUsbGadgetHal == null) {
+                        Slog.e(TAG, "mGadgetHal is null, operationId:" + operationId);
+                        break;
+                    }
 
-                        try {
-                            android.hardware.usb.gadget.V1_2.IUsbGadget gadgetProxy =
-                                    android.hardware.usb.gadget.V1_2.IUsbGadget
-                                            .castFrom(mGadgetProxy);
-                            if (gadgetProxy != null) {
-                                gadgetProxy.getUsbSpeed(new UsbGadgetCallback());
-                            }
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "get UsbSpeed failed", e);
-                        }
+                    try {
+                        mUsbGadgetHal.getUsbSpeed(operationId);
+                    } catch (Exception e) {
+                        Slog.e(TAG, "get UsbSpeed failed", e);
                     }
                     break;
                 case MSG_UPDATE_HAL_VERSION:
-                    synchronized (mGadgetProxyLock) {
-                        if (mGadgetProxy == null) {
-                            Slog.e(TAG, "mGadgetProxy is null");
-                            break;
+                    if (mUsbGadgetHal == null) {
+                        Slog.e(TAG, "mUsbGadgetHal is null");
+                        break;
+                    }
+                    else {
+                        try {
+                            mCurrentGadgetHalVersion = mUsbGadgetHal.getGadgetHalVersion();
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "update Usb gadget version failed", e);
                         }
-
-                        android.hardware.usb.gadget.V1_2.IUsbGadget gadgetProxy =
-                                android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy);
-                        if (gadgetProxy == null) {
-                            android.hardware.usb.gadget.V1_1.IUsbGadget gadgetProxyV1By1 =
-                                    android.hardware.usb.gadget.V1_1.IUsbGadget
-                                            .castFrom(mGadgetProxy);
-                            if (gadgetProxyV1By1 == null) {
-                                mCurrentGadgetHalVersion = UsbManager.GADGET_HAL_V1_0;
-                                break;
-                            }
-                            mCurrentGadgetHalVersion = UsbManager.GADGET_HAL_V1_1;
-                            break;
-                        }
-                        mCurrentGadgetHalVersion = UsbManager.GADGET_HAL_V1_2;
                     }
                     break;
                 default:
@@ -2101,56 +2169,31 @@
             }
         }
 
-        private class UsbGadgetCallback extends IUsbGadgetCallback.Stub {
-            int mRequest;
-            long mFunctions;
-            boolean mChargingFunctions;
+        @Override
+        public void setCurrentUsbFunctionsCb(long functions,
+                    int status, int mRequest, long mFunctions, boolean mChargingFunctions) {
 
-            UsbGadgetCallback() {
+            if ((mCurrentRequest != mRequest) || !hasMessages(MSG_SET_FUNCTIONS_TIMEOUT)
+                  || (mFunctions != functions)) {
+                return;
             }
 
-            UsbGadgetCallback(int request, long functions,
-                    boolean chargingFunctions) {
-                mRequest = request;
-                mFunctions = functions;
-                mChargingFunctions = chargingFunctions;
-            }
-
-            @Override
-            public void setCurrentUsbFunctionsCb(long functions,
-                    int status) {
-                /**
-                 * Callback called for a previous setCurrenUsbFunction
-                 */
-                if ((mCurrentRequest != mRequest) || !hasMessages(MSG_SET_FUNCTIONS_TIMEOUT)
-                        || (mFunctions != functions)) {
-                    return;
-                }
-
-                removeMessages(MSG_SET_FUNCTIONS_TIMEOUT);
-                Slog.e(TAG, "notifyCurrentFunction request:" + mRequest + " status:" + status);
-                if (status == Status.SUCCESS) {
-                    mCurrentFunctionsApplied = true;
-                } else if (!mChargingFunctions) {
-                    Slog.e(TAG, "Setting default fuctions");
-                    sendEmptyMessage(MSG_SET_CHARGING_FUNCTIONS);
-                }
-            }
-
-            @Override
-            public void getCurrentUsbFunctionsCb(long functions,
-                    int status) {
-                sendMessage(MSG_GET_CURRENT_USB_FUNCTIONS, functions,
-                        status == Status.FUNCTIONS_APPLIED);
-            }
-
-            @Override
-            public void getUsbSpeedCb(int speed) {
-                mUsbSpeed = speed;
+            removeMessages(MSG_SET_FUNCTIONS_TIMEOUT);
+            Slog.i(TAG, "notifyCurrentFunction request:" + mRequest + " status:" + status);
+            if (status == Status.SUCCESS) {
+                mCurrentFunctionsApplied = true;
+            } else if (!mChargingFunctions) {
+                Slog.e(TAG, "Setting default fuctions");
+                sendEmptyMessage(MSG_SET_CHARGING_FUNCTIONS);
             }
         }
 
-        private void setUsbConfig(long config, boolean chargingFunctions) {
+        @Override
+        public void getUsbSpeedCb(int speed) {
+            mUsbSpeed = speed;
+        }
+
+        private void setUsbConfig(long config, boolean chargingFunctions, int operationId) {
             if (true) Slog.d(TAG, "setUsbConfig(" + config + ") request:" + ++mCurrentRequest);
             /**
              * Cancel any ongoing requests, if present.
@@ -2160,8 +2203,8 @@
             removeMessages(MSG_SET_CHARGING_FUNCTIONS);
 
             synchronized (mGadgetProxyLock) {
-                if (mGadgetProxy == null) {
-                    Slog.e(TAG, "setUsbConfig mGadgetProxy is null");
+                if (mUsbGadgetHal == null) {
+                    Slog.e(TAG, "setUsbConfig mUsbGadgetHal is null");
                     return;
                 }
                 try {
@@ -2178,10 +2221,9 @@
                         LocalServices.getService(AdbManagerInternal.class)
                                 .stopAdbdForTransport(AdbTransportType.USB);
                     }
-                    UsbGadgetCallback usbGadgetCallback = new UsbGadgetCallback(mCurrentRequest,
-                            config, chargingFunctions);
-                    mGadgetProxy.setCurrentUsbFunctions(config, usbGadgetCallback,
-                            SET_FUNCTIONS_TIMEOUT_MS - SET_FUNCTIONS_LEEWAY_MS);
+                    mUsbGadgetHal.setCurrentUsbFunctions(mCurrentRequest,
+                            config, chargingFunctions,
+                            SET_FUNCTIONS_TIMEOUT_MS - SET_FUNCTIONS_LEEWAY_MS, operationId);
                     sendMessageDelayed(MSG_SET_FUNCTIONS_TIMEOUT, chargingFunctions,
                             SET_FUNCTIONS_TIMEOUT_MS);
                     if (mConnected) {
@@ -2190,17 +2232,19 @@
                                 SET_FUNCTIONS_TIMEOUT_MS + ENUMERATION_TIME_OUT_MS);
                     }
                     if (DEBUG) Slog.d(TAG, "timeout message queued");
-                } catch (RemoteException e) {
+                } catch (Exception e) {//RemoteException e) {
                     Slog.e(TAG, "Remoteexception while calling setCurrentUsbFunctions", e);
                 }
             }
         }
 
         @Override
-        protected void setEnabledFunctions(long functions, boolean forceRestart) {
+        protected void setEnabledFunctions(long functions, boolean forceRestart, int operationId) {
             if (DEBUG) {
-                Slog.d(TAG, "setEnabledFunctions functions=" + functions + ", "
-                        + "forceRestart=" + forceRestart);
+                Slog.d(TAG, "setEnabledFunctionsi " +
+                        "functions=" + functions +
+                        ", forceRestart=" + forceRestart +
+                        ", operationId=" + operationId);
             }
             if (mCurrentGadgetHalVersion < UsbManager.GADGET_HAL_V1_2) {
                 if ((functions & UsbManager.FUNCTION_NCM) != 0) {
@@ -2221,7 +2265,7 @@
                 functions = getAppliedFunctions(functions);
 
                 // Set the new USB configuration.
-                setUsbConfig(functions, chargingFunctions);
+                setUsbConfig(functions, chargingFunctions, operationId);
 
                 if (mBootCompleted && isUsbDataTransferActive(functions)) {
                     // Start up dependent services.
@@ -2229,6 +2273,11 @@
                 }
             }
         }
+
+        @Override
+        public void handlerInitDone(int operationId) {
+            mUsbGadgetHal.getCurrentUsbFunctions(operationId);
+        }
     }
 
     /* returns the currently attached USB accessory */
@@ -2270,6 +2319,21 @@
         return mHandler.getGadgetHalVersion();
     }
 
+    public void setCurrentUsbFunctionsCb(long functions,
+                    int status, int mRequest, long mFunctions, boolean mChargingFunctions) {
+        mHandler.setCurrentUsbFunctionsCb(functions, status,
+                    mRequest, mFunctions, mChargingFunctions);
+    }
+
+    public void getCurrentUsbFunctionsCb(long functions, int status) {
+        mHandler.sendMessage(MSG_GET_CURRENT_USB_FUNCTIONS, functions,
+                    status == Status.FUNCTIONS_APPLIED);
+    }
+
+    public void getUsbSpeedCb(int speed) {
+        mHandler.getUsbSpeedCb(speed);
+    }
+
     /**
      * Returns a dup of the control file descriptor for the given function.
      */
@@ -2295,7 +2359,7 @@
      *
      * @param functions The functions to set, or empty to set the charging function.
      */
-    public void setCurrentFunctions(long functions) {
+    public void setCurrentFunctions(long functions, int operationId) {
         if (DEBUG) {
             Slog.d(TAG, "setCurrentFunctions(" + UsbManager.usbFunctionsToString(functions) + ")");
         }
@@ -2312,7 +2376,7 @@
         } else if (functions == UsbManager.FUNCTION_ACCESSORY) {
             MetricsLogger.action(mContext, MetricsEvent.ACTION_USB_CONFIG_ACCESSORY);
         }
-        mHandler.sendMessage(MSG_SET_CURRENT_FUNCTIONS, functions);
+        mHandler.sendMessage(MSG_SET_CURRENT_FUNCTIONS, functions, operationId);
     }
 
     /**
@@ -2340,7 +2404,8 @@
     }
 
     private void onAdbEnabled(boolean enabled) {
-        mHandler.sendMessage(MSG_ENABLE_ADB, enabled);
+        int operationId = sUsbOperationCount.incrementAndGet();
+        mHandler.sendMessage(MSG_ENABLE_ADB, enabled, operationId);
     }
 
     /**
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index d821dee..d09f729 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -622,16 +622,16 @@
     }
 
     @Override
-    public void setCurrentFunctions(long functions) {
+    public void setCurrentFunctions(long functions, int operationId) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
         Preconditions.checkArgument(UsbManager.areSettableFunctions(functions));
         Preconditions.checkState(mDeviceManager != null);
-        mDeviceManager.setCurrentFunctions(functions);
+        mDeviceManager.setCurrentFunctions(functions, operationId);
     }
 
     @Override
-    public void setCurrentFunction(String functions, boolean usbDataUnlocked) {
-        setCurrentFunctions(UsbManager.usbFunctionsFromString(functions));
+    public void setCurrentFunction(String functions, boolean usbDataUnlocked, int operationId) {
+        setCurrentFunctions(UsbManager.usbFunctionsFromString(functions), operationId);
     }
 
     @Override
diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetAidl.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetAidl.java
new file mode 100644
index 0000000..bdfe60a
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetAidl.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2022 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.usb.hal.gadget;
+
+import static android.hardware.usb.UsbManager.GADGET_HAL_V2_0;
+
+import static com.android.server.usb.UsbDeviceManager.logAndPrint;
+import static com.android.server.usb.UsbDeviceManager.logAndPrintException;
+
+import android.annotation.Nullable;
+import android.hardware.usb.gadget.IUsbGadget;
+import android.hardware.usb.gadget.IUsbGadgetCallback;
+import android.hardware.usb.UsbManager.UsbGadgetHalVersion;
+import android.os.ServiceManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.usb.UsbDeviceManager;
+
+import java.util.ArrayList;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+
+/**
+ * Implements the methods to interact with AIDL USB HAL.
+ */
+public final class UsbGadgetAidl implements UsbGadgetHal {
+    private static final String TAG = UsbGadgetAidl.class.getSimpleName();
+    private static final String USB_GADGET_AIDL_SERVICE = IUsbGadget.DESCRIPTOR + "/default";
+    // Proxy object for the usb gadget hal daemon.
+    @GuardedBy("mGadgetProxyLock")
+    private IUsbGadget mGadgetProxy;
+    private final UsbDeviceManager mDeviceManager;
+    public final IndentingPrintWriter mPw;
+    // Mutex for all mutable shared state.
+    private final Object mGadgetProxyLock = new Object();
+    // Callback when the UsbDevice status is changed by the kernel.
+    private UsbGadgetCallback mUsbGadgetCallback;
+
+    public @UsbGadgetHalVersion int getGadgetHalVersion() throws RemoteException {
+        synchronized (mGadgetProxyLock) {
+            if (mGadgetProxy == null) {
+                throw new RemoteException("IUsb not initialized yet");
+            }
+        }
+        Slog.i(TAG, "USB Gadget HAL AIDL version: GADGET_HAL_V2_0");
+        return GADGET_HAL_V2_0;
+    }
+
+    @Override
+    public void systemReady() {
+    }
+
+    public void serviceDied() {
+        logAndPrint(Log.ERROR, mPw, "Usb Gadget AIDL hal service died");
+        synchronized (mGadgetProxyLock) {
+            mGadgetProxy = null;
+        }
+        connectToProxy(null);
+    }
+
+    private void connectToProxy(IndentingPrintWriter pw) {
+        synchronized (mGadgetProxyLock) {
+            if (mGadgetProxy != null) {
+                return;
+            }
+
+            try {
+                mGadgetProxy = IUsbGadget.Stub.asInterface(
+                        ServiceManager.waitForService(USB_GADGET_AIDL_SERVICE));
+            } catch (NoSuchElementException e) {
+                logAndPrintException(pw, "connectToProxy: usb gadget hal service not found."
+                        + " Did the service fail to start?", e);
+            }
+        }
+    }
+
+    static boolean isServicePresent(IndentingPrintWriter pw) {
+        try {
+            return ServiceManager.isDeclared(USB_GADGET_AIDL_SERVICE);
+        } catch (NoSuchElementException e) {
+            logAndPrintException(pw, "connectToProxy: usb gadget Aidl hal service not found.", e);
+        }
+
+        return false;
+    }
+
+    public UsbGadgetAidl(UsbDeviceManager deviceManager, IndentingPrintWriter pw) {
+        mDeviceManager = Objects.requireNonNull(deviceManager);
+        mPw = pw;
+        connectToProxy(mPw);
+    }
+
+    @Override
+    public void getCurrentUsbFunctions(long operationId) {
+        synchronized (mGadgetProxyLock) {
+            try {
+                mGadgetProxy.getCurrentUsbFunctions(new UsbGadgetCallback(), operationId);
+            } catch (RemoteException e) {
+                logAndPrintException(mPw,
+                        "RemoteException while calling getCurrentUsbFunctions"
+                        + ", opID:" + operationId, e);
+                return;
+            }
+        }
+    }
+
+    @Override
+    public void getUsbSpeed(long operationId) {
+        try {
+            synchronized (mGadgetProxyLock) {
+                mGadgetProxy.getUsbSpeed(new UsbGadgetCallback(), operationId);
+            }
+        } catch (RemoteException e) {
+            logAndPrintException(mPw,
+                    "RemoteException while calling getUsbSpeed"
+                    + ", opID:" + operationId, e);
+            return;
+        }
+    }
+
+    @Override
+    public void reset() {
+        try {
+            synchronized (mGadgetProxyLock) {
+                mGadgetProxy.reset();
+            }
+        } catch (RemoteException e) {
+            logAndPrintException(mPw,
+                    "RemoteException while calling getUsbSpeed", e);
+            return;
+        }
+    }
+
+    @Override
+    public void setCurrentUsbFunctions(int mRequest, long mFunctions,
+            boolean mChargingFunctions, int timeout, long operationId) {
+        try {
+            mUsbGadgetCallback = new UsbGadgetCallback(mRequest,
+                                      mFunctions, mChargingFunctions);
+            synchronized (mGadgetProxyLock) {
+                mGadgetProxy.setCurrentUsbFunctions(mFunctions, mUsbGadgetCallback,
+                        timeout, operationId);
+            }
+        } catch (RemoteException e) {
+            logAndPrintException(mPw,
+                    "RemoteException while calling setCurrentUsbFunctions: "
+                    + "mRequest=" + mRequest
+                    + ", mFunctions=" + mFunctions
+                    + ", mChargingFunctions=" + mChargingFunctions
+                    + ", timeout=" + timeout
+                    + ", opID:" + operationId, e);
+            return;
+        }
+    }
+
+    private class UsbGadgetCallback extends IUsbGadgetCallback.Stub {
+        public int mRequest;
+        public long mFunctions;
+        public boolean mChargingFunctions;
+
+        UsbGadgetCallback() {
+        }
+
+        UsbGadgetCallback(int request, long functions,
+                boolean chargingFunctions) {
+            mRequest = request;
+            mFunctions = functions;
+            mChargingFunctions = chargingFunctions;
+        }
+
+        @Override
+        public void setCurrentUsbFunctionsCb(long functions,
+                int status, long transactionId) {
+            mDeviceManager.setCurrentUsbFunctionsCb(functions, status,
+                    mRequest, mFunctions, mChargingFunctions);
+        }
+
+        @Override
+        public void getCurrentUsbFunctionsCb(long functions,
+                int status, long transactionId) {
+            mDeviceManager.getCurrentUsbFunctionsCb(functions, status);
+        }
+
+        @Override
+        public void getUsbSpeedCb(int speed, long transactionId) {
+            mDeviceManager.getUsbSpeedCb(speed);
+        }
+
+        @Override
+        public String getInterfaceHash() {
+            return IUsbGadgetCallback.HASH;
+        }
+
+        @Override
+        public int getInterfaceVersion() {
+            return IUsbGadgetCallback.VERSION;
+        }
+    }
+}
+
diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHal.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHal.java
new file mode 100644
index 0000000..267247b
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHal.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2022 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.usb.hal.gadget;
+
+import android.annotation.IntDef;
+import android.hardware.usb.gadget.IUsbGadgetCallback;
+import android.hardware.usb.IUsbOperationInternal;
+import android.hardware.usb.UsbManager.UsbHalVersion;
+import android.os.RemoteException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.String;
+
+/**
+ * @hide
+ */
+public interface UsbGadgetHal {
+    /**
+     * Power role: This USB port can act as a source (provide power).
+     * @hide
+     */
+    public static final int HAL_POWER_ROLE_SOURCE = 1;
+
+    /**
+     * Power role: This USB port can act as a sink (receive power).
+     * @hide
+     */
+    public static final int HAL_POWER_ROLE_SINK = 2;
+
+    @IntDef(prefix = { "HAL_POWER_ROLE_" }, value = {
+            HAL_POWER_ROLE_SOURCE,
+            HAL_POWER_ROLE_SINK
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface HalUsbPowerRole{}
+
+    /**
+     * Data role: This USB port can act as a host (access data services).
+     * @hide
+     */
+    public static final int HAL_DATA_ROLE_HOST = 1;
+
+    /**
+     * Data role: This USB port can act as a device (offer data services).
+     * @hide
+     */
+    public static final int HAL_DATA_ROLE_DEVICE = 2;
+
+    @IntDef(prefix = { "HAL_DATA_ROLE_" }, value = {
+            HAL_DATA_ROLE_HOST,
+            HAL_DATA_ROLE_DEVICE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface HalUsbDataRole{}
+
+    /**
+     * This USB port can act as a downstream facing port (host).
+     *
+     * @hide
+     */
+    public static final int HAL_MODE_DFP = 1;
+
+    /**
+     * This USB port can act as an upstream facing port (device).
+     *
+     * @hide
+     */
+    public static final int HAL_MODE_UFP = 2;
+    @IntDef(prefix = { "HAL_MODE_" }, value = {
+            HAL_MODE_DFP,
+            HAL_MODE_UFP,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface HalUsbPortMode{}
+
+    /**
+     * UsbPortManager would call this when the system is done booting.
+     */
+    public void systemReady();
+
+    /**
+     * This function is used to query the USB functions included in the
+     * current USB configuration.
+     *
+     * @param transactionId Used for tracking the current request and is passed down to the HAL
+     *                      implementation as needed.
+     */
+    public void getCurrentUsbFunctions(long transactionId);
+
+    /**
+     * The function is used to query current USB speed.
+     *
+     * @param transactionId Used for tracking the current request and is passed down to the HAL
+     *                      implementation as needed.
+     */
+    public void getUsbSpeed(long transactionId);
+
+    /**
+     * This function is used to reset USB gadget driver.
+     * Performs USB data connection reset. The connection will disconnect and
+     * reconnect.
+     */
+    public void reset();
+
+    /**
+     * Invoked to query the version of current gadget hal implementation.
+     */
+    public @UsbHalVersion int getGadgetHalVersion() throws RemoteException;
+
+    /**
+     * This function is used to set the current USB gadget configuration.
+     * The USB gadget needs to be torn down if a USB configuration is already
+     * active.
+     *
+     * @param functions list of functions defined by GadgetFunction to be
+     *                  included in the gadget composition.
+     * @param timeout The maximum time (in milliseconds) within which the
+     *                IUsbGadgetCallback needs to be returned.
+     * @param transactionId Used for tracking the current request and is passed down to the HAL
+     *                      implementation as needed.
+     */
+    public void setCurrentUsbFunctions(int request, long functions,
+        boolean chargingFunctions, int timeout, long transactionId);
+}
+
diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHalInstance.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHalInstance.java
new file mode 100644
index 0000000..d268315
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHalInstance.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 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.usb.hal.gadget;
+
+import static com.android.server.usb.UsbPortManager.logAndPrint;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.usb.hal.gadget.UsbGadgetHidl;
+import com.android.server.usb.hal.gadget.UsbGadgetAidl;
+import com.android.server.usb.UsbDeviceManager;
+
+import android.util.Log;
+/**
+ * Helper class that queries the underlying hal layer to populate UsbPortHal instance.
+ */
+public final class UsbGadgetHalInstance {
+
+    public static UsbGadgetHal getInstance(UsbDeviceManager deviceManager,
+            IndentingPrintWriter pw) {
+
+        logAndPrint(Log.DEBUG, pw, "Querying USB Gadget HAL version");
+        if (UsbGadgetAidl.isServicePresent(null)) {
+            logAndPrint(Log.INFO, pw, "USB Gadget HAL AIDL present");
+            return new UsbGadgetAidl(deviceManager, pw);
+        }
+        if (UsbGadgetHidl.isServicePresent(null)) {
+            logAndPrint(Log.INFO, pw, "USB Gadget HAL HIDL present");
+            return new UsbGadgetHidl(deviceManager, pw);
+        }
+
+        logAndPrint(Log.ERROR, pw, "USB Gadget HAL AIDL/HIDL not present");
+        return null;
+    }
+}
+
diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java
new file mode 100644
index 0000000..3e5ecc5
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2022 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.usb.hal.gadget;
+
+import static android.hardware.usb.UsbManager.GADGET_HAL_NOT_SUPPORTED;
+import static android.hardware.usb.UsbManager.GADGET_HAL_V1_0;
+import static android.hardware.usb.UsbManager.GADGET_HAL_V1_1;
+import static android.hardware.usb.UsbManager.GADGET_HAL_V1_2;
+
+import static com.android.server.usb.UsbDeviceManager.logAndPrint;
+import static com.android.server.usb.UsbDeviceManager.logAndPrintException;
+
+import android.annotation.Nullable;
+import android.hardware.usb.gadget.V1_0.Status;
+import android.hardware.usb.gadget.V1_0.IUsbGadget;
+import android.hardware.usb.gadget.V1_2.IUsbGadgetCallback;
+import android.hardware.usb.gadget.V1_2.UsbSpeed;
+import android.hardware.usb.UsbAccessory;
+import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbManager.UsbGadgetHalVersion;
+import android.hardware.usb.UsbManager.UsbHalVersion;
+import android.hidl.manager.V1_0.IServiceManager;
+import android.hidl.manager.V1_0.IServiceNotification;
+import android.os.IHwBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.usb.UsbDeviceManager;
+
+import java.util.ArrayList;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+/**
+ *
+ */
+public final class UsbGadgetHidl implements UsbGadgetHal {
+    // Cookie sent for usb gadget hal death notification.
+    private static final int USB_GADGET_HAL_DEATH_COOKIE = 2000;
+    // Proxy object for the usb gadget hal daemon.
+    @GuardedBy("mGadgetProxyLock")
+    private IUsbGadget mGadgetProxy;
+    private UsbDeviceManager mDeviceManager;
+    private final IndentingPrintWriter mPw;
+    // Mutex for all mutable shared state.
+    private final Object mGadgetProxyLock = new Object();
+    private UsbGadgetCallback mUsbGadgetCallback;
+
+    public @UsbGadgetHalVersion int getGadgetHalVersion() throws RemoteException {
+        int version;
+        synchronized(mGadgetProxyLock) {
+            if (mGadgetProxy == null) {
+                throw new RemoteException("IUsbGadget not initialized yet");
+            }
+            if (android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy) != null) {
+                version = UsbManager.GADGET_HAL_V1_2;
+            } else if (android.hardware.usb.gadget.V1_1.IUsbGadget.castFrom(mGadgetProxy) != null) {
+                version = UsbManager.GADGET_HAL_V1_1;
+            } else {
+                version = UsbManager.GADGET_HAL_V1_0;
+            }
+            logAndPrint(Log.INFO, mPw, "USB Gadget HAL HIDL version: " + version);
+            return version;
+        }
+    }
+
+    final class DeathRecipient implements IHwBinder.DeathRecipient {
+        private final IndentingPrintWriter mPw;
+
+        DeathRecipient(IndentingPrintWriter pw) {
+            mPw = pw;
+        }
+
+        @Override
+        public void serviceDied(long cookie) {
+            if (cookie == USB_GADGET_HAL_DEATH_COOKIE) {
+                logAndPrint(Log.ERROR, mPw, "Usb Gadget hal service died cookie: " + cookie);
+                synchronized (mGadgetProxyLock) {
+                    mGadgetProxy = null;
+                }
+            }
+        }
+    }
+
+    final class ServiceNotification extends IServiceNotification.Stub {
+        @Override
+        public void onRegistration(String fqName, String name, boolean preexisting) {
+            logAndPrint(Log.INFO, mPw, "Usb gadget hal service started " + fqName + " " + name);
+            connectToProxy(null);
+        }
+    }
+
+    private void connectToProxy(IndentingPrintWriter pw) {
+        synchronized (mGadgetProxyLock) {
+            if (mGadgetProxy != null) {
+                return;
+            }
+
+            try {
+                mGadgetProxy = IUsbGadget.getService();
+                mGadgetProxy.linkToDeath(new DeathRecipient(pw), USB_GADGET_HAL_DEATH_COOKIE);
+            } catch (NoSuchElementException e) {
+                logAndPrintException(pw, "connectToProxy: usb gadget hal service not found."
+                        + " Did the service fail to start?", e);
+            } catch (RemoteException e) {
+                logAndPrintException(pw, "connectToProxy: usb gadget hal service not responding"
+                        , e);
+            }
+        }
+    }
+
+    @Override
+    public void systemReady() {
+    }
+
+    static boolean isServicePresent(IndentingPrintWriter pw) {
+        try {
+            IUsbGadget.getService(true);
+        } catch (NoSuchElementException e) {
+            logAndPrintException(pw, "connectToProxy: usb gadget hidl hal service not found.", e);
+            return false;
+        } catch (RemoteException e) {
+            logAndPrintException(pw, "IUSBGadget hal service present but failed to get service", e);
+        }
+
+        return true;
+    }
+
+    public UsbGadgetHidl(UsbDeviceManager deviceManager, IndentingPrintWriter pw) {
+        mDeviceManager = Objects.requireNonNull(deviceManager);
+        mPw = pw;
+        try {
+            ServiceNotification serviceNotification = new ServiceNotification();
+
+            boolean ret = IServiceManager.getService()
+                    .registerForNotifications("android.hardware.usb.gadget@1.0::IUsbGadget",
+                            "", serviceNotification);
+            if (!ret) {
+                logAndPrint(Log.ERROR, pw, "Failed to register service start notification");
+            }
+        } catch (RemoteException e) {
+            logAndPrintException(pw, "Failed to register service start notification", e);
+            return;
+        }
+        connectToProxy(mPw);
+    }
+
+    @Override
+    public void getCurrentUsbFunctions(long transactionId) {
+        try {
+            synchronized(mGadgetProxyLock) {
+                mGadgetProxy.getCurrentUsbFunctions(new UsbGadgetCallback());
+            }
+        } catch (RemoteException e) {
+            logAndPrintException(mPw,
+                    "RemoteException while calling getCurrentUsbFunctions", e);
+            return;
+        }
+    }
+
+    @Override
+    public void getUsbSpeed(long transactionId) {
+        try {
+            synchronized(mGadgetProxyLock) {
+                if (android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy) != null) {
+                    android.hardware.usb.gadget.V1_2.IUsbGadget gadgetProxy =
+                    android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy);
+                    gadgetProxy.getUsbSpeed(new UsbGadgetCallback());
+                }
+            }
+        } catch (RemoteException e) {
+            logAndPrintException(mPw, "get UsbSpeed failed", e);
+        }
+    }
+
+    @Override
+    public void reset() {
+        try {
+            synchronized(mGadgetProxyLock) {
+                if (android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy) != null) {
+                    android.hardware.usb.gadget.V1_2.IUsbGadget gadgetProxy =
+                    android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy);
+                    gadgetProxy.reset();
+                }
+            }
+        } catch (RemoteException e) {
+            logAndPrintException(mPw,
+                    "RemoteException while calling getUsbSpeed", e);
+            return;
+        }
+    }
+
+    @Override
+    public void setCurrentUsbFunctions(int mRequest, long mFunctions,
+            boolean mChargingFunctions, int timeout, long operationId) {
+        try {
+            mUsbGadgetCallback = new UsbGadgetCallback(null, mRequest,
+                                      mFunctions, mChargingFunctions);
+            synchronized(mGadgetProxyLock) {
+                mGadgetProxy.setCurrentUsbFunctions(mFunctions, mUsbGadgetCallback, timeout);
+            }
+        } catch (RemoteException e) {
+            logAndPrintException(mPw,
+                    "RemoteException while calling setCurrentUsbFunctions"
+                    + " mRequest = " + mRequest
+                    + ", mFunctions = " + mFunctions
+                    + ", timeout = " + timeout
+                    + ", mChargingFunctions = " + mChargingFunctions
+                    + ", operationId =" + operationId, e);
+            return;
+        }
+    }
+
+    private class UsbGadgetCallback extends IUsbGadgetCallback.Stub {
+        public int mRequest;
+        public long mFunctions;
+        public boolean mChargingFunctions;
+
+        UsbGadgetCallback() {
+        }
+        UsbGadgetCallback(IndentingPrintWriter pw, int request,
+                long functions, boolean chargingFunctions) {
+            mRequest = request;
+            mFunctions = functions;
+            mChargingFunctions = chargingFunctions;
+        }
+
+        @Override
+        public void setCurrentUsbFunctionsCb(long functions,
+                int status) {
+            mDeviceManager.setCurrentUsbFunctionsCb(functions, status,
+                    mRequest, mFunctions, mChargingFunctions);
+        }
+
+        @Override
+        public void getCurrentUsbFunctionsCb(long functions,
+                int status) {
+            mDeviceManager.getCurrentUsbFunctionsCb(functions, status);
+        }
+
+        @Override
+        public void getUsbSpeedCb(int speed) {
+            mDeviceManager.getUsbSpeedCb(speed);
+        }
+    }
+}
+
diff --git a/startop/view_compiler/apk_layout_compiler.cc b/startop/view_compiler/apk_layout_compiler.cc
index 5cb0c17..1d3b6481 100644
--- a/startop/view_compiler/apk_layout_compiler.cc
+++ b/startop/view_compiler/apk_layout_compiler.cc
@@ -100,56 +100,60 @@
       dex_file.MakeClass(StringPrintf("%s.CompiledView", package_name.c_str()))};
   std::vector<dex::MethodBuilder> methods;
 
-  assets->GetAssetsProvider()->ForEachFile("res/", [&](const android::StringPiece& s,
-                                                       android::FileType) {
-    if (s == "layout") {
-      auto path = StringPrintf("res/%s/", s.to_string().c_str());
-      assets->GetAssetsProvider()->ForEachFile(path, [&](const android::StringPiece& layout_file,
-                                                         android::FileType) {
-        auto layout_path = StringPrintf("%s%s", path.c_str(), layout_file.to_string().c_str());
-        android::ApkAssetsCookie cookie = android::kInvalidCookie;
-        auto asset = resources.OpenNonAsset(layout_path, android::Asset::ACCESS_RANDOM, &cookie);
-        CHECK(asset);
-        CHECK(android::kInvalidCookie != cookie);
-        const auto dynamic_ref_table = resources.GetDynamicRefTableForCookie(cookie);
-        CHECK(nullptr != dynamic_ref_table);
-        android::ResXMLTree xml_tree{dynamic_ref_table};
-        xml_tree.setTo(asset->getBuffer(/*wordAligned=*/true),
-                       asset->getLength(),
-                       /*copy_data=*/true);
-        android::ResXMLParser parser{xml_tree};
-        parser.restart();
-        if (CanCompileLayout(&parser)) {
-          parser.restart();
-          const std::string layout_name = startop::util::FindLayoutNameFromFilename(layout_path);
-          ResXmlVisitorAdapter adapter{&parser};
-          switch (target) {
-            case CompilationTarget::kDex: {
-              methods.push_back(compiled_view.CreateMethod(
-                  layout_name,
-                  dex::Prototype{dex::TypeDescriptor::FromClassname("android.view.View"),
-                                 dex::TypeDescriptor::FromClassname("android.content.Context"),
-                                 dex::TypeDescriptor::Int()}));
-              DexViewBuilder builder(&methods.back());
-              builder.Start();
-              LayoutCompilerVisitor visitor{&builder};
-              adapter.Accept(&visitor);
-              builder.Finish();
-              methods.back().Encode();
-              break;
-            }
-            case CompilationTarget::kJavaLanguage: {
-              JavaLangViewBuilder builder{package_name, layout_name, target_out};
-              builder.Start();
-              LayoutCompilerVisitor visitor{&builder};
-              adapter.Accept(&visitor);
-              builder.Finish();
-              break;
-            }
-          }
-        }
-      });
-    }
+  assets->GetAssetsProvider()->ForEachFile("res/", [&](android::StringPiece s, android::FileType) {
+      if (s == "layout") {
+          auto path = StringPrintf("res/%.*s/", (int)s.size(), s.data());
+          assets->GetAssetsProvider()
+                  ->ForEachFile(path, [&](android::StringPiece layout_file, android::FileType) {
+                      auto layout_path = StringPrintf("%s%.*s", path.c_str(),
+                                                      (int)layout_file.size(), layout_file.data());
+                      android::ApkAssetsCookie cookie = android::kInvalidCookie;
+                      auto asset = resources.OpenNonAsset(layout_path,
+                                                          android::Asset::ACCESS_RANDOM, &cookie);
+                      CHECK(asset);
+                      CHECK(android::kInvalidCookie != cookie);
+                      const auto dynamic_ref_table = resources.GetDynamicRefTableForCookie(cookie);
+                      CHECK(nullptr != dynamic_ref_table);
+                      android::ResXMLTree xml_tree{dynamic_ref_table};
+                      xml_tree.setTo(asset->getBuffer(/*wordAligned=*/true), asset->getLength(),
+                                     /*copy_data=*/true);
+                      android::ResXMLParser parser{xml_tree};
+                      parser.restart();
+                      if (CanCompileLayout(&parser)) {
+                          parser.restart();
+                          const std::string layout_name =
+                                  startop::util::FindLayoutNameFromFilename(layout_path);
+                          ResXmlVisitorAdapter adapter{&parser};
+                          switch (target) {
+                              case CompilationTarget::kDex: {
+                                  methods.push_back(compiled_view.CreateMethod(
+                                          layout_name,
+                                          dex::Prototype{dex::TypeDescriptor::FromClassname(
+                                                                 "android.view.View"),
+                                                         dex::TypeDescriptor::FromClassname(
+                                                                 "android.content.Context"),
+                                                         dex::TypeDescriptor::Int()}));
+                                  DexViewBuilder builder(&methods.back());
+                                  builder.Start();
+                                  LayoutCompilerVisitor visitor{&builder};
+                                  adapter.Accept(&visitor);
+                                  builder.Finish();
+                                  methods.back().Encode();
+                                  break;
+                              }
+                              case CompilationTarget::kJavaLanguage: {
+                                  JavaLangViewBuilder builder{package_name, layout_name,
+                                                              target_out};
+                                  builder.Start();
+                                  LayoutCompilerVisitor visitor{&builder};
+                                  adapter.Accept(&visitor);
+                                  builder.Finish();
+                                  break;
+                              }
+                          }
+                      }
+                  });
+      }
   });
 
   if (target == CompilationTarget::kDex) {
diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java
index 86b98f1..2435243 100644
--- a/telephony/java/android/telephony/Annotation.java
+++ b/telephony/java/android/telephony/Annotation.java
@@ -5,6 +5,7 @@
 import android.net.NetworkCapabilities;
 import android.telecom.Connection;
 import android.telephony.data.ApnSetting;
+import android.telephony.ims.ImsCallProfile;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -494,7 +495,7 @@
             PreciseCallState.PRECISE_CALL_STATE_HOLDING,
             PreciseCallState.PRECISE_CALL_STATE_DIALING,
             PreciseCallState.PRECISE_CALL_STATE_ALERTING,
-            PreciseCallState. PRECISE_CALL_STATE_INCOMING,
+            PreciseCallState.PRECISE_CALL_STATE_INCOMING,
             PreciseCallState.PRECISE_CALL_STATE_WAITING,
             PreciseCallState.PRECISE_CALL_STATE_DISCONNECTED,
             PreciseCallState.PRECISE_CALL_STATE_DISCONNECTING})
@@ -727,6 +728,36 @@
     })
     public @interface ValidationStatus {}
 
+    /**
+     * IMS call Service types
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "SERVICE_TYPE_" }, value = {
+            ImsCallProfile.SERVICE_TYPE_NONE,
+            ImsCallProfile.SERVICE_TYPE_NORMAL,
+            ImsCallProfile.SERVICE_TYPE_EMERGENCY,
+    })
+    public @interface ImsCallServiceType {}
+
+    /**
+     * IMS call types
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "CALL_TYPE_" }, value = {
+            ImsCallProfile.CALL_TYPE_NONE,
+            ImsCallProfile.CALL_TYPE_VOICE_N_VIDEO,
+            ImsCallProfile.CALL_TYPE_VOICE,
+            ImsCallProfile.CALL_TYPE_VIDEO_N_VOICE,
+            ImsCallProfile.CALL_TYPE_VT,
+            ImsCallProfile.CALL_TYPE_VT_TX,
+            ImsCallProfile.CALL_TYPE_VT_RX,
+            ImsCallProfile.CALL_TYPE_VT_NODIR,
+            ImsCallProfile.CALL_TYPE_VS,
+            ImsCallProfile.CALL_TYPE_VS_TX,
+            ImsCallProfile.CALL_TYPE_VS_RX,
+    })
+    public @interface ImsCallType {}
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = { "NET_CAPABILITY_ENTERPRISE_SUB_LEVEL" }, value = {
diff --git a/telephony/java/android/telephony/CallAttributes.java b/telephony/java/android/telephony/CallAttributes.java
index b7bef39..1dc64a9 100644
--- a/telephony/java/android/telephony/CallAttributes.java
+++ b/telephony/java/android/telephony/CallAttributes.java
@@ -29,8 +29,10 @@
  * Contains information about a call's attributes as passed up from the HAL. If there are multiple
  * ongoing calls, the CallAttributes will pertain to the call in the foreground.
  * @hide
+ * @deprecated use {@link CallState} for call information for each call.
  */
 @SystemApi
+@Deprecated
 public final class CallAttributes implements Parcelable {
     private PreciseCallState mPreciseCallState;
     @NetworkType
diff --git a/telephony/java/android/telephony/CallState.aidl b/telephony/java/android/telephony/CallState.aidl
new file mode 100644
index 0000000..dd5af8e
--- /dev/null
+++ b/telephony/java/android/telephony/CallState.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2018 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.telephony;
+
+parcelable CallState;
+
diff --git a/telephony/java/android/telephony/CallState.java b/telephony/java/android/telephony/CallState.java
new file mode 100644
index 0000000..51ecfb0
--- /dev/null
+++ b/telephony/java/android/telephony/CallState.java
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2022 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.telephony;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.Annotation.ImsCallServiceType;
+import android.telephony.Annotation.ImsCallType;
+import android.telephony.Annotation.NetworkType;
+import android.telephony.Annotation.PreciseCallStates;
+import android.telephony.ims.ImsCallProfile;
+import android.telephony.ims.ImsCallSession;
+
+import java.util.Objects;
+
+/**
+ * Contains information about various states for a call.
+ * @hide
+ */
+@SystemApi
+public final class CallState implements Parcelable {
+
+    /**
+     * Call classifications are just used for backward compatibility of deprecated API {@link
+     * TelephonyCallback#CallAttributesListener#onCallAttributesChanged}, Since these will be
+     * removed when the deprecated API is removed, they should not be opened.
+     */
+    /**
+     * Call classification is not valid. It should not be opened.
+     * @hide
+     */
+    public static final int CALL_CLASSIFICATION_UNKNOWN = -1;
+
+    /**
+     * Call classification indicating foreground call
+     * @hide
+     */
+    public static final int CALL_CLASSIFICATION_RINGING = 0;
+
+    /**
+     * Call classification indicating background call
+     * @hide
+     */
+    public static final int CALL_CLASSIFICATION_FOREGROUND = 1;
+
+    /**
+     * Call classification indicating ringing call
+     * @hide
+     */
+    public static final int CALL_CLASSIFICATION_BACKGROUND = 2;
+
+    /**
+     * Call classification Max value.
+     * @hide
+     */
+    public static final int CALL_CLASSIFICATION_MAX = CALL_CLASSIFICATION_BACKGROUND + 1;
+
+    @PreciseCallStates
+    private final int mPreciseCallState;
+
+    @NetworkType
+    private final int mNetworkType; // TelephonyManager.NETWORK_TYPE_* ints
+    private final CallQuality mCallQuality;
+
+    private final int mCallClassification;
+    /**
+     * IMS call session ID. {@link ImsCallSession#getCallId()}
+     */
+    @Nullable
+    private String mImsCallId;
+
+    /**
+     * IMS call service type of this call
+     */
+    @ImsCallServiceType
+    private int mImsCallServiceType;
+
+    /**
+     * IMS call type of this call.
+     */
+    @ImsCallType
+    private int mImsCallType;
+
+    /**
+     * Constructor of CallAttributes
+     *
+     * @param callState call state defined in {@link PreciseCallState}
+     * @param networkType network type for this call attributes
+     * @param callQuality call quality for this call attributes, only CallState in
+     *                    {@link PreciseCallState#PRECISE_CALL_STATE_ACTIVE} will have valid call
+     *                    quality.
+     * @param callClassification call classification
+     * @param imsCallId IMS call session ID for this call attributes
+     * @param imsCallServiceType IMS call service type for this call attributes
+     * @param imsCallType IMS call type for this call attributes
+     */
+    private CallState(@PreciseCallStates int callState, @NetworkType int networkType,
+            @NonNull CallQuality callQuality, int callClassification, @Nullable String imsCallId,
+            @ImsCallServiceType int imsCallServiceType, @ImsCallType int imsCallType) {
+        this.mPreciseCallState = callState;
+        this.mNetworkType = networkType;
+        this.mCallQuality = callQuality;
+        this.mCallClassification = callClassification;
+        this.mImsCallId = imsCallId;
+        this.mImsCallServiceType = imsCallServiceType;
+        this.mImsCallType = imsCallType;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "mPreciseCallState=" + mPreciseCallState + " mNetworkType=" + mNetworkType
+                + " mCallQuality=" + mCallQuality + " mCallClassification" + mCallClassification
+                + " mImsCallId=" + mImsCallId + " mImsCallServiceType=" + mImsCallServiceType
+                + " mImsCallType=" + mImsCallType;
+    }
+
+    private CallState(Parcel in) {
+        this.mPreciseCallState = in.readInt();
+        this.mNetworkType = in.readInt();
+        this.mCallQuality = in.readParcelable(
+                CallQuality.class.getClassLoader(), CallQuality.class);
+        this.mCallClassification = in.readInt();
+        this.mImsCallId = in.readString();
+        this.mImsCallServiceType = in.readInt();
+        this.mImsCallType = in.readInt();
+    }
+
+    // getters
+    /**
+     * Returns the precise call state of the call.
+     */
+    @PreciseCallStates
+    public int getCallState() {
+        return mPreciseCallState;
+    }
+
+    /**
+     * Returns the {@link TelephonyManager#NetworkType} of the call.
+     *
+     * @see TelephonyManager#NETWORK_TYPE_UNKNOWN
+     * @see TelephonyManager#NETWORK_TYPE_GPRS
+     * @see TelephonyManager#NETWORK_TYPE_EDGE
+     * @see TelephonyManager#NETWORK_TYPE_UMTS
+     * @see TelephonyManager#NETWORK_TYPE_CDMA
+     * @see TelephonyManager#NETWORK_TYPE_EVDO_0
+     * @see TelephonyManager#NETWORK_TYPE_EVDO_A
+     * @see TelephonyManager#NETWORK_TYPE_1xRTT
+     * @see TelephonyManager#NETWORK_TYPE_HSDPA
+     * @see TelephonyManager#NETWORK_TYPE_HSUPA
+     * @see TelephonyManager#NETWORK_TYPE_HSPA
+     * @see TelephonyManager#NETWORK_TYPE_IDEN
+     * @see TelephonyManager#NETWORK_TYPE_EVDO_B
+     * @see TelephonyManager#NETWORK_TYPE_LTE
+     * @see TelephonyManager#NETWORK_TYPE_EHRPD
+     * @see TelephonyManager#NETWORK_TYPE_HSPAP
+     * @see TelephonyManager#NETWORK_TYPE_GSM
+     * @see TelephonyManager#NETWORK_TYPE_TD_SCDMA
+     * @see TelephonyManager#NETWORK_TYPE_IWLAN
+     * @see TelephonyManager#NETWORK_TYPE_LTE_CA
+     * @see TelephonyManager#NETWORK_TYPE_NR
+     */
+    @NetworkType
+    public int getNetworkType() {
+        return mNetworkType;
+    }
+
+    /**
+     * Returns the {#link CallQuality} of the call.
+     * @return call quality for this call attributes, only CallState in {@link
+     *         PreciseCallState#PRECISE_CALL_STATE_ACTIVE} will have valid call quality. It will be
+     *         null for the call which is not in {@link PreciseCallState#PRECISE_CALL_STATE_ACTIVE}.
+     */
+    @Nullable
+    public CallQuality getCallQuality() {
+        return mCallQuality;
+    }
+
+    /**
+     * Returns the call classification.
+     * @hide
+     */
+    public int getCallClassification() {
+        return mCallClassification;
+    }
+
+    /**
+     * Returns the IMS call session ID.
+     */
+    @Nullable
+    public String getImsCallSessionId() {
+        return mImsCallId;
+    }
+
+    /**
+     * Returns the IMS call service type.
+     */
+    @ImsCallServiceType
+    public int getImsCallServiceType() {
+        return mImsCallServiceType;
+    }
+
+    /**
+     * Returns the IMS call type.
+     */
+    @ImsCallType
+    public int getImsCallType() {
+        return mImsCallType;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mPreciseCallState, mNetworkType, mCallQuality, mCallClassification,
+                mImsCallId, mImsCallServiceType, mImsCallType);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (o == null || !(o instanceof CallState) || hashCode() != o.hashCode()) {
+            return false;
+        }
+
+        if (this == o) {
+            return true;
+        }
+
+        CallState s = (CallState) o;
+
+        return (mPreciseCallState == s.mPreciseCallState
+                && mNetworkType == s.mNetworkType
+                && Objects.equals(mCallQuality, s.mCallQuality)
+                && mCallClassification == s.mCallClassification
+                && Objects.equals(mImsCallId, s.mImsCallId)
+                && mImsCallType == s.mImsCallType
+                && mImsCallServiceType == s.mImsCallServiceType);
+    }
+
+    /**
+     * {@link Parcelable#describeContents}
+     */
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * {@link Parcelable#writeToParcel}
+     */
+    public void writeToParcel(@Nullable Parcel dest, int flags) {
+        dest.writeInt(mPreciseCallState);
+        dest.writeInt(mNetworkType);
+        dest.writeParcelable(mCallQuality, flags);
+        dest.writeInt(mCallClassification);
+        dest.writeString(mImsCallId);
+        dest.writeInt(mImsCallServiceType);
+        dest.writeInt(mImsCallType);
+    }
+
+    public static final @NonNull Creator<CallState> CREATOR = new Creator() {
+        public CallState createFromParcel(Parcel in) {
+            return new CallState(in);
+        }
+
+        public CallState[] newArray(int size) {
+            return new CallState[size];
+        }
+    };
+
+    /**
+     * Builder of {@link CallState}
+     *
+     * <p>The example below shows how you might create a new {@code CallState}:
+     *
+     * <pre><code>
+     *
+     * CallState = new CallState.Builder()
+     *     .setCallState(3)
+     *     .setNetworkType({@link TelephonyManager#NETWORK_TYPE_LTE})
+     *     .setCallQuality({@link CallQuality})
+     *     .setImsCallSessionId({@link String})
+     *     .setImsCallServiceType({@link ImsCallProfile#SERVICE_TYPE_NORMAL})
+     *     .setImsCallType({@link ImsCallProfile#CALL_TYPE_VOICE})
+     *     .build();
+     * </code></pre>
+     */
+    public static final class Builder {
+        private @PreciseCallStates int mPreciseCallState;
+        private @NetworkType int mNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+        private CallQuality mCallQuality = null;
+        private int mCallClassification = CALL_CLASSIFICATION_UNKNOWN;
+        private String mImsCallId;
+        private @ImsCallServiceType int mImsCallServiceType = ImsCallProfile.SERVICE_TYPE_NONE;
+        private @ImsCallType int mImsCallType = ImsCallProfile.CALL_TYPE_NONE;
+
+
+        /**
+         * Default constructor for the Builder.
+         */
+        public Builder(@PreciseCallStates int preciseCallState) {
+            mPreciseCallState = preciseCallState;
+        }
+
+        /**
+         * Set network type of this call.
+         *
+         * @param networkType the transport type.
+         * @return The same instance of the builder.
+         */
+        @NonNull
+        public CallState.Builder setNetworkType(@NetworkType int networkType) {
+            this.mNetworkType = networkType;
+            return this;
+        }
+
+        /**
+         * Set the call quality {@link CallQuality} of this call.
+         *
+         * @param callQuality call quality of active call.
+         * @return The same instance of the builder.
+         */
+        @NonNull
+        public CallState.Builder setCallQuality(@Nullable CallQuality callQuality) {
+            this.mCallQuality = callQuality;
+            return this;
+        }
+
+        /**
+         * Set call classification for this call.
+         *
+         * @param classification call classification type defined in this class.
+         * @return The same instance of the builder.
+         * @hide
+         */
+        @NonNull
+        public CallState.Builder setCallClassification(int classification) {
+            this.mCallClassification = classification;
+            return this;
+        }
+
+        /**
+         * Set IMS call session ID of this call.
+         *
+         * @param imsCallId  IMS call session ID.
+         * @return The same instance of the builder.
+         */
+        @NonNull
+        public CallState.Builder setImsCallSessionId(@Nullable String imsCallId) {
+            this.mImsCallId = imsCallId;
+            return this;
+        }
+
+        /**
+         * Set IMS call service type of this call.
+         *
+         * @param serviceType IMS call service type defined in {@link ImsCallProfile}.
+         * @return The same instance of the builder.
+         */
+        @NonNull
+        public CallState.Builder setImsCallServiceType(@ImsCallServiceType int serviceType) {
+            this.mImsCallServiceType = serviceType;
+            return this;
+        }
+
+        /**
+         * Set IMS call type of this call.
+         *
+         * @param callType IMS call type defined in {@link ImsCallProfile}.
+         * @return The same instance of the builder.
+         */
+        @NonNull
+        public CallState.Builder setImsCallType(@ImsCallType int callType) {
+            this.mImsCallType = callType;
+            return this;
+        }
+
+        /**
+         * Build the {@link CallState}
+         *
+         * @return the {@link CallState} object
+         */
+        @NonNull
+        public CallState build() {
+            return new CallState(
+                    mPreciseCallState,
+                    mNetworkType,
+                    mCallQuality,
+                    mCallClassification,
+                    mImsCallId,
+                    mImsCallServiceType,
+                    mImsCallType);
+        }
+    }
+}
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index e6d7df3..1ea7fdc 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -78,8 +78,9 @@
     public static final int SERVICE_TYPE_EMERGENCY = 2;
 
     /**
-     * Call types
+     * Call type none
      */
+    public static final int CALL_TYPE_NONE = 0;
     /**
      * IMSPhone to support IR.92 & IR.94 (voice + video upgrade/downgrade)
      */
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 0c14dba..a1257e3 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -546,6 +546,8 @@
     int RIL_REQUEST_UPDATE_IMS_CALL_STATUS = 240;
     int RIL_REQUEST_SET_N1_MODE_ENABLED = 241;
     int RIL_REQUEST_IS_N1_MODE_ENABLED = 242;
+    int RIL_REQUEST_SET_LOCATION_PRIVACY_SETTING = 243;
+    int RIL_REQUEST_GET_LOCATION_PRIVACY_SETTING = 244;
 
     /* Responses begin */
     int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
@@ -620,4 +622,5 @@
     int RIL_UNSOL_TRIGGER_IMS_DEREGISTRATION = 1107;
     int RIL_UNSOL_CONNECTION_SETUP_FAILURE = 1108;
     int RIL_UNSOL_NOTIFY_ANBR = 1109;
+    int RIL_UNSOL_ON_NETWORK_INITIATED_LOCATION_RESULT = 1110;
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index 8a1e1fa..3f6a75d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -172,4 +172,17 @@
     open fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
         testSpec.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() }
     }
+
+    open fun cujCompleted() {
+        entireScreenCovered()
+        navBarLayerIsVisibleAtStartAndEnd()
+        navBarWindowIsAlwaysVisible()
+        taskBarLayerIsVisibleAtStartAndEnd()
+        taskBarWindowIsAlwaysVisible()
+        statusBarLayerIsVisibleAtStartAndEnd()
+        statusBarLayerPositionAtStartAndEnd()
+        statusBarWindowIsAlwaysVisible()
+        visibleLayersShownMoreThanOneConsecutiveEntry()
+        visibleWindowsShownMoreThanOneConsecutiveEntry()
+    }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/TEST_MAPPING b/tests/FlickerTests/src/com/android/server/wm/flicker/TEST_MAPPING
new file mode 100644
index 0000000..945de33
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+  "ironwood-postsubmit": [
+    {
+      "name": "FlickerTests",
+      "options": [
+        {
+          "include-annotation": "android.platform.test.annotations.IwTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
index b9c875a..ef42766 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
@@ -17,6 +17,7 @@
 package com.android.server.wm.flicker.ime
 
 import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
@@ -101,6 +102,16 @@
         testSpec.assertWm { this.isAppWindowOnTop(testApp) }
     }
 
+    @Test
+    @IwTest(focusArea = "ime")
+    override fun cujCompleted() {
+        super.cujCompleted()
+        navBarLayerPositionAtStartAndEnd()
+        imeLayerBecomesInvisible()
+        imeAppLayerIsAlwaysVisible()
+        imeAppWindowIsAlwaysVisible()
+    }
+
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index 1dc3ca5..c92fce3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.server.wm.flicker.ime
 
+import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import android.view.WindowManagerPolicyConstants
@@ -100,6 +101,17 @@
         testSpec.assertLayers { this.isVisible(testApp).then().isInvisible(testApp) }
     }
 
+    @Test
+    @IwTest(focusArea = "ime")
+    override fun cujCompleted() {
+        super.cujCompleted()
+        navBarLayerPositionAtStartAndEnd()
+        imeLayerBecomesInvisible()
+        imeAppWindowBecomesInvisible()
+        imeWindowBecomesInvisible()
+        imeLayerBecomesInvisible()
+    }
+
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
index a6bd791..7d7953b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
@@ -17,6 +17,7 @@
 package com.android.server.wm.flicker.ime
 
 import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import android.view.WindowManagerPolicyConstants
@@ -79,6 +80,14 @@
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
     }
 
+    @Test
+    @IwTest(focusArea = "ime")
+    override fun cujCompleted() {
+        super.cujCompleted()
+        imeLayerBecomesInvisible()
+        imeWindowBecomesInvisible()
+    }
+
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
index b43efea..9919d87 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.server.wm.flicker.ime
 
+import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import android.view.WindowManagerPolicyConstants
@@ -50,6 +51,15 @@
         }
     }
 
+    @Test
+    @IwTest(focusArea = "ime")
+    override fun cujCompleted() {
+        super.cujCompleted()
+        imeWindowBecomesVisible()
+        appWindowAlwaysVisibleOnTop()
+        layerAlwaysVisible()
+    }
+
     @Presubmit @Test fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible()
 
     @Presubmit
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index 1973ec0..ad14d0d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -17,6 +17,7 @@
 package com.android.server.wm.flicker.rotation
 
 import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -125,6 +126,14 @@
     @Test
     override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
+    @Test
+    @IwTest(focusArea = "ime")
+    override fun cujCompleted() {
+        super.cujCompleted()
+        focusChanges()
+        rotationLayerAppearsAndVanishes()
+    }
+
     companion object {
         /**
          * Creates the test configurations.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
index 4faeb24..8e3fd40 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
@@ -73,4 +73,10 @@
             }
         }
     }
+
+    override fun cujCompleted() {
+        super.cujCompleted()
+        appLayerRotates_StartingPos()
+        appLayerRotates_EndingPos()
+    }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index a08db29..d0d4122 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -17,6 +17,7 @@
 package com.android.server.wm.flicker.rotation
 
 import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.RequiresDevice
 import android.view.WindowManager
@@ -204,6 +205,31 @@
     @Test
     override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
+    @Test
+    @IwTest(focusArea = "ime")
+    override fun cujCompleted() {
+        if (!testSpec.isTablet) {
+            // not yet tablet compatible
+            appLayerRotates()
+            appLayerAlwaysVisible()
+        }
+
+        appWindowFullScreen()
+        appWindowSeamlessRotation()
+        focusDoesNotChange()
+        statusBarLayerIsAlwaysInvisible()
+        statusBarWindowIsAlwaysInvisible()
+        appLayerRotates_StartingPos()
+        appLayerRotates_EndingPos()
+        entireScreenCovered()
+        navBarLayerIsVisibleAtStartAndEnd()
+        navBarWindowIsAlwaysVisible()
+        taskBarLayerIsVisibleAtStartAndEnd()
+        taskBarWindowIsAlwaysVisible()
+        visibleLayersShownMoreThanOneConsecutiveEntry()
+        visibleWindowsShownMoreThanOneConsecutiveEntry()
+    }
+
     companion object {
         private val FlickerTestParameter.starveUiThread
             get() =
diff --git a/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java b/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java
index d133f6f..e2099e6 100644
--- a/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java
+++ b/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java
@@ -24,12 +24,15 @@
 
 import android.content.Context;
 import android.hardware.usb.UsbManager;
+import android.os.Binder;
 import android.os.RemoteException;
 import android.util.Log;
 
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.concurrent.atomic.AtomicInteger;
+
 /**
  * Unit tests lib for {@link android.hardware.usb.UsbManager}.
  */
@@ -42,6 +45,11 @@
     private UsbManager mUsbManagerMock;
     @Mock private android.hardware.usb.IUsbManager mMockUsbService;
 
+    /**
+     * Counter for tracking UsbOperation operations.
+     */
+    private static final AtomicInteger sUsbOperationCount = new AtomicInteger();
+
     public UsbManagerTestLib(Context context) {
         MockitoAnnotations.initMocks(this);
         mContext = context;
@@ -82,10 +90,11 @@
     }
 
     private void testSetCurrentFunctionsMock_Matched(long functions) {
+        int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
         try {
             setCurrentFunctions(functions);
 
-            verify(mMockUsbService).setCurrentFunctions(eq(functions));
+            verify(mMockUsbService).setCurrentFunctions(eq(functions), operationId);
         } catch (RemoteException remEx) {
             Log.w(TAG, "RemoteException");
         }
@@ -106,9 +115,10 @@
     }
 
     public void testSetCurrentFunctionsEx(long functions) throws Exception {
+        int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
         setCurrentFunctions(functions);
 
-        verify(mMockUsbService).setCurrentFunctions(eq(functions));
+        verify(mMockUsbService).setCurrentFunctions(eq(functions), operationId);
     }
 
     public void testGetCurrentFunctions_shouldMatched() {
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
index 86bcb72..4103ca7 100644
--- a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
+++ b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
@@ -98,7 +98,7 @@
         }
 
         @Override
-        protected void setEnabledFunctions(long functions, boolean force) {
+        protected void setEnabledFunctions(long functions, boolean force, int operationId) {
             mCurrentFunctions = functions;
         }
 
@@ -134,6 +134,20 @@
         protected void sendStickyBroadcast(Intent intent) {
             mBroadcastedIntent = intent;
         }
+
+        @Override
+        public void handlerInitDone(int operationId) {
+        }
+
+        @Override
+        public void setCurrentUsbFunctionsCb(long functions,
+                    int status, int mRequest, long mFunctions, boolean mChargingFunctions){
+        }
+
+        @Override
+        public void getUsbSpeedCb(int speed){
+        }
+
     }
 
     @Before
diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp
index 9b9cde2..6b1fd9f 100644
--- a/tools/aapt2/LoadedApk.cpp
+++ b/tools/aapt2/LoadedApk.cpp
@@ -72,7 +72,7 @@
   }
 }
 
-std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(const StringPiece& path,
+std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(StringPiece path,
                                                       android::IDiagnostics* diag) {
   android::Source source(path);
   std::string error;
diff --git a/tools/aapt2/LoadedApk.h b/tools/aapt2/LoadedApk.h
index a4aff3f..4cd7eae 100644
--- a/tools/aapt2/LoadedApk.h
+++ b/tools/aapt2/LoadedApk.h
@@ -45,7 +45,7 @@
   virtual ~LoadedApk() = default;
 
   // Loads both binary and proto APKs from disk.
-  static std::unique_ptr<LoadedApk> LoadApkFromPath(const ::android::StringPiece& path,
+  static std::unique_ptr<LoadedApk> LoadApkFromPath(android::StringPiece path,
                                                     android::IDiagnostics* diag);
 
   // Loads a proto APK from the given file collection.
diff --git a/tools/aapt2/NameMangler.h b/tools/aapt2/NameMangler.h
index 0b49052..0b08c32 100644
--- a/tools/aapt2/NameMangler.h
+++ b/tools/aapt2/NameMangler.h
@@ -36,7 +36,7 @@
    * We must know which references to mangle, and which to keep (android vs.
    * com.android.support).
    */
-  std::set<std::string> packages_to_mangle;
+  std::set<std::string, std::less<>> packages_to_mangle;
 };
 
 class NameMangler {
@@ -54,7 +54,7 @@
                         mangled_entry_name);
   }
 
-  bool ShouldMangle(const std::string& package) const {
+  bool ShouldMangle(std::string_view package) const {
     if (package.empty() || policy_.target_package_name == package) {
       return false;
     }
@@ -68,8 +68,8 @@
    * The mangled name should contain symbols that are illegal to define in XML,
    * so that there will never be name mangling collisions.
    */
-  static std::string MangleEntry(const std::string& package, const std::string& name) {
-    return package + "$" + name;
+  static std::string MangleEntry(std::string_view package, std::string_view name) {
+    return (std::string(package) += '$') += name;
   }
 
   /**
diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp
index df8c3b9..cfcb2bb 100644
--- a/tools/aapt2/Resource.cpp
+++ b/tools/aapt2/Resource.cpp
@@ -138,11 +138,11 @@
   return {to_string(t), t};
 }
 
-std::optional<ResourceNamedTypeRef> ParseResourceNamedType(const android::StringPiece& s) {
+std::optional<ResourceNamedTypeRef> ParseResourceNamedType(android::StringPiece s) {
   auto dot = std::find(s.begin(), s.end(), '.');
   const ResourceType* parsedType;
   if (dot != s.end() && dot != std::prev(s.end())) {
-    parsedType = ParseResourceType(s.substr(s.begin(), dot));
+    parsedType = ParseResourceType(android::StringPiece(s.begin(), dot - s.begin()));
   } else {
     parsedType = ParseResourceType(s);
   }
@@ -152,7 +152,7 @@
   return ResourceNamedTypeRef(s, *parsedType);
 }
 
-const ResourceType* ParseResourceType(const StringPiece& str) {
+const ResourceType* ParseResourceType(StringPiece str) {
   auto iter = sResourceTypeMap.find(str);
   if (iter == std::end(sResourceTypeMap)) {
     return nullptr;
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index 9cfaf47..7ba3277 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -74,7 +74,7 @@
 /**
  * Returns a pointer to a valid ResourceType, or nullptr if the string was invalid.
  */
-const ResourceType* ParseResourceType(const android::StringPiece& str);
+const ResourceType* ParseResourceType(android::StringPiece str);
 
 /**
  * Pair of type name as in ResourceTable and actual resource type.
@@ -87,7 +87,7 @@
   ResourceType type = ResourceType::kRaw;
 
   ResourceNamedType() = default;
-  ResourceNamedType(const android::StringPiece& n, ResourceType t);
+  ResourceNamedType(android::StringPiece n, ResourceType t);
 
   int compare(const ResourceNamedType& other) const;
 
@@ -108,19 +108,19 @@
   ResourceNamedTypeRef(const ResourceNamedTypeRef&) = default;
   ResourceNamedTypeRef(ResourceNamedTypeRef&&) = default;
   ResourceNamedTypeRef(const ResourceNamedType& rhs);  // NOLINT(google-explicit-constructor)
-  ResourceNamedTypeRef(const android::StringPiece& n, ResourceType t);
+  ResourceNamedTypeRef(android::StringPiece n, ResourceType t);
   ResourceNamedTypeRef& operator=(const ResourceNamedTypeRef& rhs) = default;
   ResourceNamedTypeRef& operator=(ResourceNamedTypeRef&& rhs) = default;
   ResourceNamedTypeRef& operator=(const ResourceNamedType& rhs);
 
   ResourceNamedType ToResourceNamedType() const;
 
-  std::string to_string() const;
+  std::string_view to_string() const;
 };
 
 ResourceNamedTypeRef ResourceNamedTypeWithDefaultName(ResourceType t);
 
-std::optional<ResourceNamedTypeRef> ParseResourceNamedType(const android::StringPiece& s);
+std::optional<ResourceNamedTypeRef> ParseResourceNamedType(android::StringPiece s);
 
 /**
  * A resource's name. This can uniquely identify
@@ -132,9 +132,8 @@
   std::string entry;
 
   ResourceName() = default;
-  ResourceName(const android::StringPiece& p, const ResourceNamedTypeRef& t,
-               const android::StringPiece& e);
-  ResourceName(const android::StringPiece& p, ResourceType t, const android::StringPiece& e);
+  ResourceName(android::StringPiece p, const ResourceNamedTypeRef& t, android::StringPiece e);
+  ResourceName(android::StringPiece p, ResourceType t, android::StringPiece e);
 
   int compare(const ResourceName& other) const;
 
@@ -157,9 +156,8 @@
   ResourceNameRef(const ResourceNameRef&) = default;
   ResourceNameRef(ResourceNameRef&&) = default;
   ResourceNameRef(const ResourceName& rhs);  // NOLINT(google-explicit-constructor)
-  ResourceNameRef(const android::StringPiece& p, const ResourceNamedTypeRef& t,
-                  const android::StringPiece& e);
-  ResourceNameRef(const android::StringPiece& p, ResourceType t, const android::StringPiece& e);
+  ResourceNameRef(android::StringPiece p, const ResourceNamedTypeRef& t, android::StringPiece e);
+  ResourceNameRef(android::StringPiece p, ResourceType t, android::StringPiece e);
   ResourceNameRef& operator=(const ResourceNameRef& rhs) = default;
   ResourceNameRef& operator=(ResourceNameRef&& rhs) = default;
   ResourceNameRef& operator=(const ResourceName& rhs);
@@ -346,8 +344,8 @@
 //
 // ResourceNamedType implementation.
 //
-inline ResourceNamedType::ResourceNamedType(const android::StringPiece& n, ResourceType t)
-    : name(n.to_string()), type(t) {
+inline ResourceNamedType::ResourceNamedType(android::StringPiece n, ResourceType t)
+    : name(n), type(t) {
 }
 
 inline int ResourceNamedType::compare(const ResourceNamedType& other) const {
@@ -380,7 +378,7 @@
 //
 // ResourceNamedTypeRef implementation.
 //
-inline ResourceNamedTypeRef::ResourceNamedTypeRef(const android::StringPiece& n, ResourceType t)
+inline ResourceNamedTypeRef::ResourceNamedTypeRef(android::StringPiece n, ResourceType t)
     : name(n), type(t) {
 }
 
@@ -398,8 +396,8 @@
   return ResourceNamedType(name, type);
 }
 
-inline std::string ResourceNamedTypeRef::to_string() const {
-  return name.to_string();
+inline std::string_view ResourceNamedTypeRef::to_string() const {
+  return name;
 }
 
 inline bool operator<(const ResourceNamedTypeRef& lhs, const ResourceNamedTypeRef& rhs) {
@@ -422,13 +420,12 @@
 // ResourceName implementation.
 //
 
-inline ResourceName::ResourceName(const android::StringPiece& p, const ResourceNamedTypeRef& t,
-                                  const android::StringPiece& e)
-    : package(p.to_string()), type(t.ToResourceNamedType()), entry(e.to_string()) {
+inline ResourceName::ResourceName(android::StringPiece p, const ResourceNamedTypeRef& t,
+                                  android::StringPiece e)
+    : package(p), type(t.ToResourceNamedType()), entry(e) {
 }
 
-inline ResourceName::ResourceName(const android::StringPiece& p, ResourceType t,
-                                  const android::StringPiece& e)
+inline ResourceName::ResourceName(android::StringPiece p, ResourceType t, android::StringPiece e)
     : ResourceName(p, ResourceNamedTypeWithDefaultName(t), e) {
 }
 
@@ -471,14 +468,13 @@
 inline ResourceNameRef::ResourceNameRef(const ResourceName& rhs)
     : package(rhs.package), type(rhs.type), entry(rhs.entry) {}
 
-inline ResourceNameRef::ResourceNameRef(const android::StringPiece& p,
-                                        const ResourceNamedTypeRef& t,
-                                        const android::StringPiece& e)
+inline ResourceNameRef::ResourceNameRef(android::StringPiece p, const ResourceNamedTypeRef& t,
+                                        android::StringPiece e)
     : package(p), type(t), entry(e) {
 }
 
-inline ResourceNameRef::ResourceNameRef(const android::StringPiece& p, ResourceType t,
-                                        const android::StringPiece& e)
+inline ResourceNameRef::ResourceNameRef(android::StringPiece p, ResourceType t,
+                                        android::StringPiece e)
     : ResourceNameRef(p, ResourceNamedTypeWithDefaultName(t), e) {
 }
 
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 19fd306..fa9a98f 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -50,11 +50,11 @@
 constexpr const char* sXliffNamespaceUri = "urn:oasis:names:tc:xliff:document:1.2";
 
 // Returns true if the element is <skip> or <eat-comment> and can be safely ignored.
-static bool ShouldIgnoreElement(const StringPiece& ns, const StringPiece& name) {
+static bool ShouldIgnoreElement(StringPiece ns, StringPiece name) {
   return ns.empty() && (name == "skip" || name == "eat-comment");
 }
 
-static uint32_t ParseFormatTypeNoEnumsOrFlags(const StringPiece& piece) {
+static uint32_t ParseFormatTypeNoEnumsOrFlags(StringPiece piece) {
   if (piece == "reference") {
     return android::ResTable_map::TYPE_REFERENCE;
   } else if (piece == "string") {
@@ -75,7 +75,7 @@
   return 0;
 }
 
-static uint32_t ParseFormatType(const StringPiece& piece) {
+static uint32_t ParseFormatType(StringPiece piece) {
   if (piece == "enum") {
     return android::ResTable_map::TYPE_ENUM;
   } else if (piece == "flags") {
@@ -84,9 +84,9 @@
   return ParseFormatTypeNoEnumsOrFlags(piece);
 }
 
-static uint32_t ParseFormatAttribute(const StringPiece& str) {
+static uint32_t ParseFormatAttribute(StringPiece str) {
   uint32_t mask = 0;
-  for (const StringPiece& part : util::Tokenize(str, '|')) {
+  for (StringPiece part : util::Tokenize(str, '|')) {
     StringPiece trimmed_part = util::TrimWhitespace(part);
     uint32_t type = ParseFormatType(trimmed_part);
     if (type == 0) {
@@ -122,7 +122,7 @@
   StringPiece trimmed_comment = util::TrimWhitespace(res->comment);
   if (trimmed_comment.size() != res->comment.size()) {
     // Only if there was a change do we re-assign.
-    res->comment = trimmed_comment.to_string();
+    res->comment = std::string(trimmed_comment);
   }
 
   NewResourceBuilder res_builder(res->name);
@@ -362,7 +362,7 @@
       // Trim leading whitespace.
       StringPiece trimmed = util::TrimLeadingWhitespace(first_segment->data);
       if (trimmed.size() != first_segment->data.size()) {
-        first_segment->data = trimmed.to_string();
+        first_segment->data = std::string(trimmed);
       }
     }
 
@@ -370,7 +370,7 @@
       // Trim trailing whitespace.
       StringPiece trimmed = util::TrimTrailingWhitespace(last_segment->data);
       if (trimmed.size() != last_segment->data.size()) {
-        last_segment->data = trimmed.to_string();
+        last_segment->data = std::string(trimmed);
       }
     }
   }
@@ -466,7 +466,7 @@
 
     // Extract the product name if it exists.
     if (std::optional<StringPiece> maybe_product = xml::FindNonEmptyAttribute(parser, "product")) {
-      parsed_resource.product = maybe_product.value().to_string();
+      parsed_resource.product = std::string(maybe_product.value());
     }
 
     // Parse the resource regardless of product.
@@ -559,7 +559,7 @@
 
     // Items have their type encoded in the type attribute.
     if (std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) {
-      resource_type = maybe_type.value().to_string();
+      resource_type = std::string(maybe_type.value());
     } else {
       diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
                    << "<item> must have a 'type' attribute");
@@ -582,7 +582,7 @@
 
     // Bags have their type encoded in the type attribute.
     if (std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) {
-      resource_type = maybe_type.value().to_string();
+      resource_type = std::string(maybe_type.value());
     } else {
       diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
                    << "<bag> must have a 'type' attribute");
@@ -603,7 +603,7 @@
 
     out_resource->name.type =
         ResourceNamedTypeWithDefaultName(ResourceType::kId).ToResourceNamedType();
-    out_resource->name.entry = maybe_name.value().to_string();
+    out_resource->name.entry = std::string(maybe_name.value());
 
     // Ids either represent a unique resource id or reference another resource id
     auto item = ParseItem(parser, out_resource, resource_format);
@@ -640,7 +640,7 @@
 
     out_resource->name.type =
         ResourceNamedTypeWithDefaultName(ResourceType::kMacro).ToResourceNamedType();
-    out_resource->name.entry = maybe_name.value().to_string();
+    out_resource->name.entry = std::string(maybe_name.value());
     return ParseMacro(parser, out_resource);
   }
 
@@ -657,7 +657,7 @@
 
       out_resource->name.type =
           ResourceNamedTypeWithDefaultName(item_iter->second.type).ToResourceNamedType();
-      out_resource->name.entry = maybe_name.value().to_string();
+      out_resource->name.entry = std::string(maybe_name.value());
 
       // Only use the implied format of the type when there is no explicit format.
       if (resource_format == 0u) {
@@ -684,7 +684,7 @@
           return false;
         }
 
-        out_resource->name.entry = maybe_name.value().to_string();
+        out_resource->name.entry = std::string(maybe_name.value());
       }
 
       // Call the associated parse method. The type will be filled in by the
@@ -708,7 +708,7 @@
       }
 
       out_resource->name.type = parsed_type->ToResourceNamedType();
-      out_resource->name.entry = maybe_name.value().to_string();
+      out_resource->name.entry = std::string(maybe_name.value());
       out_resource->value = ParseXml(parser, android::ResTable_map::TYPE_REFERENCE, kNoRawString);
       if (!out_resource->value) {
         diag_->Error(android::DiagMessage(out_resource->source)
@@ -1005,7 +1005,7 @@
   const size_t depth = parser->depth();
   while (xml::XmlPullParser::NextChildNode(parser, depth)) {
     if (parser->event() == xml::XmlPullParser::Event::kComment) {
-      comment = util::TrimWhitespace(parser->comment()).to_string();
+      comment = std::string(util::TrimWhitespace(parser->comment()));
       continue;
     } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
       // Skip text.
@@ -1045,7 +1045,7 @@
       }
 
       ParsedResource& entry_res = out_resource->child_resources.emplace_back(ParsedResource{
-          .name = ResourceName{{}, parsed_type, maybe_name.value().to_string()},
+          .name = ResourceName{{}, parsed_type, std::string(maybe_name.value())},
           .source = item_source,
           .comment = std::move(comment),
       });
@@ -1231,7 +1231,7 @@
 
       ParsedResource child_resource{};
       child_resource.name.type = type->ToResourceNamedType();
-      child_resource.name.entry = item_name.value().to_string();
+      child_resource.name.entry = std::string(item_name.value());
       child_resource.overlayable_item = overlayable_item;
       out_resource->child_resources.push_back(std::move(child_resource));
 
@@ -1246,7 +1246,7 @@
                      xml::FindNonEmptyAttribute(parser, "type")) {
         // Parse the polices separated by vertical bar characters to allow for specifying multiple
         // policies. Items within the policy tag will have the specified policy.
-        for (const StringPiece& part : util::Tokenize(maybe_type.value(), '|')) {
+        for (StringPiece part : util::Tokenize(maybe_type.value(), '|')) {
           StringPiece trimmed_part = util::TrimWhitespace(part);
           const auto policy = std::find_if(kPolicyStringToFlag.begin(),
                                            kPolicyStringToFlag.end(),
@@ -1377,7 +1377,7 @@
   const size_t depth = parser->depth();
   while (xml::XmlPullParser::NextChildNode(parser, depth)) {
     if (parser->event() == xml::XmlPullParser::Event::kComment) {
-      comment = util::TrimWhitespace(parser->comment()).to_string();
+      comment = std::string(util::TrimWhitespace(parser->comment()));
       continue;
     } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
       // Skip text.
@@ -1457,7 +1457,7 @@
 }
 
 std::optional<Attribute::Symbol> ResourceParser::ParseEnumOrFlagItem(xml::XmlPullParser* parser,
-                                                                     const StringPiece& tag) {
+                                                                     StringPiece tag) {
   const android::Source source = source_.WithLine(parser->line_number());
 
   std::optional<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
@@ -1764,7 +1764,7 @@
   const size_t depth = parser->depth();
   while (xml::XmlPullParser::NextChildNode(parser, depth)) {
     if (parser->event() == xml::XmlPullParser::Event::kComment) {
-      comment = util::TrimWhitespace(parser->comment()).to_string();
+      comment = std::string(util::TrimWhitespace(parser->comment()));
       continue;
     } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
       // Ignore text.
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 396ce97..012a056 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -122,7 +122,7 @@
   bool ParseAttr(xml::XmlPullParser* parser, ParsedResource* out_resource);
   bool ParseAttrImpl(xml::XmlPullParser* parser, ParsedResource* out_resource, bool weak);
   std::optional<Attribute::Symbol> ParseEnumOrFlagItem(xml::XmlPullParser* parser,
-                                                       const android::StringPiece& tag);
+                                                       android::StringPiece tag);
   bool ParseStyle(ResourceType type, xml::XmlPullParser* parser, ParsedResource* out_resource);
   bool ParseStyleItem(xml::XmlPullParser* parser, Style* style);
   bool ParseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* out_resource);
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index fe7eb96..b59b165 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -65,11 +65,11 @@
     context_ = test::ContextBuilder().Build();
   }
 
-  ::testing::AssertionResult TestParse(const StringPiece& str) {
+  ::testing::AssertionResult TestParse(StringPiece str) {
     return TestParse(str, ConfigDescription{});
   }
 
-  ::testing::AssertionResult TestParse(const StringPiece& str, const ConfigDescription& config) {
+  ::testing::AssertionResult TestParse(StringPiece str, const ConfigDescription& config) {
     ResourceParserOptions parserOptions;
     ResourceParser parser(context_->GetDiagnostics(), &table_, android::Source{"test"}, config,
                           parserOptions);
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index cb48114..a3b0b45 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -49,21 +49,21 @@
 }
 
 template <typename T>
-bool less_than_struct_with_name(const std::unique_ptr<T>& lhs, const StringPiece& rhs) {
+bool less_than_struct_with_name(const std::unique_ptr<T>& lhs, StringPiece rhs) {
   return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
 }
 
 template <typename T>
-bool greater_than_struct_with_name(const StringPiece& lhs, const std::unique_ptr<T>& rhs) {
+bool greater_than_struct_with_name(StringPiece lhs, const std::unique_ptr<T>& rhs) {
   return rhs->name.compare(0, rhs->name.size(), lhs.data(), lhs.size()) > 0;
 }
 
 template <typename T>
 struct NameEqualRange {
-  bool operator()(const std::unique_ptr<T>& lhs, const StringPiece& rhs) const {
+  bool operator()(const std::unique_ptr<T>& lhs, StringPiece rhs) const {
     return less_than_struct_with_name<T>(lhs, rhs);
   }
-  bool operator()(const StringPiece& lhs, const std::unique_ptr<T>& rhs) const {
+  bool operator()(StringPiece lhs, const std::unique_ptr<T>& rhs) const {
     return greater_than_struct_with_name<T>(lhs, rhs);
   }
 };
@@ -78,7 +78,7 @@
 }
 
 template <typename T, typename Func, typename Elements>
-T* FindElementsRunAction(const android::StringPiece& name, Elements& entries, Func action) {
+T* FindElementsRunAction(android::StringPiece name, Elements& entries, Func action) {
   const auto iter =
       std::lower_bound(entries.begin(), entries.end(), name, less_than_struct_with_name<T>);
   const bool found = iter != entries.end() && name == (*iter)->name;
@@ -87,7 +87,7 @@
 
 struct ConfigKey {
   const ConfigDescription* config;
-  const StringPiece& product;
+  StringPiece product;
 };
 
 template <typename T>
@@ -104,12 +104,12 @@
 ResourceTable::ResourceTable(ResourceTable::Validation validation) : validation_(validation) {
 }
 
-ResourceTablePackage* ResourceTable::FindPackage(const android::StringPiece& name) const {
+ResourceTablePackage* ResourceTable::FindPackage(android::StringPiece name) const {
   return FindElementsRunAction<ResourceTablePackage>(
       name, packages, [&](bool found, auto& iter) { return found ? iter->get() : nullptr; });
 }
 
-ResourceTablePackage* ResourceTable::FindOrCreatePackage(const android::StringPiece& name) {
+ResourceTablePackage* ResourceTable::FindOrCreatePackage(android::StringPiece name) {
   return FindElementsRunAction<ResourceTablePackage>(name, packages, [&](bool found, auto& iter) {
     return found ? iter->get() : packages.emplace(iter, new ResourceTablePackage(name))->get();
   });
@@ -139,18 +139,18 @@
   });
 }
 
-ResourceEntry* ResourceTableType::CreateEntry(const android::StringPiece& name) {
+ResourceEntry* ResourceTableType::CreateEntry(android::StringPiece name) {
   return FindElementsRunAction<ResourceEntry>(name, entries, [&](bool found, auto& iter) {
     return entries.emplace(iter, new ResourceEntry(name))->get();
   });
 }
 
-ResourceEntry* ResourceTableType::FindEntry(const android::StringPiece& name) const {
+ResourceEntry* ResourceTableType::FindEntry(android::StringPiece name) const {
   return FindElementsRunAction<ResourceEntry>(
       name, entries, [&](bool found, auto& iter) { return found ? iter->get() : nullptr; });
 }
 
-ResourceEntry* ResourceTableType::FindOrCreateEntry(const android::StringPiece& name) {
+ResourceEntry* ResourceTableType::FindOrCreateEntry(android::StringPiece name) {
   return FindElementsRunAction<ResourceEntry>(name, entries, [&](bool found, auto& iter) {
     return found ? iter->get() : entries.emplace(iter, new ResourceEntry(name))->get();
   });
@@ -183,7 +183,7 @@
 }
 
 ResourceConfigValue* ResourceEntry::FindOrCreateValue(const ConfigDescription& config,
-                                                      const StringPiece& product) {
+                                                      StringPiece product) {
   auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
                                lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>);
   if (iter != values.end()) {
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index f49ce81..bb286a8 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -71,12 +71,11 @@
 
 struct Overlayable {
   Overlayable() = default;
-   Overlayable(const android::StringPiece& name, const android::StringPiece& actor)
-       : name(name.to_string()), actor(actor.to_string()) {}
-   Overlayable(const android::StringPiece& name, const android::StringPiece& actor,
-               const android::Source& source)
-       : name(name.to_string()), actor(actor.to_string()), source(source) {
-   }
+  Overlayable(android::StringPiece name, android::StringPiece actor) : name(name), actor(actor) {
+  }
+  Overlayable(android::StringPiece name, android::StringPiece actor, const android::Source& source)
+      : name(name), actor(actor), source(source) {
+  }
 
   static const char* kActorScheme;
   std::string name;
@@ -105,8 +104,9 @@
   // The actual Value.
   std::unique_ptr<Value> value;
 
-  ResourceConfigValue(const android::ConfigDescription& config, const android::StringPiece& product)
-      : config(config), product(product.to_string()) {}
+  ResourceConfigValue(const android::ConfigDescription& config, android::StringPiece product)
+      : config(config), product(product) {
+  }
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ResourceConfigValue);
@@ -136,7 +136,8 @@
   // The resource's values for each configuration.
   std::vector<std::unique_ptr<ResourceConfigValue>> values;
 
-  explicit ResourceEntry(const android::StringPiece& name) : name(name.to_string()) {}
+  explicit ResourceEntry(android::StringPiece name) : name(name) {
+  }
 
   ResourceConfigValue* FindValue(const android::ConfigDescription& config,
                                  android::StringPiece product = {});
@@ -144,7 +145,7 @@
                                        android::StringPiece product = {}) const;
 
   ResourceConfigValue* FindOrCreateValue(const android::ConfigDescription& config,
-                                         const android::StringPiece& product);
+                                         android::StringPiece product);
   std::vector<ResourceConfigValue*> FindAllValues(const android::ConfigDescription& config);
 
   template <typename Func>
@@ -180,9 +181,9 @@
       : named_type(type.ToResourceNamedType()) {
   }
 
-  ResourceEntry* CreateEntry(const android::StringPiece& name);
-  ResourceEntry* FindEntry(const android::StringPiece& name) const;
-  ResourceEntry* FindOrCreateEntry(const android::StringPiece& name);
+  ResourceEntry* CreateEntry(android::StringPiece name);
+  ResourceEntry* FindEntry(android::StringPiece name) const;
+  ResourceEntry* FindOrCreateEntry(android::StringPiece name);
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ResourceTableType);
@@ -194,7 +195,7 @@
 
   std::vector<std::unique_ptr<ResourceTableType>> types;
 
-  explicit ResourceTablePackage(const android::StringPiece& name) : name(name.to_string()) {
+  explicit ResourceTablePackage(android::StringPiece name) : name(name) {
   }
 
   ResourceTablePackage() = default;
@@ -319,8 +320,8 @@
   // Returns the package struct with the given name, or nullptr if such a package does not
   // exist. The empty string is a valid package and typically is used to represent the
   // 'current' package before it is known to the ResourceTable.
-  ResourceTablePackage* FindPackage(const android::StringPiece& name) const;
-  ResourceTablePackage* FindOrCreatePackage(const android::StringPiece& name);
+  ResourceTablePackage* FindPackage(android::StringPiece name) const;
+  ResourceTablePackage* FindOrCreatePackage(android::StringPiece name);
 
   std::unique_ptr<ResourceTable> Clone() const;
 
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
index 0cf8473..54b98d1 100644
--- a/tools/aapt2/ResourceTable_test.cpp
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -187,7 +187,7 @@
 static ::testing::AssertionResult VisibilityOfResource(const ResourceTable& table,
                                                        const ResourceNameRef& name,
                                                        Visibility::Level level,
-                                                       const StringPiece& comment) {
+                                                       StringPiece comment) {
   std::optional<ResourceTable::SearchResult> result = table.FindResource(name);
   if (!result) {
     return ::testing::AssertionFailure() << "no resource '" << name << "' found in table";
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 41c7435..5a118a9 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -109,8 +109,7 @@
   return name_out;
 }
 
-bool ParseResourceName(const StringPiece& str, ResourceNameRef* out_ref,
-                       bool* out_private) {
+bool ParseResourceName(StringPiece str, ResourceNameRef* out_ref, bool* out_private) {
   if (str.empty()) {
     return false;
   }
@@ -151,8 +150,8 @@
   return true;
 }
 
-bool ParseReference(const StringPiece& str, ResourceNameRef* out_ref,
-                    bool* out_create, bool* out_private) {
+bool ParseReference(StringPiece str, ResourceNameRef* out_ref, bool* out_create,
+                    bool* out_private) {
   StringPiece trimmed_str(util::TrimWhitespace(str));
   if (trimmed_str.empty()) {
     return false;
@@ -198,11 +197,11 @@
   return false;
 }
 
-bool IsReference(const StringPiece& str) {
+bool IsReference(StringPiece str) {
   return ParseReference(str, nullptr, nullptr, nullptr);
 }
 
-bool ParseAttributeReference(const StringPiece& str, ResourceNameRef* out_ref) {
+bool ParseAttributeReference(StringPiece str, ResourceNameRef* out_ref) {
   StringPiece trimmed_str(util::TrimWhitespace(str));
   if (trimmed_str.empty()) {
     return false;
@@ -235,7 +234,7 @@
   return false;
 }
 
-bool IsAttributeReference(const StringPiece& str) {
+bool IsAttributeReference(StringPiece str) {
   return ParseAttributeReference(str, nullptr);
 }
 
@@ -247,7 +246,7 @@
  * <[*]package>:[style/]<entry>
  * [[*]package:style/]<entry>
  */
-std::optional<Reference> ParseStyleParentReference(const StringPiece& str, std::string* out_error) {
+std::optional<Reference> ParseStyleParentReference(StringPiece str, std::string* out_error) {
   if (str.empty()) {
     return {};
   }
@@ -296,7 +295,7 @@
   return result;
 }
 
-std::optional<Reference> ParseXmlAttributeName(const StringPiece& str) {
+std::optional<Reference> ParseXmlAttributeName(StringPiece str) {
   StringPiece trimmed_str = util::TrimWhitespace(str);
   const char* start = trimmed_str.data();
   const char* const end = start + trimmed_str.size();
@@ -325,8 +324,7 @@
   return std::optional<Reference>(std::move(ref));
 }
 
-std::unique_ptr<Reference> TryParseReference(const StringPiece& str,
-                                             bool* out_create) {
+std::unique_ptr<Reference> TryParseReference(StringPiece str, bool* out_create) {
   ResourceNameRef ref;
   bool private_ref = false;
   if (ParseReference(str, &ref, out_create, &private_ref)) {
@@ -344,7 +342,7 @@
   return {};
 }
 
-std::unique_ptr<Item> TryParseNullOrEmpty(const StringPiece& str) {
+std::unique_ptr<Item> TryParseNullOrEmpty(StringPiece str) {
   const StringPiece trimmed_str(util::TrimWhitespace(str));
   if (trimmed_str == "@null") {
     return MakeNull();
@@ -365,8 +363,7 @@
                                             android::Res_value::DATA_NULL_EMPTY);
 }
 
-std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr,
-                                                    const StringPiece& str) {
+std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr, StringPiece str) {
   StringPiece trimmed_str(util::TrimWhitespace(str));
   for (const Attribute::Symbol& symbol : enum_attr->symbols) {
     // Enum symbols are stored as @package:id/symbol resources,
@@ -382,8 +379,7 @@
   return {};
 }
 
-std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* flag_attr,
-                                                    const StringPiece& str) {
+std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* flag_attr, StringPiece str) {
   android::Res_value flags = {};
   flags.dataType = android::Res_value::TYPE_INT_HEX;
   flags.data = 0u;
@@ -393,7 +389,7 @@
     return util::make_unique<BinaryPrimitive>(flags);
   }
 
-  for (const StringPiece& part : util::Tokenize(str, '|')) {
+  for (StringPiece part : util::Tokenize(str, '|')) {
     StringPiece trimmed_part = util::TrimWhitespace(part);
 
     bool flag_set = false;
@@ -429,7 +425,7 @@
   }
 }
 
-std::unique_ptr<BinaryPrimitive> TryParseColor(const StringPiece& str) {
+std::unique_ptr<BinaryPrimitive> TryParseColor(StringPiece str) {
   StringPiece color_str(util::TrimWhitespace(str));
   const char* start = color_str.data();
   const size_t len = color_str.size();
@@ -484,7 +480,7 @@
                : util::make_unique<BinaryPrimitive>(value);
 }
 
-std::optional<bool> ParseBool(const StringPiece& str) {
+std::optional<bool> ParseBool(StringPiece str) {
   StringPiece trimmed_str(util::TrimWhitespace(str));
   if (trimmed_str == "true" || trimmed_str == "TRUE" || trimmed_str == "True") {
     return std::optional<bool>(true);
@@ -495,7 +491,7 @@
   return {};
 }
 
-std::optional<uint32_t> ParseInt(const StringPiece& str) {
+std::optional<uint32_t> ParseInt(StringPiece str) {
   std::u16string str16 = android::util::Utf8ToUtf16(str);
   android::Res_value value;
   if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) {
@@ -504,7 +500,7 @@
   return {};
 }
 
-std::optional<ResourceId> ParseResourceId(const StringPiece& str) {
+std::optional<ResourceId> ParseResourceId(StringPiece str) {
   StringPiece trimmed_str(util::TrimWhitespace(str));
 
   std::u16string str16 = android::util::Utf8ToUtf16(trimmed_str);
@@ -520,7 +516,7 @@
   return {};
 }
 
-std::optional<int> ParseSdkVersion(const StringPiece& str) {
+std::optional<int> ParseSdkVersion(StringPiece str) {
   StringPiece trimmed_str(util::TrimWhitespace(str));
 
   std::u16string str16 = android::util::Utf8ToUtf16(trimmed_str);
@@ -539,14 +535,14 @@
   const StringPiece::const_iterator begin = std::begin(trimmed_str);
   const StringPiece::const_iterator end = std::end(trimmed_str);
   const StringPiece::const_iterator codename_end = std::find(begin, end, '.');
-  entry = GetDevelopmentSdkCodeNameVersion(trimmed_str.substr(begin, codename_end));
+  entry = GetDevelopmentSdkCodeNameVersion(StringPiece(begin, codename_end - begin));
   if (entry) {
     return entry.value();
   }
   return {};
 }
 
-std::unique_ptr<BinaryPrimitive> TryParseBool(const StringPiece& str) {
+std::unique_ptr<BinaryPrimitive> TryParseBool(StringPiece str) {
   if (std::optional<bool> maybe_result = ParseBool(str)) {
     const uint32_t data = maybe_result.value() ? 0xffffffffu : 0u;
     return util::make_unique<BinaryPrimitive>(android::Res_value::TYPE_INT_BOOLEAN, data);
@@ -559,7 +555,7 @@
                                             val ? 0xffffffffu : 0u);
 }
 
-std::unique_ptr<BinaryPrimitive> TryParseInt(const StringPiece& str) {
+std::unique_ptr<BinaryPrimitive> TryParseInt(StringPiece str) {
   std::u16string str16 = android::util::Utf8ToUtf16(util::TrimWhitespace(str));
   android::Res_value value;
   if (!android::ResTable::stringToInt(str16.data(), str16.size(), &value)) {
@@ -572,7 +568,7 @@
   return util::make_unique<BinaryPrimitive>(android::Res_value::TYPE_INT_DEC, val);
 }
 
-std::unique_ptr<BinaryPrimitive> TryParseFloat(const StringPiece& str) {
+std::unique_ptr<BinaryPrimitive> TryParseFloat(StringPiece str) {
   std::u16string str16 = android::util::Utf8ToUtf16(util::TrimWhitespace(str));
   android::Res_value value;
   if (!android::ResTable::stringToFloat(str16.data(), str16.size(), &value)) {
@@ -623,7 +619,7 @@
 }
 
 std::unique_ptr<Item> TryParseItemForAttribute(
-    const StringPiece& value, uint32_t type_mask,
+    StringPiece value, uint32_t type_mask,
     const std::function<bool(const ResourceName&)>& on_create_reference) {
   using android::ResTable_map;
 
@@ -687,7 +683,7 @@
  * allows.
  */
 std::unique_ptr<Item> TryParseItemForAttribute(
-    const StringPiece& str, const Attribute* attr,
+    StringPiece str, const Attribute* attr,
     const std::function<bool(const ResourceName&)>& on_create_reference) {
   using android::ResTable_map;
 
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
index 22cf345..f30f4ac 100644
--- a/tools/aapt2/ResourceUtils.h
+++ b/tools/aapt2/ResourceUtils.h
@@ -38,7 +38,7 @@
  * `out_resource` set to the parsed resource name and `out_private` set to true
  * if a '*' prefix was present.
  */
-bool ParseResourceName(const android::StringPiece& str, ResourceNameRef* out_resource,
+bool ParseResourceName(android::StringPiece str, ResourceNameRef* out_resource,
                        bool* out_private = nullptr);
 
 /*
@@ -49,27 +49,27 @@
  * If '+' was present in the reference, `out_create` is set to true.
  * If '*' was present in the reference, `out_private` is set to true.
  */
-bool ParseReference(const android::StringPiece& str, ResourceNameRef* out_reference,
+bool ParseReference(android::StringPiece str, ResourceNameRef* out_reference,
                     bool* out_create = nullptr, bool* out_private = nullptr);
 
 /*
  * Returns true if the string is in the form of a resource reference
  * (@[+][package:]type/name).
  */
-bool IsReference(const android::StringPiece& str);
+bool IsReference(android::StringPiece str);
 
 /*
  * Returns true if the string was parsed as an attribute reference
  * (?[package:][type/]name),
  * with `out_reference` set to the parsed reference.
  */
-bool ParseAttributeReference(const android::StringPiece& str, ResourceNameRef* out_reference);
+bool ParseAttributeReference(android::StringPiece str, ResourceNameRef* out_reference);
 
 /**
  * Returns true if the string is in the form of an attribute
  * reference(?[package:][type/]name).
  */
-bool IsAttributeReference(const android::StringPiece& str);
+bool IsAttributeReference(android::StringPiece str);
 
 /**
  * Convert an android::ResTable::resource_name to an aapt::ResourceName struct.
@@ -85,22 +85,22 @@
  * Returns a boolean value if the string is equal to TRUE, true, True, FALSE,
  * false, or False.
  */
-std::optional<bool> ParseBool(const android::StringPiece& str);
+std::optional<bool> ParseBool(android::StringPiece str);
 
 /**
  * Returns a uint32_t if the string is an integer.
  */
-std::optional<uint32_t> ParseInt(const android::StringPiece& str);
+std::optional<uint32_t> ParseInt(android::StringPiece str);
 
 /**
  * Returns an ID if it the string represented a valid ID.
  */
-std::optional<ResourceId> ParseResourceId(const android::StringPiece& str);
+std::optional<ResourceId> ParseResourceId(android::StringPiece str);
 
 /**
  * Parses an SDK version, which can be an integer, or a letter from A-Z.
  */
-std::optional<int> ParseSdkVersion(const android::StringPiece& str);
+std::optional<int> ParseSdkVersion(android::StringPiece str);
 
 /*
  * Returns a Reference, or None Maybe instance if the string `str` was parsed as
@@ -113,7 +113,7 @@
  * ?[package:]style/<entry> or
  * <package>:[style/]<entry>
  */
-std::optional<Reference> ParseStyleParentReference(const android::StringPiece& str,
+std::optional<Reference> ParseStyleParentReference(android::StringPiece str,
                                                    std::string* out_error);
 
 /*
@@ -123,7 +123,7 @@
  *
  * package:entry
  */
-std::optional<Reference> ParseXmlAttributeName(const android::StringPiece& str);
+std::optional<Reference> ParseXmlAttributeName(android::StringPiece str);
 
 /*
  * Returns a Reference object if the string was parsed as a resource or
@@ -132,14 +132,13 @@
  * if
  * the '+' was present in the string.
  */
-std::unique_ptr<Reference> TryParseReference(const android::StringPiece& str,
-                                             bool* out_create = nullptr);
+std::unique_ptr<Reference> TryParseReference(android::StringPiece str, bool* out_create = nullptr);
 
 /*
  * Returns a BinaryPrimitve object representing @null or @empty if the string
  * was parsed as one.
  */
-std::unique_ptr<Item> TryParseNullOrEmpty(const android::StringPiece& str);
+std::unique_ptr<Item> TryParseNullOrEmpty(android::StringPiece str);
 
 // Returns a Reference representing @null.
 // Due to runtime compatibility issues, this is encoded as a reference with ID 0.
@@ -154,13 +153,13 @@
  * Returns a BinaryPrimitve object representing a color if the string was parsed
  * as one.
  */
-std::unique_ptr<BinaryPrimitive> TryParseColor(const android::StringPiece& str);
+std::unique_ptr<BinaryPrimitive> TryParseColor(android::StringPiece str);
 
 /*
  * Returns a BinaryPrimitve object representing a boolean if the string was
  * parsed as one.
  */
-std::unique_ptr<BinaryPrimitive> TryParseBool(const android::StringPiece& str);
+std::unique_ptr<BinaryPrimitive> TryParseBool(android::StringPiece str);
 
 // Returns a boolean BinaryPrimitive.
 std::unique_ptr<BinaryPrimitive> MakeBool(bool val);
@@ -169,7 +168,7 @@
  * Returns a BinaryPrimitve object representing an integer if the string was
  * parsed as one.
  */
-std::unique_ptr<BinaryPrimitive> TryParseInt(const android::StringPiece& str);
+std::unique_ptr<BinaryPrimitive> TryParseInt(android::StringPiece str);
 
 // Returns an integer BinaryPrimitive.
 std::unique_ptr<BinaryPrimitive> MakeInt(uint32_t value);
@@ -178,21 +177,21 @@
  * Returns a BinaryPrimitve object representing a floating point number
  * (float, dimension, etc) if the string was parsed as one.
  */
-std::unique_ptr<BinaryPrimitive> TryParseFloat(const android::StringPiece& str);
+std::unique_ptr<BinaryPrimitive> TryParseFloat(android::StringPiece str);
 
 /*
  * Returns a BinaryPrimitve object representing an enum symbol if the string was
  * parsed as one.
  */
 std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr,
-                                                    const android::StringPiece& str);
+                                                    android::StringPiece str);
 
 /*
  * Returns a BinaryPrimitve object representing a flag symbol if the string was
  * parsed as one.
  */
 std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* enum_attr,
-                                                    const android::StringPiece& str);
+                                                    android::StringPiece str);
 /*
  * Try to convert a string to an Item for the given attribute. The attribute
  * will
@@ -201,11 +200,11 @@
  * reference to an ID that must be created (@+id/foo).
  */
 std::unique_ptr<Item> TryParseItemForAttribute(
-    const android::StringPiece& value, const Attribute* attr,
+    android::StringPiece value, const Attribute* attr,
     const std::function<bool(const ResourceName&)>& on_create_reference = {});
 
 std::unique_ptr<Item> TryParseItemForAttribute(
-    const android::StringPiece& value, uint32_t type_mask,
+    android::StringPiece value, uint32_t type_mask,
     const std::function<bool(const ResourceName&)>& on_create_reference = {});
 
 uint32_t AndroidTypeToAttributeTypeMask(uint16_t type);
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index c4d54be..a5754e0 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -206,7 +206,7 @@
   PrettyPrintReferenceImpl(*this, true /*print_package*/, printer);
 }
 
-void Reference::PrettyPrint(const StringPiece& package, Printer* printer) const {
+void Reference::PrettyPrint(StringPiece package, Printer* printer) const {
   const bool print_package = name ? package != name.value().package : true;
   PrettyPrintReferenceImpl(*this, print_package, printer);
 }
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index f5167a1..6f9dccb 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -83,8 +83,8 @@
     return comment_;
   }
 
-  void SetComment(const android::StringPiece& str) {
-    comment_ = str.to_string();
+  void SetComment(android::StringPiece str) {
+    comment_.assign(str);
   }
 
   void SetComment(std::string&& str) {
@@ -176,7 +176,7 @@
   void PrettyPrint(text::Printer* printer) const override;
 
   // Prints the reference without a package name if the package name matches the one given.
-  void PrettyPrint(const android::StringPiece& package, text::Printer* printer) const;
+  void PrettyPrint(android::StringPiece package, text::Printer* printer) const;
 };
 
 bool operator<(const Reference&, const Reference&);
diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp
index 34e8edb..a7c5479 100644
--- a/tools/aapt2/SdkConstants.cpp
+++ b/tools/aapt2/SdkConstants.cpp
@@ -77,7 +77,7 @@
   return iter->second;
 }
 
-std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(const StringPiece& code_name) {
+std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(StringPiece code_name) {
   return (sDevelopmentSdkCodeNames.find(code_name) == sDevelopmentSdkCodeNames.end())
              ? std::optional<ApiVersion>()
              : sDevelopmentSdkLevel;
diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h
index 0bd61c0..40bcef7 100644
--- a/tools/aapt2/SdkConstants.h
+++ b/tools/aapt2/SdkConstants.h
@@ -63,7 +63,7 @@
 };
 
 ApiVersion FindAttributeSdkLevel(const ResourceId& id);
-std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(const android::StringPiece& code_name);
+std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(android::StringPiece code_name);
 
 }  // namespace aapt
 
diff --git a/tools/aapt2/cmd/ApkInfo.cpp b/tools/aapt2/cmd/ApkInfo.cpp
index 697b110..3c0831c 100644
--- a/tools/aapt2/cmd/ApkInfo.cpp
+++ b/tools/aapt2/cmd/ApkInfo.cpp
@@ -64,7 +64,7 @@
     Usage(&std::cerr);
     return 1;
   }
-  const StringPiece& path = args[0];
+  StringPiece path = args[0];
   std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, diag_);
   if (!apk) {
     return 1;
diff --git a/tools/aapt2/cmd/Command.cpp b/tools/aapt2/cmd/Command.cpp
index b1452fa..514651e 100644
--- a/tools/aapt2/cmd/Command.cpp
+++ b/tools/aapt2/cmd/Command.cpp
@@ -33,7 +33,7 @@
 
 namespace aapt {
 
-std::string GetSafePath(const StringPiece& arg) {
+std::string GetSafePath(StringPiece arg) {
 #ifdef _WIN32
   // If the path exceeds the maximum path length for Windows, encode the path using the
   // extended-length prefix
@@ -47,63 +47,62 @@
 
   return path8;
 #else
-  return arg.to_string();
+  return std::string(arg);
 #endif
 }
 
-void Command::AddRequiredFlag(const StringPiece& name, const StringPiece& description,
-                              std::string* value, uint32_t flags) {
-  auto func = [value, flags](const StringPiece& arg) -> bool {
-    *value = (flags & Command::kPath) ? GetSafePath(arg) : arg.to_string();
+void Command::AddRequiredFlag(StringPiece name, StringPiece description, std::string* value,
+                              uint32_t flags) {
+  auto func = [value, flags](StringPiece arg) -> bool {
+    *value = (flags & Command::kPath) ? GetSafePath(arg) : std::string(arg);
     return true;
   };
 
   flags_.emplace_back(Flag(name, description, /* required */ true, /* num_args */ 1, func));
 }
 
-void Command::AddRequiredFlagList(const StringPiece& name, const StringPiece& description,
+void Command::AddRequiredFlagList(StringPiece name, StringPiece description,
                                   std::vector<std::string>* value, uint32_t flags) {
-  auto func = [value, flags](const StringPiece& arg) -> bool {
-    value->push_back((flags & Command::kPath) ? GetSafePath(arg) : arg.to_string());
+  auto func = [value, flags](StringPiece arg) -> bool {
+    value->push_back((flags & Command::kPath) ? GetSafePath(arg) : std::string(arg));
     return true;
   };
 
   flags_.emplace_back(Flag(name, description, /* required */ true, /* num_args */ 1, func));
 }
 
-void Command::AddOptionalFlag(const StringPiece& name, const StringPiece& description,
+void Command::AddOptionalFlag(StringPiece name, StringPiece description,
                               std::optional<std::string>* value, uint32_t flags) {
-  auto func = [value, flags](const StringPiece& arg) -> bool {
-    *value = (flags & Command::kPath) ? GetSafePath(arg) : arg.to_string();
+  auto func = [value, flags](StringPiece arg) -> bool {
+    *value = (flags & Command::kPath) ? GetSafePath(arg) : std::string(arg);
     return true;
   };
 
   flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func));
 }
 
-void Command::AddOptionalFlagList(const StringPiece& name, const StringPiece& description,
+void Command::AddOptionalFlagList(StringPiece name, StringPiece description,
                                   std::vector<std::string>* value, uint32_t flags) {
-  auto func = [value, flags](const StringPiece& arg) -> bool {
-    value->push_back((flags & Command::kPath) ? GetSafePath(arg) : arg.to_string());
+  auto func = [value, flags](StringPiece arg) -> bool {
+    value->push_back((flags & Command::kPath) ? GetSafePath(arg) : std::string(arg));
     return true;
   };
 
   flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func));
 }
 
-void Command::AddOptionalFlagList(const StringPiece& name, const StringPiece& description,
+void Command::AddOptionalFlagList(StringPiece name, StringPiece description,
                                   std::unordered_set<std::string>* value) {
-  auto func = [value](const StringPiece& arg) -> bool {
-    value->insert(arg.to_string());
+  auto func = [value](StringPiece arg) -> bool {
+    value->emplace(arg);
     return true;
   };
 
   flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func));
 }
 
-void Command::AddOptionalSwitch(const StringPiece& name, const StringPiece& description,
-                                bool* value) {
-  auto func = [value](const StringPiece& arg) -> bool {
+void Command::AddOptionalSwitch(StringPiece name, StringPiece description, bool* value) {
+  auto func = [value](StringPiece arg) -> bool {
     *value = true;
     return true;
   };
@@ -120,8 +119,8 @@
   }
 }
 
-void Command::SetDescription(const StringPiece& description) {
-  description_ = description.to_string();
+void Command::SetDescription(StringPiece description) {
+  description_ = std::string(description);
 }
 
 void Command::Usage(std::ostream* out) {
@@ -183,7 +182,7 @@
   std::vector<std::string> file_args;
 
   for (size_t i = 0; i < args.size(); i++) {
-    const StringPiece& arg = args[i];
+    StringPiece arg = args[i];
     if (*(arg.data()) != '-') {
       // Continue parsing as the subcommand if the first argument matches one of the subcommands
       if (i == 0) {
diff --git a/tools/aapt2/cmd/Command.h b/tools/aapt2/cmd/Command.h
index 8678cda..1416e98 100644
--- a/tools/aapt2/cmd/Command.h
+++ b/tools/aapt2/cmd/Command.h
@@ -30,13 +30,10 @@
 
 class Command {
  public:
-  explicit Command(const android::StringPiece& name)
-      : name_(name.to_string()), full_subcommand_name_(name.to_string()){};
+  explicit Command(android::StringPiece name) : name_(name), full_subcommand_name_(name){};
 
-  explicit Command(const android::StringPiece& name, const android::StringPiece& short_name)
-      : name_(name.to_string()),
-        short_name_(short_name.to_string()),
-        full_subcommand_name_(name.to_string()){};
+  explicit Command(android::StringPiece name, android::StringPiece short_name)
+      : name_(name), short_name_(short_name), full_subcommand_name_(name){};
 
   Command(Command&&) = default;
   Command& operator=(Command&&) = default;
@@ -52,30 +49,26 @@
     kPath = 1 << 0,
   };
 
-  void AddRequiredFlag(const android::StringPiece& name, const android::StringPiece& description,
+  void AddRequiredFlag(android::StringPiece name, android::StringPiece description,
                        std::string* value, uint32_t flags = 0);
 
-  void AddRequiredFlagList(const android::StringPiece& name,
-                           const android::StringPiece& description, std::vector<std::string>* value,
-                           uint32_t flags = 0);
+  void AddRequiredFlagList(android::StringPiece name, android::StringPiece description,
+                           std::vector<std::string>* value, uint32_t flags = 0);
 
-  void AddOptionalFlag(const android::StringPiece& name, const android::StringPiece& description,
+  void AddOptionalFlag(android::StringPiece name, android::StringPiece description,
                        std::optional<std::string>* value, uint32_t flags = 0);
 
-  void AddOptionalFlagList(const android::StringPiece& name,
-                           const android::StringPiece& description, std::vector<std::string>* value,
-                           uint32_t flags = 0);
+  void AddOptionalFlagList(android::StringPiece name, android::StringPiece description,
+                           std::vector<std::string>* value, uint32_t flags = 0);
 
-  void AddOptionalFlagList(const android::StringPiece& name,
-                           const android::StringPiece& description,
+  void AddOptionalFlagList(android::StringPiece name, android::StringPiece description,
                            std::unordered_set<std::string>* value);
 
-  void AddOptionalSwitch(const android::StringPiece& name, const android::StringPiece& description,
-                         bool* value);
+  void AddOptionalSwitch(android::StringPiece name, android::StringPiece description, bool* value);
 
   void AddOptionalSubcommand(std::unique_ptr<Command>&& subcommand, bool experimental = false);
 
-  void SetDescription(const android::StringPiece& name);
+  void SetDescription(android::StringPiece name);
 
   // Prints the help menu of the command.
   void Usage(std::ostream* out);
@@ -90,17 +83,21 @@
 
  private:
   struct Flag {
-    explicit Flag(const android::StringPiece& name, const android::StringPiece& description,
+    explicit Flag(android::StringPiece name, android::StringPiece description,
                   const bool is_required, const size_t num_args,
-                  std::function<bool(const android::StringPiece& value)>&& action)
-        : name(name.to_string()), description(description.to_string()), is_required(is_required),
-          num_args(num_args), action(std::move(action)) {}
+                  std::function<bool(android::StringPiece value)>&& action)
+        : name(name),
+          description(description),
+          is_required(is_required),
+          num_args(num_args),
+          action(std::move(action)) {
+    }
 
     const std::string name;
     const std::string description;
     const bool is_required;
     const size_t num_args;
-    const std::function<bool(const android::StringPiece& value)> action;
+    const std::function<bool(android::StringPiece value)> action;
     bool found = false;
   };
 
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 0409f73..03f9715 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -125,8 +125,12 @@
   const android::Source res_path =
       options.source_path ? StringPiece(options.source_path.value()) : StringPiece(path);
 
-  return ResourcePathData{res_path, dir_str.to_string(), name.to_string(),
-                          extension.to_string(), config_str.to_string(), config};
+  return ResourcePathData{res_path,
+                          std::string(dir_str),
+                          std::string(name),
+                          std::string(extension),
+                          std::string(config_str),
+                          config};
 }
 
 static std::string BuildIntermediateContainerFilename(const ResourcePathData& data) {
@@ -279,7 +283,7 @@
   return true;
 }
 
-static bool WriteHeaderAndDataToWriter(const StringPiece& output_path, const ResourceFile& file,
+static bool WriteHeaderAndDataToWriter(StringPiece output_path, const ResourceFile& file,
                                        io::KnownSizeInputStream* in, IArchiveWriter* writer,
                                        android::IDiagnostics* diag) {
   TRACE_CALL();
@@ -311,7 +315,7 @@
   return true;
 }
 
-static bool FlattenXmlToOutStream(const StringPiece& output_path, const xml::XmlResource& xmlres,
+static bool FlattenXmlToOutStream(StringPiece output_path, const xml::XmlResource& xmlres,
                                   ContainerWriter* container_writer, android::IDiagnostics* diag) {
   pb::internal::CompiledFile pb_compiled_file;
   SerializeCompiledFileToPb(xmlres.file, &pb_compiled_file);
@@ -538,7 +542,7 @@
     if (context->IsVerbose()) {
       // For debugging only, use the legacy PNG cruncher and compare the resulting file sizes.
       // This will help catch exotic cases where the new code may generate larger PNGs.
-      std::stringstream legacy_stream(content.to_string());
+      std::stringstream legacy_stream{std::string(content)};
       android::BigBuffer legacy_buffer(4096);
       Png png(context->GetDiagnostics());
       if (!png.process(path_data.source, &legacy_stream, &legacy_buffer, {})) {
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
index 52e113e..612e3a6 100644
--- a/tools/aapt2/cmd/Convert.cpp
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -387,7 +387,7 @@
   }
 
   Context context;
-  const StringPiece& path = args[0];
+  StringPiece path = args[0];
   unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, context.GetDiagnostics());
   if (apk == nullptr) {
     context.GetDiagnostics()->Error(android::DiagMessage(path) << "failed to load APK");
diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp
index 423e939..5bfc732 100644
--- a/tools/aapt2/cmd/Diff.cpp
+++ b/tools/aapt2/cmd/Diff.cpp
@@ -78,7 +78,7 @@
   SymbolTable symbol_table_;
 };
 
-static void EmitDiffLine(const android::Source& source, const StringPiece& message) {
+static void EmitDiffLine(const android::Source& source, StringPiece message) {
   std::cerr << source << ": " << message << "\n";
 }
 
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index a8d2299..97404fc 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -126,8 +126,8 @@
     return compilation_package_;
   }
 
-  void SetCompilationPackage(const StringPiece& package_name) {
-    compilation_package_ = package_name.to_string();
+  void SetCompilationPackage(StringPiece package_name) {
+    compilation_package_ = std::string(package_name);
   }
 
   uint8_t GetPackageId() override {
@@ -240,9 +240,9 @@
   IAaptContext* context_;
 };
 
-static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml_res,
-                       const StringPiece& path, bool keep_raw_values, bool utf16,
-                       OutputFormat format, IArchiveWriter* writer) {
+static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml_res, StringPiece path,
+                       bool keep_raw_values, bool utf16, OutputFormat format,
+                       IArchiveWriter* writer) {
   TRACE_CALL();
   if (context->IsVerbose()) {
     context->GetDiagnostics()->Note(android::DiagMessage(path)
@@ -262,8 +262,8 @@
       }
 
       io::BigBufferInputStream input_stream(&buffer);
-      return io::CopyInputStreamToArchive(context, &input_stream, path.to_string(),
-                                          ArchiveEntry::kCompress, writer);
+      return io::CopyInputStreamToArchive(context, &input_stream, path, ArchiveEntry::kCompress,
+                                          writer);
     } break;
 
     case OutputFormat::kProto: {
@@ -272,8 +272,7 @@
       SerializeXmlOptions options;
       options.remove_empty_text_nodes = (path == kAndroidManifestPath);
       SerializeXmlResourceToPb(xml_res, &pb_node);
-      return io::CopyProtoToArchive(context, &pb_node, path.to_string(), ArchiveEntry::kCompress,
-                                    writer);
+      return io::CopyProtoToArchive(context, &pb_node, path, ArchiveEntry::kCompress, writer);
     } break;
   }
   return false;
@@ -329,13 +328,13 @@
 };
 
 template <typename T>
-uint32_t GetCompressionFlags(const StringPiece& str, T options) {
+uint32_t GetCompressionFlags(StringPiece str, T options) {
   if (options.do_not_compress_anything) {
     return 0;
   }
 
-  if (options.regex_to_not_compress
-      && std::regex_search(str.to_string(), options.regex_to_not_compress.value())) {
+  if (options.regex_to_not_compress &&
+      std::regex_search(str.begin(), str.end(), options.regex_to_not_compress.value())) {
     return 0;
   }
 
@@ -1176,7 +1175,7 @@
     return bcp47tag;
   }
 
-  std::unique_ptr<IArchiveWriter> MakeArchiveWriter(const StringPiece& out) {
+  std::unique_ptr<IArchiveWriter> MakeArchiveWriter(StringPiece out) {
     if (options_.output_to_directory) {
       return CreateDirectoryArchiveWriter(context_->GetDiagnostics(), out);
     } else {
@@ -1212,8 +1211,8 @@
     return false;
   }
 
-  bool WriteJavaFile(ResourceTable* table, const StringPiece& package_name_to_generate,
-                     const StringPiece& out_package, const JavaClassGeneratorOptions& java_options,
+  bool WriteJavaFile(ResourceTable* table, StringPiece package_name_to_generate,
+                     StringPiece out_package, const JavaClassGeneratorOptions& java_options,
                      const std::optional<std::string>& out_text_symbols_path = {}) {
     if (!options_.generate_java_class_path && !out_text_symbols_path) {
       return true;
@@ -2473,14 +2472,14 @@
   for (std::string& extra_package : extra_java_packages_) {
     // A given package can actually be a colon separated list of packages.
     for (StringPiece package : util::Split(extra_package, ':')) {
-      options_.extra_java_packages.insert(package.to_string());
+      options_.extra_java_packages.emplace(package);
     }
   }
 
   if (product_list_) {
     for (StringPiece product : util::Tokenize(product_list_.value(), ',')) {
       if (product != "" && product != "default") {
-        options_.products.insert(product.to_string());
+        options_.products.emplace(product);
       }
     }
   }
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 042926c..d7a39bf 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -154,13 +154,22 @@
       return 1;
     }
 
-    if (options_.shorten_resource_paths) {
-      Obfuscator obfuscator(options_.table_flattener_options.shortened_path_map);
+    Obfuscator obfuscator(options_);
+    if (obfuscator.IsEnabled()) {
       if (!obfuscator.Consume(context_, apk->GetResourceTable())) {
         context_->GetDiagnostics()->Error(android::DiagMessage()
                                           << "failed shortening resource paths");
         return 1;
       }
+
+      if (options_.obfuscation_map_path &&
+          !obfuscator.WriteObfuscationMap(options_.obfuscation_map_path.value())) {
+        context_->GetDiagnostics()->Error(android::DiagMessage()
+                                          << "failed to write the obfuscation map to file");
+        return 1;
+      }
+
+      // TODO(b/246489170): keep the old option and format until transform to the new one
       if (options_.shortened_paths_map_path
           && !WriteShortenedPathsMap(options_.table_flattener_options.shortened_path_map,
                                       options_.shortened_paths_map_path.value())) {
@@ -292,6 +301,7 @@
                                         ArchiveEntry::kAlign, writer);
   }
 
+  // TODO(b/246489170): keep the old option and format until transform to the new one
   bool WriteShortenedPathsMap(const std::map<std::string, std::string> &path_map,
                                const std::string &file_path) {
     std::stringstream ss;
@@ -370,8 +380,8 @@
 
     if (!kept_artifacts_.empty()) {
       for (const std::string& artifact_str : kept_artifacts_) {
-        for (const StringPiece& artifact : util::Tokenize(artifact_str, ',')) {
-          options_.kept_artifacts.insert(artifact.to_string());
+        for (StringPiece artifact : util::Tokenize(artifact_str, ',')) {
+          options_.kept_artifacts.emplace(artifact);
         }
       }
     }
@@ -403,7 +413,7 @@
 
   if (target_densities_) {
     // Parse the target screen densities.
-    for (const StringPiece& config_str : util::Tokenize(target_densities_.value(), ',')) {
+    for (StringPiece config_str : util::Tokenize(target_densities_.value(), ',')) {
       std::optional<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag);
       if (!target_density) {
         return 1;
diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h
index 794a87b..1879f25 100644
--- a/tools/aapt2/cmd/Optimize.h
+++ b/tools/aapt2/cmd/Optimize.h
@@ -58,6 +58,7 @@
   bool shorten_resource_paths = false;
 
   // Path to the output map of original resource paths to shortened paths.
+  // TODO(b/246489170): keep the old option and format until transform to the new one
   std::optional<std::string> shortened_paths_map_path;
 
   // Whether sparse encoding should be used for O+ resources.
@@ -65,6 +66,9 @@
 
   // Whether sparse encoding should be used for all resources.
   bool force_sparse_encoding = false;
+
+  // Path to the output map of original resource paths/names to obfuscated paths/names.
+  std::optional<std::string> obfuscation_map_path;
 };
 
 class OptimizeCommand : public Command {
@@ -120,9 +124,13 @@
     AddOptionalSwitch("--shorten-resource-paths",
         "Shortens the paths of resources inside the APK.",
         &options_.shorten_resource_paths);
+    // TODO(b/246489170): keep the old option and format until transform to the new one
     AddOptionalFlag("--resource-path-shortening-map",
-        "Path to output the map of old resource paths to shortened paths.",
-        &options_.shortened_paths_map_path);
+                    "[Deprecated]Path to output the map of old resource paths to shortened paths.",
+                    &options_.shortened_paths_map_path);
+    AddOptionalFlag("--save-obfuscation-map",
+                    "Path to output the map of original paths/names to obfuscated paths/names.",
+                    &options_.obfuscation_map_path);
     AddOptionalSwitch(
         "--deduplicate-entry-values",
         "Whether to deduplicate pairs of resource entry and value for simple resources.\n"
diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp
index 56e2f52..92849cf 100644
--- a/tools/aapt2/cmd/Util.cpp
+++ b/tools/aapt2/cmd/Util.cpp
@@ -34,8 +34,7 @@
 
 namespace aapt {
 
-std::optional<uint16_t> ParseTargetDensityParameter(const StringPiece& arg,
-                                                    android::IDiagnostics* diag) {
+std::optional<uint16_t> ParseTargetDensityParameter(StringPiece arg, android::IDiagnostics* diag) {
   ConfigDescription preferred_density_config;
   if (!ConfigDescription::Parse(arg, &preferred_density_config)) {
     diag->Error(android::DiagMessage()
@@ -55,7 +54,7 @@
   return preferred_density_config.density;
 }
 
-bool ParseSplitParameter(const StringPiece& arg, android::IDiagnostics* diag, std::string* out_path,
+bool ParseSplitParameter(StringPiece arg, android::IDiagnostics* diag, std::string* out_path,
                          SplitConstraints* out_split) {
   CHECK(diag != nullptr);
   CHECK(out_path != nullptr);
@@ -77,7 +76,7 @@
 
   *out_path = parts[0];
   out_split->name = parts[1];
-  for (const StringPiece& config_str : util::Tokenize(parts[1], ',')) {
+  for (StringPiece config_str : util::Tokenize(parts[1], ',')) {
     ConfigDescription config;
     if (!ConfigDescription::Parse(config_str, &config)) {
       diag->Error(android::DiagMessage()
@@ -93,7 +92,7 @@
                                                            android::IDiagnostics* diag) {
   std::unique_ptr<AxisConfigFilter> filter = util::make_unique<AxisConfigFilter>();
   for (const std::string& config_arg : args) {
-    for (const StringPiece& config_str : util::Tokenize(config_arg, ',')) {
+    for (StringPiece config_str : util::Tokenize(config_arg, ',')) {
       ConfigDescription config;
       LocaleValue lv;
       if (lv.InitFromFilterString(config_str)) {
diff --git a/tools/aapt2/cmd/Util.h b/tools/aapt2/cmd/Util.h
index 3d4ca24..169d5f9 100644
--- a/tools/aapt2/cmd/Util.h
+++ b/tools/aapt2/cmd/Util.h
@@ -34,13 +34,13 @@
 
 // Parses a configuration density (ex. hdpi, xxhdpi, 234dpi, anydpi, etc).
 // Returns Nothing and logs a human friendly error message if the string was not legal.
-std::optional<uint16_t> ParseTargetDensityParameter(const android::StringPiece& arg,
+std::optional<uint16_t> ParseTargetDensityParameter(android::StringPiece arg,
                                                     android::IDiagnostics* diag);
 
 // Parses a string of the form 'path/to/output.apk:<config>[,<config>...]' and fills in
 // `out_path` with the path and `out_split` with the set of ConfigDescriptions.
 // Returns false and logs a human friendly error message if the string was not legal.
-bool ParseSplitParameter(const android::StringPiece& arg, android::IDiagnostics* diag,
+bool ParseSplitParameter(android::StringPiece arg, android::IDiagnostics* diag,
                          std::string* out_path, SplitConstraints* out_split);
 
 // Parses a set of config filter strings of the form 'en,fr-rFR' and returns an IConfigFilter.
diff --git a/tools/aapt2/compile/NinePatch.cpp b/tools/aapt2/compile/NinePatch.cpp
index c931da4..4538ecc 100644
--- a/tools/aapt2/compile/NinePatch.cpp
+++ b/tools/aapt2/compile/NinePatch.cpp
@@ -218,11 +218,9 @@
 
 static bool PopulateBounds(const std::vector<Range>& padding,
                            const std::vector<Range>& layout_bounds,
-                           const std::vector<Range>& stretch_regions,
-                           const int32_t length, int32_t* padding_start,
-                           int32_t* padding_end, int32_t* layout_start,
-                           int32_t* layout_end, const StringPiece& edge_name,
-                           std::string* out_err) {
+                           const std::vector<Range>& stretch_regions, const int32_t length,
+                           int32_t* padding_start, int32_t* padding_end, int32_t* layout_start,
+                           int32_t* layout_end, StringPiece edge_name, std::string* out_err) {
   if (padding.size() > 1) {
     std::stringstream err_stream;
     err_stream << "too many padding sections on " << edge_name << " border";
diff --git a/tools/aapt2/compile/Png.h b/tools/aapt2/compile/Png.h
index 7f8d923..a8b7dd1 100644
--- a/tools/aapt2/compile/Png.h
+++ b/tools/aapt2/compile/Png.h
@@ -59,7 +59,7 @@
  */
 class PngChunkFilter : public io::InputStream {
  public:
-  explicit PngChunkFilter(const android::StringPiece& data);
+  explicit PngChunkFilter(android::StringPiece data);
   virtual ~PngChunkFilter() = default;
 
   bool Next(const void** buffer, size_t* len) override;
diff --git a/tools/aapt2/compile/PngChunkFilter.cpp b/tools/aapt2/compile/PngChunkFilter.cpp
index 4db2392..2e55d0c 100644
--- a/tools/aapt2/compile/PngChunkFilter.cpp
+++ b/tools/aapt2/compile/PngChunkFilter.cpp
@@ -70,7 +70,7 @@
   }
 }
 
-PngChunkFilter::PngChunkFilter(const StringPiece& data) : data_(data) {
+PngChunkFilter::PngChunkFilter(StringPiece data) : data_(data) {
   if (util::StartsWith(data_, kPngSignature)) {
     window_start_ = 0;
     window_end_ = kPngSignatureSize;
diff --git a/tools/aapt2/compile/Pseudolocalizer.cpp b/tools/aapt2/compile/Pseudolocalizer.cpp
index 3a515fa..463ce78 100644
--- a/tools/aapt2/compile/Pseudolocalizer.cpp
+++ b/tools/aapt2/compile/Pseudolocalizer.cpp
@@ -20,36 +20,42 @@
 
 using android::StringPiece;
 
+using namespace std::literals;
+
 namespace aapt {
 
 // String basis to generate expansion
-static const std::string kExpansionString =
+static constexpr auto kExpansionString =
     "one two three "
     "four five six seven eight nine ten eleven twelve thirteen "
-    "fourteen fiveteen sixteen seventeen nineteen twenty";
+    "fourteen fiveteen sixteen seventeen nineteen twenty"sv;
 
 // Special unicode characters to override directionality of the words
-static const std::string kRlm = "\u200f";
-static const std::string kRlo = "\u202e";
-static const std::string kPdf = "\u202c";
+static constexpr auto kRlm = "\u200f"sv;
+static constexpr auto kRlo = "\u202e"sv;
+static constexpr auto kPdf = "\u202c"sv;
 
 // Placeholder marks
-static const std::string kPlaceholderOpen = "\u00bb";
-static const std::string kPlaceholderClose = "\u00ab";
+static constexpr auto kPlaceholderOpen = "\u00bb"sv;
+static constexpr auto kPlaceholderClose = "\u00ab"sv;
 
 static const char kArgStart = '{';
 static const char kArgEnd = '}';
 
 class PseudoMethodNone : public PseudoMethodImpl {
  public:
-  std::string Text(const StringPiece& text) override { return text.to_string(); }
-  std::string Placeholder(const StringPiece& text) override { return text.to_string(); }
+  std::string Text(StringPiece text) override {
+    return std::string(text);
+  }
+  std::string Placeholder(StringPiece text) override {
+    return std::string(text);
+  }
 };
 
 class PseudoMethodBidi : public PseudoMethodImpl {
  public:
-  std::string Text(const StringPiece& text) override;
-  std::string Placeholder(const StringPiece& text) override;
+  std::string Text(StringPiece text) override;
+  std::string Placeholder(StringPiece text) override;
 };
 
 class PseudoMethodAccent : public PseudoMethodImpl {
@@ -57,8 +63,8 @@
   PseudoMethodAccent() : depth_(0), word_count_(0), length_(0) {}
   std::string Start() override;
   std::string End() override;
-  std::string Text(const StringPiece& text) override;
-  std::string Placeholder(const StringPiece& text) override;
+  std::string Text(StringPiece text) override;
+  std::string Placeholder(StringPiece text) override;
 
  private:
   size_t depth_;
@@ -84,7 +90,7 @@
   }
 }
 
-std::string Pseudolocalizer::Text(const StringPiece& text) {
+std::string Pseudolocalizer::Text(StringPiece text) {
   std::string out;
   size_t depth = last_depth_;
   size_t lastpos, pos;
@@ -116,7 +122,7 @@
       }
       size_t size = nextpos - lastpos;
       if (size) {
-        std::string chunk = text.substr(lastpos, size).to_string();
+        std::string chunk(text.substr(lastpos, size));
         if (pseudo) {
           chunk = impl_->Text(chunk);
         } else if (str[lastpos] == kArgStart && str[nextpos - 1] == kArgEnd) {
@@ -301,21 +307,23 @@
 }
 
 static std::string PseudoGenerateExpansion(const unsigned int length) {
-  std::string result = kExpansionString;
-  const char* s = result.data();
+  std::string result(kExpansionString);
   if (result.size() < length) {
     result += " ";
     result += PseudoGenerateExpansion(length - result.size());
   } else {
     int ext = 0;
     // Should contain only whole words, so looking for a space
-    for (unsigned int i = length + 1; i < result.size(); ++i) {
-      ++ext;
-      if (s[i] == ' ') {
-        break;
+    {
+      const char* const s = result.data();
+      for (unsigned int i = length + 1; i < result.size(); ++i) {
+        ++ext;
+        if (s[i] == ' ') {
+          break;
+        }
       }
     }
-    result = result.substr(0, length + ext);
+    result.resize(length + ext);
   }
   return result;
 }
@@ -349,7 +357,7 @@
  *
  * Note: This leaves placeholder syntax untouched.
  */
-std::string PseudoMethodAccent::Text(const StringPiece& source) {
+std::string PseudoMethodAccent::Text(StringPiece source) {
   const char* s = source.data();
   std::string result;
   const size_t I = source.size();
@@ -435,12 +443,12 @@
   return result;
 }
 
-std::string PseudoMethodAccent::Placeholder(const StringPiece& source) {
+std::string PseudoMethodAccent::Placeholder(StringPiece source) {
   // Surround a placeholder with brackets
-  return kPlaceholderOpen + source.to_string() + kPlaceholderClose;
+  return (std::string(kPlaceholderOpen) += source) += kPlaceholderClose;
 }
 
-std::string PseudoMethodBidi::Text(const StringPiece& source) {
+std::string PseudoMethodBidi::Text(StringPiece source) {
   const char* s = source.data();
   std::string result;
   bool lastspace = true;
@@ -456,10 +464,10 @@
     space = (!escape && isspace(c)) || (escape && (c == 'n' || c == 't'));
     if (lastspace && !space) {
       // Word start
-      result += kRlm + kRlo;
+      (result += kRlm) += kRlo;
     } else if (!lastspace && space) {
       // Word end
-      result += kPdf + kRlm;
+      (result += kPdf) += kRlm;
     }
     lastspace = space;
     if (escape) {
@@ -470,14 +478,14 @@
   }
   if (!lastspace) {
     // End of last word
-    result += kPdf + kRlm;
+    (result += kPdf) += kRlm;
   }
   return result;
 }
 
-std::string PseudoMethodBidi::Placeholder(const StringPiece& source) {
+std::string PseudoMethodBidi::Placeholder(StringPiece source) {
   // Surround a placeholder with directionality change sequence
-  return kRlm + kRlo + source.to_string() + kPdf + kRlm;
+  return (((std::string(kRlm) += kRlo) += source) += kPdf) += kRlm;
 }
 
 }  // namespace aapt
diff --git a/tools/aapt2/compile/Pseudolocalizer.h b/tools/aapt2/compile/Pseudolocalizer.h
index 4dedc70..2b94bcc 100644
--- a/tools/aapt2/compile/Pseudolocalizer.h
+++ b/tools/aapt2/compile/Pseudolocalizer.h
@@ -31,8 +31,8 @@
   virtual ~PseudoMethodImpl() {}
   virtual std::string Start() { return {}; }
   virtual std::string End() { return {}; }
-  virtual std::string Text(const android::StringPiece& text) = 0;
-  virtual std::string Placeholder(const android::StringPiece& text) = 0;
+  virtual std::string Text(android::StringPiece text) = 0;
+  virtual std::string Placeholder(android::StringPiece text) = 0;
 };
 
 class Pseudolocalizer {
@@ -47,7 +47,7 @@
   void SetMethod(Method method);
   std::string Start() { return impl_->Start(); }
   std::string End() { return impl_->End(); }
-  std::string Text(const android::StringPiece& text);
+  std::string Text(android::StringPiece text);
 
  private:
   std::unique_ptr<PseudoMethodImpl> impl_;
diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp
index 6bba11e..1b03253 100644
--- a/tools/aapt2/configuration/ConfigurationParser.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser.cpp
@@ -152,7 +152,7 @@
  * success, or false if the either the placeholder is not found in the name, or the value is not
  * present and the placeholder was.
  */
-bool ReplacePlaceholder(const StringPiece& placeholder, const std::optional<StringPiece>& value,
+bool ReplacePlaceholder(StringPiece placeholder, const std::optional<StringPiece>& value,
                         std::string* name, android::IDiagnostics* diag) {
   size_t offset = name->find(placeholder.data());
   bool found = (offset != std::string::npos);
@@ -338,17 +338,17 @@
   return {config};
 }
 
-const StringPiece& AbiToString(Abi abi) {
+StringPiece AbiToString(Abi abi) {
   return kAbiToStringMap.at(static_cast<size_t>(abi));
 }
 
 /**
  * Returns the common artifact base name from a template string.
  */
-std::optional<std::string> ToBaseName(std::string result, const StringPiece& apk_name,
+std::optional<std::string> ToBaseName(std::string result, StringPiece apk_name,
                                       android::IDiagnostics* diag) {
   const StringPiece ext = file::GetExtension(apk_name);
-  size_t end_index = apk_name.to_string().rfind(ext.to_string());
+  size_t end_index = apk_name.rfind(ext);
   const std::string base_name =
       (end_index != std::string::npos) ? std::string{apk_name.begin(), end_index} : "";
 
@@ -371,17 +371,17 @@
     // If no extension is specified, and the name template does not end in the current extension,
     // add the existing extension.
     if (!util::EndsWith(result, ext)) {
-      result.append(ext.to_string());
+      result.append(ext);
     }
   }
 
   return result;
 }
 
-std::optional<std::string> ConfiguredArtifact::ToArtifactName(const StringPiece& format,
-                                                              const StringPiece& apk_name,
+std::optional<std::string> ConfiguredArtifact::ToArtifactName(StringPiece format,
+                                                              StringPiece apk_name,
                                                               android::IDiagnostics* diag) const {
-  std::optional<std::string> base = ToBaseName(format.to_string(), apk_name, diag);
+  std::optional<std::string> base = ToBaseName(std::string(format), apk_name, diag);
   if (!base) {
     return {};
   }
@@ -414,7 +414,7 @@
   return result;
 }
 
-std::optional<std::string> ConfiguredArtifact::Name(const StringPiece& apk_name,
+std::optional<std::string> ConfiguredArtifact::Name(StringPiece apk_name,
                                                     android::IDiagnostics* diag) const {
   if (!name) {
     return {};
@@ -439,7 +439,7 @@
 }
 
 std::optional<std::vector<OutputArtifact>> ConfigurationParser::Parse(
-    const android::StringPiece& apk_path) {
+    android::StringPiece apk_path) {
   std::optional<PostProcessingConfiguration> maybe_config =
       ExtractConfiguration(contents_, config_path_, diag_);
   if (!maybe_config) {
@@ -447,7 +447,7 @@
   }
 
   // Convert from a parsed configuration to a list of artifacts for processing.
-  const std::string& apk_name = file::GetFilename(apk_path).to_string();
+  const std::string apk_name(file::GetFilename(apk_path));
   std::vector<OutputArtifact> output_artifacts;
 
   PostProcessingConfiguration& config = maybe_config.value();
@@ -519,7 +519,7 @@
   for (auto& node : root_element->children) {
     xml::Text* t;
     if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
-      config->artifact_format = TrimWhitespace(t->text).to_string();
+      config->artifact_format.emplace(TrimWhitespace(t->text));
       break;
     }
   }
@@ -561,7 +561,7 @@
       for (auto& node : child->children) {
         xml::Text* t;
         if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
-          auto abi = kStringToAbiMap.find(TrimWhitespace(t->text).to_string());
+          auto abi = kStringToAbiMap.find(TrimWhitespace(t->text));
           if (abi != kStringToAbiMap.end()) {
             group.push_back(abi->second);
           } else {
@@ -622,7 +622,7 @@
         xml::Text* t;
         if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
           ConfigDescription config_descriptor;
-          const android::StringPiece& text = TrimWhitespace(t->text);
+          android::StringPiece text = TrimWhitespace(t->text);
           bool parsed = ConfigDescription::Parse(text, &config_descriptor);
           if (parsed &&
               (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) ==
@@ -688,7 +688,7 @@
         xml::Text* t;
         if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
           ConfigDescription config_descriptor;
-          const android::StringPiece& text = TrimWhitespace(t->text);
+          android::StringPiece text = TrimWhitespace(t->text);
           bool parsed = ConfigDescription::Parse(text, &config_descriptor);
           if (parsed &&
               (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) ==
@@ -806,7 +806,7 @@
         for (auto& node : element->children) {
           xml::Text* t;
           if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
-            result.texture_paths.push_back(TrimWhitespace(t->text).to_string());
+            result.texture_paths.emplace_back(TrimWhitespace(t->text));
           }
         }
       }
@@ -843,7 +843,7 @@
       for (auto& node : child->children) {
         xml::Text* t;
         if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
-          group.push_back(TrimWhitespace(t->text).to_string());
+          group.emplace_back(TrimWhitespace(t->text));
           break;
         }
       }
diff --git a/tools/aapt2/configuration/ConfigurationParser.h b/tools/aapt2/configuration/ConfigurationParser.h
index 2c8221d..d66f4ab 100644
--- a/tools/aapt2/configuration/ConfigurationParser.h
+++ b/tools/aapt2/configuration/ConfigurationParser.h
@@ -43,7 +43,7 @@
 };
 
 /** Helper method to convert an ABI to a string representing the path within the APK. */
-const android::StringPiece& AbiToString(Abi abi);
+android::StringPiece AbiToString(Abi abi);
 
 /**
  * Represents an individual locale. When a locale is included, it must be
@@ -150,8 +150,7 @@
    * Parses the configuration file and returns the results. If the configuration could not be parsed
    * the result is empty and any errors will be displayed with the provided diagnostics context.
    */
-  std::optional<std::vector<configuration::OutputArtifact>> Parse(
-      const android::StringPiece& apk_path);
+  std::optional<std::vector<configuration::OutputArtifact>> Parse(android::StringPiece apk_path);
 
  protected:
   /**
diff --git a/tools/aapt2/configuration/ConfigurationParser.internal.h b/tools/aapt2/configuration/ConfigurationParser.internal.h
index 3028c3f..198f730 100644
--- a/tools/aapt2/configuration/ConfigurationParser.internal.h
+++ b/tools/aapt2/configuration/ConfigurationParser.internal.h
@@ -138,13 +138,12 @@
   std::optional<std::string> gl_texture_group;
 
   /** Convert an artifact name template into a name string based on configuration contents. */
-  std::optional<std::string> ToArtifactName(const android::StringPiece& format,
-                                            const android::StringPiece& apk_name,
+  std::optional<std::string> ToArtifactName(android::StringPiece format,
+                                            android::StringPiece apk_name,
                                             android::IDiagnostics* diag) const;
 
   /** Convert an artifact name template into a name string based on configuration contents. */
-  std::optional<std::string> Name(const android::StringPiece& apk_name,
-                                  android::IDiagnostics* diag) const;
+  std::optional<std::string> Name(android::StringPiece apk_name, android::IDiagnostics* diag) const;
 };
 
 /** AAPT2 XML configuration file binary representation. */
diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp
index c4c002d..d60869a 100644
--- a/tools/aapt2/dump/DumpManifest.cpp
+++ b/tools/aapt2/dump/DumpManifest.cpp
@@ -1076,7 +1076,7 @@
 
   /** Adds a feature to the feature group. */
   void AddFeature(const std::string& name, bool required = true, int32_t version = -1) {
-    features_.insert(std::make_pair(name, Feature{ required, version }));
+    features_.insert_or_assign(name, Feature{required, version});
     if (required) {
       if (name == "android.hardware.camera.autofocus" ||
           name == "android.hardware.camera.flash") {
@@ -1348,6 +1348,11 @@
   std::string impliedReason;
 
   void Extract(xml::Element* element) override {
+    const auto parent_stack = extractor()->parent_stack();
+    if (!extractor()->options_.only_permissions &&
+        (parent_stack.size() != 1 || !ElementCast<Manifest>(parent_stack[0]))) {
+      return;
+    }
     name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), "");
     std::string feature =
         GetAttributeStringDefault(FindAttribute(element, REQUIRED_FEATURE_ATTR), "");
@@ -1472,6 +1477,11 @@
   const int32_t* maxSdkVersion = nullptr;
 
   void Extract(xml::Element* element) override {
+    const auto parent_stack = extractor()->parent_stack();
+    if (!extractor()->options_.only_permissions &&
+        (parent_stack.size() != 1 || !ElementCast<Manifest>(parent_stack[0]))) {
+      return;
+    }
     name = GetAttributeString(FindAttribute(element, NAME_ATTR));
     maxSdkVersion = GetAttributeInteger(FindAttribute(element, MAX_SDK_VERSION_ATTR));
 
diff --git a/tools/aapt2/filter/AbiFilter.cpp b/tools/aapt2/filter/AbiFilter.cpp
index 9ace82a..908b171 100644
--- a/tools/aapt2/filter/AbiFilter.cpp
+++ b/tools/aapt2/filter/AbiFilter.cpp
@@ -23,15 +23,15 @@
 namespace aapt {
 
 std::unique_ptr<AbiFilter> AbiFilter::FromAbiList(const std::vector<configuration::Abi>& abi_list) {
-  std::unordered_set<std::string> abi_set;
+  std::unordered_set<std::string_view> abi_set;
   for (auto& abi : abi_list) {
-    abi_set.insert(configuration::AbiToString(abi).to_string());
+    abi_set.insert(configuration::AbiToString(abi));
   }
   // Make unique by hand as the constructor is private.
-  return std::unique_ptr<AbiFilter>(new AbiFilter(abi_set));
+  return std::unique_ptr<AbiFilter>(new AbiFilter(std::move(abi_set)));
 }
 
-bool AbiFilter::Keep(const std::string& path) {
+bool AbiFilter::Keep(std::string_view path) {
   // We only care about libraries.
   if (!util::StartsWith(path, kLibPrefix)) {
     return true;
@@ -44,7 +44,7 @@
   }
 
   // Strip the lib/ prefix.
-  const std::string& path_abi = path.substr(kLibPrefixLen, abi_end - kLibPrefixLen);
+  const auto path_abi = path.substr(kLibPrefixLen, abi_end - kLibPrefixLen);
   return (abis_.find(path_abi) != abis_.end());
 }
 
diff --git a/tools/aapt2/filter/AbiFilter.h b/tools/aapt2/filter/AbiFilter.h
index 2832711..7380f3f 100644
--- a/tools/aapt2/filter/AbiFilter.h
+++ b/tools/aapt2/filter/AbiFilter.h
@@ -18,7 +18,7 @@
 #define AAPT2_ABISPLITTER_H
 
 #include <memory>
-#include <string>
+#include <string_view>
 #include <unordered_set>
 #include <vector>
 
@@ -39,16 +39,16 @@
   static std::unique_ptr<AbiFilter> FromAbiList(const std::vector<configuration::Abi>& abi_list);
 
   /** Returns true if the path is for a native library in the list of desired ABIs. */
-  bool Keep(const std::string& path) override;
+  bool Keep(std::string_view path) override;
 
  private:
-  explicit AbiFilter(std::unordered_set<std::string> abis) : abis_(std::move(abis)) {
+  explicit AbiFilter(std::unordered_set<std::string_view> abis) : abis_(std::move(abis)) {
   }
 
   /** The path prefix to where all native libs end up inside an APK file. */
   static constexpr const char* kLibPrefix = "lib/";
   static constexpr size_t kLibPrefixLen = 4;
-  const std::unordered_set<std::string> abis_;
+  const std::unordered_set<std::string_view> abis_;
 };
 
 }  // namespace aapt
diff --git a/tools/aapt2/filter/Filter.h b/tools/aapt2/filter/Filter.h
index f932f9c..baf4791 100644
--- a/tools/aapt2/filter/Filter.h
+++ b/tools/aapt2/filter/Filter.h
@@ -18,6 +18,7 @@
 #define AAPT2_FILTER_H
 
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include "util/Util.h"
@@ -30,7 +31,7 @@
   virtual ~IPathFilter() = default;
 
   /** Returns true if the path should be kept. */
-  virtual bool Keep(const std::string& path) = 0;
+  virtual bool Keep(std::string_view path) = 0;
 };
 
 /**
@@ -42,7 +43,7 @@
   }
 
   /** Returns true if the provided path matches the prefix. */
-  bool Keep(const std::string& path) override {
+  bool Keep(std::string_view path) override {
     return util::StartsWith(path, prefix_);
   }
 
@@ -59,7 +60,7 @@
   }
 
   /** Returns true if all filters keep the path. */
-  bool Keep(const std::string& path) override {
+  bool Keep(std::string_view path) override {
     for (auto& filter : filters_) {
       if (!filter->Keep(path)) {
         return false;
diff --git a/tools/aapt2/format/Archive.cpp b/tools/aapt2/format/Archive.cpp
index 80c1618..e9a93d8 100644
--- a/tools/aapt2/format/Archive.cpp
+++ b/tools/aapt2/format/Archive.cpp
@@ -40,8 +40,8 @@
  public:
   DirectoryWriter() = default;
 
-  bool Open(const StringPiece& out_dir) {
-    dir_ = out_dir.to_string();
+  bool Open(StringPiece out_dir) {
+    dir_ = std::string(out_dir);
     file::FileType type = file::GetFileType(dir_);
     if (type == file::FileType::kNonExistant) {
       error_ = "directory does not exist";
@@ -53,14 +53,14 @@
     return true;
   }
 
-  bool StartEntry(const StringPiece& path, uint32_t flags) override {
+  bool StartEntry(StringPiece path, uint32_t flags) override {
     if (file_) {
       return false;
     }
 
     std::string full_path = dir_;
     file::AppendPath(&full_path, path);
-    file::mkdirs(file::GetStem(full_path).to_string());
+    file::mkdirs(std::string(file::GetStem(full_path)));
 
     file_ = {::android::base::utf8::fopen(full_path.c_str(), "wb"), fclose};
     if (!file_) {
@@ -91,7 +91,7 @@
     return true;
   }
 
-  bool WriteFile(const StringPiece& path, uint32_t flags, io::InputStream* in) override {
+  bool WriteFile(StringPiece path, uint32_t flags, io::InputStream* in) override {
     if (!StartEntry(path, flags)) {
       return false;
     }
@@ -132,8 +132,8 @@
  public:
   ZipFileWriter() = default;
 
-  bool Open(const StringPiece& path) {
-    file_ = {::android::base::utf8::fopen(path.to_string().c_str(), "w+b"), fclose};
+  bool Open(StringPiece path) {
+    file_ = {::android::base::utf8::fopen(path.data(), "w+b"), fclose};
     if (!file_) {
       error_ = SystemErrorCodeToString(errno);
       return false;
@@ -142,7 +142,7 @@
     return true;
   }
 
-  bool StartEntry(const StringPiece& path, uint32_t flags) override {
+  bool StartEntry(StringPiece path, uint32_t flags) override {
     if (!writer_) {
       return false;
     }
@@ -182,7 +182,7 @@
     return true;
   }
 
-  bool WriteFile(const StringPiece& path, uint32_t flags, io::InputStream* in) override {
+  bool WriteFile(StringPiece path, uint32_t flags, io::InputStream* in) override {
     while (true) {
       if (!StartEntry(path, flags)) {
         return false;
@@ -257,7 +257,7 @@
 }  // namespace
 
 std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(android::IDiagnostics* diag,
-                                                             const StringPiece& path) {
+                                                             StringPiece path) {
   std::unique_ptr<DirectoryWriter> writer = util::make_unique<DirectoryWriter>();
   if (!writer->Open(path)) {
     diag->Error(android::DiagMessage(path) << writer->GetError());
@@ -267,7 +267,7 @@
 }
 
 std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(android::IDiagnostics* diag,
-                                                           const StringPiece& path) {
+                                                           StringPiece path) {
   std::unique_ptr<ZipFileWriter> writer = util::make_unique<ZipFileWriter>();
   if (!writer->Open(path)) {
     diag->Error(android::DiagMessage(path) << writer->GetError());
diff --git a/tools/aapt2/format/Archive.h b/tools/aapt2/format/Archive.h
index 55b0b2f..6cde753 100644
--- a/tools/aapt2/format/Archive.h
+++ b/tools/aapt2/format/Archive.h
@@ -46,12 +46,12 @@
  public:
   virtual ~IArchiveWriter() = default;
 
-  virtual bool WriteFile(const android::StringPiece& path, uint32_t flags, io::InputStream* in) = 0;
+  virtual bool WriteFile(android::StringPiece path, uint32_t flags, io::InputStream* in) = 0;
 
   // Starts a new entry and allows caller to write bytes to it sequentially.
   // Only use StartEntry if code you do not control needs to write to a CopyingOutputStream.
   // Prefer WriteFile instead of manually calling StartEntry/FinishEntry.
-  virtual bool StartEntry(const android::StringPiece& path, uint32_t flags) = 0;
+  virtual bool StartEntry(android::StringPiece path, uint32_t flags) = 0;
 
   // Called to finish writing an entry previously started by StartEntry.
   // Prefer WriteFile instead of manually calling StartEntry/FinishEntry.
@@ -70,10 +70,10 @@
 };
 
 std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(android::IDiagnostics* diag,
-                                                             const android::StringPiece& path);
+                                                             android::StringPiece path);
 
 std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(android::IDiagnostics* diag,
-                                                           const android::StringPiece& path);
+                                                           android::StringPiece path);
 
 }  // namespace aapt
 
diff --git a/tools/aapt2/format/Archive_test.cpp b/tools/aapt2/format/Archive_test.cpp
index ceed374..3c44da7 100644
--- a/tools/aapt2/format/Archive_test.cpp
+++ b/tools/aapt2/format/Archive_test.cpp
@@ -50,7 +50,7 @@
 }
 
 std::unique_ptr<IArchiveWriter> MakeZipFileWriter(const std::string& output_path) {
-  file::mkdirs(file::GetStem(output_path).to_string());
+  file::mkdirs(std::string(file::GetStem(output_path)));
   std::remove(output_path.c_str());
 
   StdErrDiagnostics diag;
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index 8291862..75dcba5 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -373,7 +373,7 @@
   std::optional<ResourceNamedTypeRef> parsed_type = ParseResourceNamedType(type_str);
   if (!parsed_type) {
     diag_->Warn(android::DiagMessage(source_)
-                << "invalid type name '" << type_str << "' for type with ID " << type->id);
+                << "invalid type name '" << type_str << "' for type with ID " << int(type->id));
     return true;
   }
 
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index f192234..8c594ba 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -32,6 +32,7 @@
 #include "format/binary/ChunkWriter.h"
 #include "format/binary/ResEntryWriter.h"
 #include "format/binary/ResourceTypeExtensions.h"
+#include "optimize/Obfuscator.h"
 #include "trace/TraceBuffer.h"
 
 using namespace android;
@@ -466,9 +467,6 @@
       // table.
       std::map<ConfigDescription, std::vector<FlatEntry>> config_to_entry_list_map;
 
-      // hardcoded string uses characters which make it an invalid resource name
-      const std::string obfuscated_resource_name = "0_resource_name_obfuscated";
-
       for (const ResourceTableEntryView& entry : type.entries) {
         if (entry.staged_id) {
           aliases_.insert(std::make_pair(
@@ -477,30 +475,31 @@
         }
 
         uint32_t local_key_index;
-        ResourceName resource_name({}, type.named_type, entry.name);
-        if (!collapse_key_stringpool_ ||
-            name_collapse_exemptions_.find(resource_name) != name_collapse_exemptions_.end()) {
-          local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index();
-        } else {
-          // resource isn't exempt from collapse, add it as obfuscated value
-          if (entry.overlayable_item) {
+        auto onObfuscate = [this, &local_key_index, &entry](Obfuscator::Result obfuscatedResult,
+                                                            const ResourceName& resource_name) {
+          if (obfuscatedResult == Obfuscator::Result::Keep_ExemptionList) {
+            local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index();
+          } else if (obfuscatedResult == Obfuscator::Result::Keep_Overlayable) {
             // if the resource name of the specific entry is obfuscated and this
             // entry is in the overlayable list, the overlay can't work on this
             // overlayable at runtime because the name has been obfuscated in
             // resources.arsc during flatten operation.
             const OverlayableItem& item = entry.overlayable_item.value();
             context_->GetDiagnostics()->Warn(android::DiagMessage(item.overlayable->source)
-                                             << "The resource name of overlayable entry "
-                                             << resource_name.to_string() << "'"
-                                             << " shouldn't be obfuscated in resources.arsc");
+                                             << "The resource name of overlayable entry '"
+                                             << resource_name.to_string()
+                                             << "' shouldn't be obfuscated in resources.arsc");
 
             local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index();
           } else {
-            // TODO(b/228192695): output the entry.name and Resource id to make
-            //  de-obfuscated possible.
-            local_key_index = (uint32_t)key_pool_.MakeRef(obfuscated_resource_name).index();
+            local_key_index =
+                (uint32_t)key_pool_.MakeRef(Obfuscator::kObfuscatedResourceName).index();
           }
-        }
+        };
+
+        Obfuscator::ObfuscateResourceName(collapse_key_stringpool_, name_collapse_exemptions_,
+                                          type.named_type, entry, onObfuscate);
+
         // Group values by configuration.
         for (auto& config_value : entry.values) {
           config_to_entry_list_map[config_value->config].push_back(
diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h
index 35254ba..60605d2 100644
--- a/tools/aapt2/format/binary/TableFlattener.h
+++ b/tools/aapt2/format/binary/TableFlattener.h
@@ -14,8 +14,13 @@
  * limitations under the License.
  */
 
-#ifndef AAPT_FORMAT_BINARY_TABLEFLATTENER_H
-#define AAPT_FORMAT_BINARY_TABLEFLATTENER_H
+#ifndef TOOLS_AAPT2_FORMAT_BINARY_TABLEFLATTENER_H_
+#define TOOLS_AAPT2_FORMAT_BINARY_TABLEFLATTENER_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <unordered_map>
 
 #include "Resource.h"
 #include "ResourceTable.h"
@@ -71,6 +76,9 @@
   //
   // This applies only to simple entries (entry->flags & ResTable_entry::FLAG_COMPLEX == 0).
   bool deduplicate_entry_values = false;
+
+  // Map from original resource ids to obfuscated names.
+  std::unordered_map<uint32_t, std::string> id_resource_map;
 };
 
 class TableFlattener : public IResourceTableConsumer {
@@ -82,12 +90,12 @@
   bool Consume(IAaptContext* context, ResourceTable* table) override;
 
  private:
-  DISALLOW_COPY_AND_ASSIGN(TableFlattener);
-
   TableFlattenerOptions options_;
   android::BigBuffer* buffer_;
+
+  DISALLOW_COPY_AND_ASSIGN(TableFlattener);
 };
 
 }  // namespace aapt
 
-#endif /* AAPT_FORMAT_BINARY_TABLEFLATTENER_H */
+#endif  // TOOLS_AAPT2_FORMAT_BINARY_TABLEFLATTENER_H_
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index d08b4a3..0f11685 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -84,7 +84,7 @@
     return ::testing::AssertionSuccess();
   }
 
-  ::testing::AssertionResult Exists(ResTable* table, const StringPiece& expected_name,
+  ::testing::AssertionResult Exists(ResTable* table, StringPiece expected_name,
                                     const ResourceId& expected_id,
                                     const ConfigDescription& expected_config,
                                     const uint8_t expected_data_type, const uint32_t expected_data,
diff --git a/tools/aapt2/format/binary/XmlFlattener.cpp b/tools/aapt2/format/binary/XmlFlattener.cpp
index 983e646..05f9751 100644
--- a/tools/aapt2/format/binary/XmlFlattener.cpp
+++ b/tools/aapt2/format/binary/XmlFlattener.cpp
@@ -79,7 +79,7 @@
   }
 
   void Visit(const xml::Text* node) override {
-    std::string text = util::TrimWhitespace(node->text).to_string();
+    std::string text(util::TrimWhitespace(node->text));
 
     // Skip whitespace only text nodes.
     if (text.empty()) {
@@ -88,10 +88,10 @@
 
     // Compact leading and trailing whitespace into a single space
     if (isspace(node->text[0])) {
-      text = ' ' + text;
+      text.insert(text.begin(), ' ');
     }
-    if (isspace(node->text[node->text.length() - 1])) {
-      text = text + ' ';
+    if (isspace(node->text.back())) {
+      text += ' ';
     }
 
     ChunkWriter writer(buffer_);
@@ -165,7 +165,7 @@
   // We are adding strings to a StringPool whose strings will be sorted and merged with other
   // string pools. That means we can't encode the ID of a string directly. Instead, we defer the
   // writing of the ID here, until after the StringPool is merged and sorted.
-  void AddString(const StringPiece& str, uint32_t priority, android::ResStringPool_ref* dest,
+  void AddString(StringPiece str, uint32_t priority, android::ResStringPool_ref* dest,
                  bool treat_empty_string_as_null = false) {
     if (str.empty() && treat_empty_string_as_null) {
       // Some parts of the runtime treat null differently than empty string.
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index a6d58fd..0e40124 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -18,6 +18,7 @@
 
 #include "ValueVisitor.h"
 #include "androidfw/BigBuffer.h"
+#include "optimize/Obfuscator.h"
 
 using android::ConfigDescription;
 
@@ -366,21 +367,21 @@
       }
       pb_type->set_name(type.named_type.to_string());
 
-      // hardcoded string uses characters which make it an invalid resource name
-      static const char* obfuscated_resource_name = "0_resource_name_obfuscated";
       for (const auto& entry : type.entries) {
         pb::Entry* pb_entry = pb_type->add_entry();
         if (entry.id) {
           pb_entry->mutable_entry_id()->set_id(entry.id.value());
         }
-        ResourceName resource_name({}, type.named_type, entry.name);
-        if (options.collapse_key_stringpool &&
-            options.name_collapse_exemptions.find(resource_name) ==
-            options.name_collapse_exemptions.end()) {
-          pb_entry->set_name(obfuscated_resource_name);
-        } else {
-          pb_entry->set_name(entry.name);
-        }
+        auto onObfuscate = [pb_entry, &entry](Obfuscator::Result obfuscatedResult,
+                                              const ResourceName& resource_name) {
+          pb_entry->set_name(obfuscatedResult == Obfuscator::Result::Obfuscated
+                                 ? Obfuscator::kObfuscatedResourceName
+                                 : entry.name);
+        };
+
+        Obfuscator::ObfuscateResourceName(options.collapse_key_stringpool,
+                                          options.name_collapse_exemptions, type.named_type, entry,
+                                          onObfuscate);
 
         // Write the Visibility struct.
         pb::Visibility* pb_visibility = pb_entry->mutable_visibility();
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index 5adc5e6..ecfdba8 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -35,7 +35,7 @@
 
 class MockFileCollection : public io::IFileCollection {
  public:
-  MOCK_METHOD1(FindFile, io::IFile*(const StringPiece& path));
+  MOCK_METHOD1(FindFile, io::IFile*(StringPiece path));
   MOCK_METHOD0(Iterator, std::unique_ptr<io::IFileCollectionIterator>());
   MOCK_METHOD0(GetDirSeparator, char());
 };
@@ -491,7 +491,7 @@
   EXPECT_THAT(bp->value.data, Eq(ResourceUtils::MakeEmpty()->value.data));
 }
 
-static void ExpectConfigSerializes(const StringPiece& config_str) {
+static void ExpectConfigSerializes(StringPiece config_str) {
   const ConfigDescription expected_config = test::ParseConfigOrDie(config_str);
   pb::Configuration pb_config;
   SerializeConfig(expected_config, &pb_config);
diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h
index 422658a..08d497d 100644
--- a/tools/aapt2/io/File.h
+++ b/tools/aapt2/io/File.h
@@ -101,7 +101,7 @@
  public:
   virtual ~IFileCollection() = default;
 
-  virtual IFile* FindFile(const android::StringPiece& path) = 0;
+  virtual IFile* FindFile(android::StringPiece path) = 0;
   virtual std::unique_ptr<IFileCollectionIterator> Iterator() = 0;
   virtual char GetDirSeparator() = 0;
 };
diff --git a/tools/aapt2/io/FileSystem.cpp b/tools/aapt2/io/FileSystem.cpp
index 3f071af..a64982a 100644
--- a/tools/aapt2/io/FileSystem.cpp
+++ b/tools/aapt2/io/FileSystem.cpp
@@ -67,8 +67,8 @@
   return result;
 }
 
-std::unique_ptr<FileCollection> FileCollection::Create(const android::StringPiece& root,
-                                                        std::string* outError) {
+std::unique_ptr<FileCollection> FileCollection::Create(android::StringPiece root,
+                                                       std::string* outError) {
   std::unique_ptr<FileCollection> collection =
       std::unique_ptr<FileCollection>(new FileCollection());
 
@@ -80,7 +80,7 @@
 
   std::vector<std::string> sorted_files;
   while (struct dirent *entry = readdir(d.get())) {
-    std::string prefix_path = root.to_string();
+    std::string prefix_path(root);
     file::AppendPath(&prefix_path, entry->d_name);
 
     // The directory to iterate over looking for files
@@ -117,12 +117,19 @@
   return collection;
 }
 
-IFile* FileCollection::InsertFile(const StringPiece& path) {
-  return (files_[path.to_string()] = util::make_unique<RegularFile>(android::Source(path))).get();
+IFile* FileCollection::InsertFile(StringPiece path) {
+  auto file = util::make_unique<RegularFile>(android::Source(path));
+  auto it = files_.lower_bound(path);
+  if (it != files_.end() && it->first == path) {
+    it->second = std::move(file);
+  } else {
+    it = files_.emplace_hint(it, path, std::move(file));
+  }
+  return it->second.get();
 }
 
-IFile* FileCollection::FindFile(const StringPiece& path) {
-  auto iter = files_.find(path.to_string());
+IFile* FileCollection::FindFile(StringPiece path) {
+  auto iter = files_.find(path);
   if (iter != files_.end()) {
     return iter->second.get();
   }
diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h
index bc03b9b..0e798fc 100644
--- a/tools/aapt2/io/FileSystem.h
+++ b/tools/aapt2/io/FileSystem.h
@@ -60,12 +60,11 @@
   FileCollection() = default;
 
   /** Creates a file collection containing all files contained in the specified root directory. */
-  static std::unique_ptr<FileCollection> Create(const android::StringPiece& path,
-                                                std::string* outError);
+  static std::unique_ptr<FileCollection> Create(android::StringPiece path, std::string* outError);
 
   // Adds a file located at path. Returns the IFile representation of that file.
-  IFile* InsertFile(const android::StringPiece& path);
-  IFile* FindFile(const android::StringPiece& path) override;
+  IFile* InsertFile(android::StringPiece path);
+  IFile* FindFile(android::StringPiece path) override;
   std::unique_ptr<IFileCollectionIterator> Iterator() override;
   char GetDirSeparator() override;
 
@@ -74,7 +73,7 @@
 
   friend class FileCollectionIterator;
 
-  std::map<std::string, std::unique_ptr<IFile>> files_;
+  std::map<std::string, std::unique_ptr<IFile>, std::less<>> files_;
 };
 
 }  // namespace io
diff --git a/tools/aapt2/io/StringStream.cpp b/tools/aapt2/io/StringStream.cpp
index 4ca04a8..9c49788 100644
--- a/tools/aapt2/io/StringStream.cpp
+++ b/tools/aapt2/io/StringStream.cpp
@@ -21,7 +21,7 @@
 namespace aapt {
 namespace io {
 
-StringInputStream::StringInputStream(const StringPiece& str) : str_(str), offset_(0u) {
+StringInputStream::StringInputStream(StringPiece str) : str_(str), offset_(0u) {
 }
 
 bool StringInputStream::Next(const void** data, size_t* size) {
diff --git a/tools/aapt2/io/StringStream.h b/tools/aapt2/io/StringStream.h
index f29890a..f7bdecca 100644
--- a/tools/aapt2/io/StringStream.h
+++ b/tools/aapt2/io/StringStream.h
@@ -29,7 +29,7 @@
 
 class StringInputStream : public KnownSizeInputStream {
  public:
-  explicit StringInputStream(const android::StringPiece& str);
+  explicit StringInputStream(android::StringPiece str);
 
   bool Next(const void** data, size_t* size) override;
 
diff --git a/tools/aapt2/io/Util.cpp b/tools/aapt2/io/Util.cpp
index afe54d4..79d8d52 100644
--- a/tools/aapt2/io/Util.cpp
+++ b/tools/aapt2/io/Util.cpp
@@ -26,7 +26,7 @@
 namespace aapt {
 namespace io {
 
-bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, const std::string& out_path,
+bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, std::string_view out_path,
                               uint32_t compression_flags, IArchiveWriter* writer) {
   TRACE_CALL();
   if (context->IsVerbose()) {
@@ -43,7 +43,7 @@
   return true;
 }
 
-bool CopyFileToArchive(IAaptContext* context, io::IFile* file, const std::string& out_path,
+bool CopyFileToArchive(IAaptContext* context, io::IFile* file, std::string_view out_path,
                        uint32_t compression_flags, IArchiveWriter* writer) {
   TRACE_CALL();
   std::unique_ptr<io::IData> data = file->OpenAsData();
@@ -56,13 +56,13 @@
 }
 
 bool CopyFileToArchivePreserveCompression(IAaptContext* context, io::IFile* file,
-                                          const std::string& out_path, IArchiveWriter* writer) {
+                                          std::string_view out_path, IArchiveWriter* writer) {
   uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
   return CopyFileToArchive(context, file, out_path, compression_flags, writer);
 }
 
 bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::Message* proto_msg,
-                        const std::string& out_path, uint32_t compression_flags,
+                        std::string_view out_path, uint32_t compression_flags,
                         IArchiveWriter* writer) {
   TRACE_CALL();
   if (context->IsVerbose()) {
@@ -110,7 +110,7 @@
   return !in->HadError();
 }
 
-bool Copy(OutputStream* out, const StringPiece& in) {
+bool Copy(OutputStream* out, StringPiece in) {
   const char* in_buffer = in.data();
   size_t in_len = in.size();
   while (in_len != 0) {
diff --git a/tools/aapt2/io/Util.h b/tools/aapt2/io/Util.h
index 1b48a28..685f522 100644
--- a/tools/aapt2/io/Util.h
+++ b/tools/aapt2/io/Util.h
@@ -17,12 +17,11 @@
 #ifndef AAPT_IO_UTIL_H
 #define AAPT_IO_UTIL_H
 
-#include <string>
-
-#include "google/protobuf/message.h"
-#include "google/protobuf/io/coded_stream.h"
+#include <string_view>
 
 #include "format/Archive.h"
+#include "google/protobuf/io/coded_stream.h"
+#include "google/protobuf/message.h"
 #include "io/File.h"
 #include "io/Io.h"
 #include "process/IResourceTableConsumer.h"
@@ -30,23 +29,23 @@
 namespace aapt {
 namespace io {
 
-bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, const std::string& out_path,
+bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, std::string_view out_path,
                               uint32_t compression_flags, IArchiveWriter* writer);
 
-bool CopyFileToArchive(IAaptContext* context, IFile* file, const std::string& out_path,
+bool CopyFileToArchive(IAaptContext* context, IFile* file, std::string_view out_path,
                        uint32_t compression_flags, IArchiveWriter* writer);
 
 bool CopyFileToArchivePreserveCompression(IAaptContext* context, IFile* file,
-                                          const std::string& out_path, IArchiveWriter* writer);
+                                          std::string_view out_path, IArchiveWriter* writer);
 
 bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::Message* proto_msg,
-                        const std::string& out_path, uint32_t compression_flags,
+                        std::string_view out_path, uint32_t compression_flags,
                         IArchiveWriter* writer);
 
 // Copies the data from in to out. Returns false if there was an error.
 // If there was an error, check the individual streams' HadError/GetError methods.
 bool Copy(OutputStream* out, InputStream* in);
-bool Copy(OutputStream* out, const ::android::StringPiece& in);
+bool Copy(OutputStream* out, android::StringPiece in);
 bool Copy(::google::protobuf::io::ZeroCopyOutputStream* out, InputStream* in);
 
 class OutputStreamAdaptor : public io::OutputStream {
diff --git a/tools/aapt2/io/ZipArchive.cpp b/tools/aapt2/io/ZipArchive.cpp
index 400269c..4a5385d 100644
--- a/tools/aapt2/io/ZipArchive.cpp
+++ b/tools/aapt2/io/ZipArchive.cpp
@@ -91,8 +91,8 @@
 
 ZipFileCollection::ZipFileCollection() : handle_(nullptr) {}
 
-std::unique_ptr<ZipFileCollection> ZipFileCollection::Create(
-    const StringPiece& path, std::string* out_error) {
+std::unique_ptr<ZipFileCollection> ZipFileCollection::Create(StringPiece path,
+                                                             std::string* out_error) {
   TRACE_CALL();
   constexpr static const int32_t kEmptyArchive = -6;
 
@@ -130,8 +130,8 @@
       continue;
     }
 
-    std::unique_ptr<IFile> file = util::make_unique<ZipFile>(
-        collection->handle_, zip_data, android::Source(zip_entry_path, path.to_string()));
+    std::unique_ptr<IFile> file = util::make_unique<ZipFile>(collection->handle_, zip_data,
+                                                             android::Source(zip_entry_path, path));
     collection->files_by_name_[zip_entry_path] = file.get();
     collection->files_.push_back(std::move(file));
   }
@@ -144,8 +144,8 @@
   return collection;
 }
 
-IFile* ZipFileCollection::FindFile(const StringPiece& path) {
-  auto iter = files_by_name_.find(path.to_string());
+IFile* ZipFileCollection::FindFile(StringPiece path) {
+  auto iter = files_by_name_.find(path);
   if (iter != files_by_name_.end()) {
     return iter->second;
   }
diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h
index 78c9c21..c263aa4 100644
--- a/tools/aapt2/io/ZipArchive.h
+++ b/tools/aapt2/io/ZipArchive.h
@@ -61,10 +61,10 @@
 // An IFileCollection that represents a ZIP archive and the entries within it.
 class ZipFileCollection : public IFileCollection {
  public:
-  static std::unique_ptr<ZipFileCollection> Create(const android::StringPiece& path,
+  static std::unique_ptr<ZipFileCollection> Create(android::StringPiece path,
                                                    std::string* outError);
 
-  io::IFile* FindFile(const android::StringPiece& path) override;
+  io::IFile* FindFile(android::StringPiece path) override;
   std::unique_ptr<IFileCollectionIterator> Iterator() override;
   char GetDirSeparator() override;
 
@@ -76,7 +76,7 @@
 
   ZipArchiveHandle handle_;
   std::vector<std::unique_ptr<IFile>> files_;
-  std::map<std::string, IFile*> files_by_name_;
+  std::map<std::string, IFile*, std::less<>> files_by_name_;
 };
 
 }  // namespace io
diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp
index 482d91a..87da09a 100644
--- a/tools/aapt2/java/AnnotationProcessor.cpp
+++ b/tools/aapt2/java/AnnotationProcessor.cpp
@@ -30,7 +30,7 @@
 
 namespace aapt {
 
-StringPiece AnnotationProcessor::ExtractFirstSentence(const StringPiece& comment) {
+StringPiece AnnotationProcessor::ExtractFirstSentence(StringPiece comment) {
   Utf8Iterator iter(comment);
   while (iter.HasNext()) {
     const char32_t codepoint = iter.Next();
@@ -62,7 +62,7 @@
 }};
 
 void AnnotationProcessor::AppendCommentLine(std::string comment) {
-  static const std::string sDeprecated = "@deprecated";
+  static constexpr std::string_view sDeprecated = "@deprecated";
 
   // Treat deprecated specially, since we don't remove it from the source comment.
   if (comment.find(sDeprecated) != std::string::npos) {
@@ -74,7 +74,7 @@
     if (idx != std::string::npos) {
       // Captures all parameters associated with the specified annotation rule
       // by matching the first pair of parantheses after the rule.
-      std::regex re(rule.doc_str.to_string() + "\\s*\\((.+)\\)");
+      std::regex re(std::string(rule.doc_str) += "\\s*\\((.+)\\)");
       std::smatch match_result;
       const bool is_match = std::regex_search(comment, match_result, re);
       // We currently only capture and preserve parameters for SystemApi.
@@ -97,7 +97,7 @@
 
   // If there was trimming to do, copy the string.
   if (trimmed.size() != comment.size()) {
-    comment = trimmed.to_string();
+    comment = std::string(trimmed);
   }
 
   if (!has_comments_) {
@@ -107,12 +107,12 @@
   comment_ << "\n * " << std::move(comment);
 }
 
-void AnnotationProcessor::AppendComment(const StringPiece& comment) {
+void AnnotationProcessor::AppendComment(StringPiece comment) {
   // We need to process line by line to clean-up whitespace and append prefixes.
   for (StringPiece line : util::Tokenize(comment, '\n')) {
     line = util::TrimWhitespace(line);
     if (!line.empty()) {
-      AppendCommentLine(line.to_string());
+      AppendCommentLine(std::string(line));
     }
   }
 }
@@ -126,7 +126,7 @@
 void AnnotationProcessor::Print(Printer* printer, bool strip_api_annotations) const {
   if (has_comments_) {
     std::string result = comment_.str();
-    for (const StringPiece& line : util::Tokenize(result, '\n')) {
+    for (StringPiece line : util::Tokenize(result, '\n')) {
       printer->Println(line);
     }
     printer->Println(" */");
diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h
index f217afb..db3437e 100644
--- a/tools/aapt2/java/AnnotationProcessor.h
+++ b/tools/aapt2/java/AnnotationProcessor.h
@@ -56,11 +56,11 @@
   // Extracts the first sentence of a comment. The algorithm selects the substring starting from
   // the beginning of the string, and ending at the first '.' character that is followed by a
   // whitespace character. If these requirements are not met, the whole string is returned.
-  static android::StringPiece ExtractFirstSentence(const android::StringPiece& comment);
+  static android::StringPiece ExtractFirstSentence(android::StringPiece comment);
 
   // Adds more comments. Resources can have value definitions for various configurations, and
   // each of the definitions may have comments that need to be processed.
-  void AppendComment(const android::StringPiece& comment);
+  void AppendComment(android::StringPiece comment);
 
   void AppendNewLine();
 
diff --git a/tools/aapt2/java/ClassDefinition.cpp b/tools/aapt2/java/ClassDefinition.cpp
index 3163497..98f3bd2 100644
--- a/tools/aapt2/java/ClassDefinition.cpp
+++ b/tools/aapt2/java/ClassDefinition.cpp
@@ -27,8 +27,8 @@
   processor_.Print(printer, strip_api_annotations);
 }
 
-void MethodDefinition::AppendStatement(const StringPiece& statement) {
-  statements_.push_back(statement.to_string());
+void MethodDefinition::AppendStatement(StringPiece statement) {
+  statements_.emplace_back(statement);
 }
 
 void MethodDefinition::Print(bool final, Printer* printer, bool) const {
@@ -110,8 +110,8 @@
     " * should not be modified by hand.\n"
     " */\n\n";
 
-void ClassDefinition::WriteJavaFile(const ClassDefinition* def, const StringPiece& package,
-                                    bool final, bool strip_api_annotations, io::OutputStream* out) {
+void ClassDefinition::WriteJavaFile(const ClassDefinition* def, StringPiece package, bool final,
+                                    bool strip_api_annotations, io::OutputStream* out) {
   Printer printer(out);
   printer.Print(sWarningHeader).Print("package ").Print(package).Println(";");
   printer.Println();
diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h
index 2acdadb..63c9982 100644
--- a/tools/aapt2/java/ClassDefinition.h
+++ b/tools/aapt2/java/ClassDefinition.h
@@ -59,8 +59,8 @@
 template <typename T>
 class PrimitiveMember : public ClassMember {
  public:
-  PrimitiveMember(const android::StringPiece& name, const T& val, bool staged_api = false)
-      : name_(name.to_string()), val_(val), staged_api_(staged_api) {
+  PrimitiveMember(android::StringPiece name, const T& val, bool staged_api = false)
+      : name_(name), val_(val), staged_api_(staged_api) {
   }
 
   bool empty() const override {
@@ -104,8 +104,8 @@
 template <>
 class PrimitiveMember<std::string> : public ClassMember {
  public:
-  PrimitiveMember(const android::StringPiece& name, const std::string& val, bool staged_api = false)
-      : name_(name.to_string()), val_(val) {
+  PrimitiveMember(android::StringPiece name, const std::string& val, bool staged_api = false)
+      : name_(name), val_(val) {
   }
 
   bool empty() const override {
@@ -141,7 +141,8 @@
 template <typename T, typename StringConverter>
 class PrimitiveArrayMember : public ClassMember {
  public:
-  explicit PrimitiveArrayMember(const android::StringPiece& name) : name_(name.to_string()) {}
+  explicit PrimitiveArrayMember(android::StringPiece name) : name_(name) {
+  }
 
   void AddElement(const T& val) {
     elements_.emplace_back(val);
@@ -209,12 +210,12 @@
 class MethodDefinition : public ClassMember {
  public:
   // Expected method signature example: 'public static void onResourcesLoaded(int p)'.
-  explicit MethodDefinition(const android::StringPiece& signature)
-      : signature_(signature.to_string()) {}
+  explicit MethodDefinition(android::StringPiece signature) : signature_(signature) {
+  }
 
   // Appends a single statement to the method. It should include no newlines or else
   // formatting may be broken.
-  void AppendStatement(const android::StringPiece& statement);
+  void AppendStatement(android::StringPiece statement);
 
   // Not quite the same as a name, but good enough.
   const std::string& GetName() const override {
@@ -239,11 +240,12 @@
 
 class ClassDefinition : public ClassMember {
  public:
-  static void WriteJavaFile(const ClassDefinition* def, const android::StringPiece& package,
-                            bool final, bool strip_api_annotations, io::OutputStream* out);
+  static void WriteJavaFile(const ClassDefinition* def, android::StringPiece package, bool final,
+                            bool strip_api_annotations, io::OutputStream* out);
 
-  ClassDefinition(const android::StringPiece& name, ClassQualifier qualifier, bool createIfEmpty)
-      : name_(name.to_string()), qualifier_(qualifier), create_if_empty_(createIfEmpty) {}
+  ClassDefinition(android::StringPiece name, ClassQualifier qualifier, bool createIfEmpty)
+      : name_(name), qualifier_(qualifier), create_if_empty_(createIfEmpty) {
+  }
 
   enum class Result {
     kAdded,
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
index a25ca22..7665d0e 100644
--- a/tools/aapt2/java/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -57,14 +57,14 @@
     "transient",  "try",          "void",      "volatile",   "while",
     "true",       "false",        "null"};
 
-static bool IsValidSymbol(const StringPiece& symbol) {
+static bool IsValidSymbol(StringPiece symbol) {
   return sJavaIdentifiers.find(symbol) == sJavaIdentifiers.end();
 }
 
 // Java symbols can not contain . or -, but those are valid in a resource name.
 // Replace those with '_'.
-std::string JavaClassGenerator::TransformToFieldName(const StringPiece& symbol) {
-  std::string output = symbol.to_string();
+std::string JavaClassGenerator::TransformToFieldName(StringPiece symbol) {
+  std::string output(symbol);
   for (char& c : output) {
     if (c == '.' || c == '-') {
       c = '_';
@@ -84,7 +84,7 @@
 // Foo_bar
 static std::string TransformNestedAttr(const ResourceNameRef& attr_name,
                                        const std::string& styleable_class_name,
-                                       const StringPiece& package_name_to_generate) {
+                                       StringPiece package_name_to_generate) {
   std::string output = styleable_class_name;
 
   // We may reference IDs from other packages, so prefix the entry name with
@@ -226,16 +226,15 @@
 
 static FieldReference GetRFieldReference(const ResourceName& name,
                                          StringPiece fallback_package_name) {
-  const std::string package_name =
-      name.package.empty() ? fallback_package_name.to_string() : name.package;
+  const std::string_view package_name = name.package.empty() ? fallback_package_name : name.package;
   const std::string entry = JavaClassGenerator::TransformToFieldName(name.entry);
-  return FieldReference(StringPrintf("%s.R.%s.%s", package_name.c_str(),
-                                     name.type.to_string().data(), entry.c_str()));
+  return FieldReference(
+      StringPrintf("%s.R.%s.%s", package_name.data(), name.type.to_string().data(), entry.c_str()));
 }
 
 bool JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const ResourceId& id,
                                           const Styleable& styleable,
-                                          const StringPiece& package_name_to_generate,
+                                          StringPiece package_name_to_generate,
                                           ClassDefinition* out_class_def,
                                           MethodDefinition* out_rewrite_method,
                                           Printer* r_txt_printer) {
@@ -314,7 +313,8 @@
         return true;
       }
       const StringPiece attr_comment_line = entry.symbol.value().attribute->GetComment();
-      return attr_comment_line.contains("@removed") || attr_comment_line.contains("@hide");
+      return attr_comment_line.find("@removed") != std::string::npos ||
+             attr_comment_line.find("@hide") != std::string::npos;
     });
     documentation_attrs.erase(documentation_remove_iter, documentation_attrs.end());
 
@@ -397,7 +397,7 @@
         comment = styleable_attr.symbol.value().attribute->GetComment();
       }
 
-      if (comment.contains("@removed")) {
+      if (comment.find("@removed") != std::string::npos) {
         // Removed attributes are public but hidden from the documentation, so
         // don't emit them as part of the class documentation.
         continue;
@@ -497,7 +497,7 @@
   }
 
   if (out_rewrite_method != nullptr) {
-    const std::string type_str = name.type.to_string();
+    const auto type_str = name.type.to_string();
     out_rewrite_method->AppendStatement(
         StringPrintf("%s.%s = (%s.%s & 0x00ffffff) | packageIdBits;", type_str.data(),
                      field_name.data(), type_str.data(), field_name.data()));
@@ -505,8 +505,7 @@
 }
 
 std::optional<std::string> JavaClassGenerator::UnmangleResource(
-    const StringPiece& package_name, const StringPiece& package_name_to_generate,
-    const ResourceEntry& entry) {
+    StringPiece package_name, StringPiece package_name_to_generate, const ResourceEntry& entry) {
   if (SkipSymbol(entry.visibility.level)) {
     return {};
   }
@@ -528,7 +527,7 @@
   return {std::move(unmangled_name)};
 }
 
-bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate,
+bool JavaClassGenerator::ProcessType(StringPiece package_name_to_generate,
                                      const ResourceTablePackage& package,
                                      const ResourceTableType& type,
                                      ClassDefinition* out_type_class_def,
@@ -577,7 +576,7 @@
   return true;
 }
 
-bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, OutputStream* out,
+bool JavaClassGenerator::Generate(StringPiece package_name_to_generate, OutputStream* out,
                                   OutputStream* out_r_txt) {
   return Generate(package_name_to_generate, package_name_to_generate, out, out_r_txt);
 }
@@ -591,8 +590,8 @@
   }
 }
 
-bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate,
-                                  const StringPiece& out_package_name, OutputStream* out,
+bool JavaClassGenerator::Generate(StringPiece package_name_to_generate,
+                                  StringPiece out_package_name, OutputStream* out,
                                   OutputStream* out_r_txt) {
   ClassDefinition r_class("R", ClassQualifier::kNone, true);
   std::unique_ptr<MethodDefinition> rewrite_method;
diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h
index b45a2f1..234df04 100644
--- a/tools/aapt2/java/JavaClassGenerator.h
+++ b/tools/aapt2/java/JavaClassGenerator.h
@@ -70,16 +70,16 @@
   // All symbols technically belong to a single package, but linked libraries will
   // have their names mangled, denoting that they came from a different package.
   // We need to generate these symbols in a separate file. Returns true on success.
-  bool Generate(const android::StringPiece& package_name_to_generate, io::OutputStream* out,
+  bool Generate(android::StringPiece package_name_to_generate, io::OutputStream* out,
                 io::OutputStream* out_r_txt = nullptr);
 
-  bool Generate(const android::StringPiece& package_name_to_generate,
-                const android::StringPiece& output_package_name, io::OutputStream* out,
+  bool Generate(android::StringPiece package_name_to_generate,
+                android::StringPiece output_package_name, io::OutputStream* out,
                 io::OutputStream* out_r_txt = nullptr);
 
   const std::string& GetError() const;
 
-  static std::string TransformToFieldName(const android::StringPiece& symbol);
+  static std::string TransformToFieldName(android::StringPiece symbol);
 
  private:
   bool SkipSymbol(Visibility::Level state);
@@ -87,11 +87,11 @@
 
   // Returns the unmangled resource entry name if the unmangled package is the same as
   // package_name_to_generate. Returns nothing if the resource should be skipped.
-  std::optional<std::string> UnmangleResource(const android::StringPiece& package_name,
-                                              const android::StringPiece& package_name_to_generate,
+  std::optional<std::string> UnmangleResource(android::StringPiece package_name,
+                                              android::StringPiece package_name_to_generate,
                                               const ResourceEntry& entry);
 
-  bool ProcessType(const android::StringPiece& package_name_to_generate,
+  bool ProcessType(android::StringPiece package_name_to_generate,
                    const ResourceTablePackage& package, const ResourceTableType& type,
                    ClassDefinition* out_type_class_def, MethodDefinition* out_rewrite_method_def,
                    text::Printer* r_txt_printer);
@@ -106,8 +106,7 @@
   // its package ID if `out_rewrite_method` is not nullptr.
   // `package_name_to_generate` is the package
   bool ProcessStyleable(const ResourceNameRef& name, const ResourceId& id,
-                        const Styleable& styleable,
-                        const android::StringPiece& package_name_to_generate,
+                        const Styleable& styleable, android::StringPiece package_name_to_generate,
                         ClassDefinition* out_class_def, MethodDefinition* out_rewrite_method,
                         text::Printer* r_txt_printer);
 
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index d0850b8..56d9075 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -646,8 +646,8 @@
   return true;
 }
 
-static void FullyQualifyClassName(const StringPiece& package, const StringPiece& attr_ns,
-                                  const StringPiece& attr_name, xml::Element* el) {
+static void FullyQualifyClassName(StringPiece package, StringPiece attr_ns, StringPiece attr_name,
+                                  xml::Element* el) {
   xml::Attribute* attr = el->FindAttribute(attr_ns, attr_name);
   if (attr != nullptr) {
     if (std::optional<std::string> new_value =
@@ -657,7 +657,7 @@
   }
 }
 
-static bool RenameManifestPackage(const StringPiece& package_override, xml::Element* manifest_el) {
+static bool RenameManifestPackage(StringPiece package_override, xml::Element* manifest_el) {
   xml::Attribute* attr = manifest_el->FindAttribute({}, "package");
 
   // We've already verified that the manifest element is present, with a package
@@ -665,7 +665,7 @@
   CHECK(attr != nullptr);
 
   std::string original_package = std::move(attr->value);
-  attr->value = package_override.to_string();
+  attr->value.assign(package_override);
 
   xml::Element* application_el = manifest_el->FindChild({}, "application");
   if (application_el != nullptr) {
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 8d1a647..7180ae6 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -61,12 +61,12 @@
             .Build();
   }
 
-  std::unique_ptr<xml::XmlResource> Verify(const StringPiece& str) {
+  std::unique_ptr<xml::XmlResource> Verify(StringPiece str) {
     return VerifyWithOptions(str, {});
   }
 
-  std::unique_ptr<xml::XmlResource> VerifyWithOptions(
-      const StringPiece& str, const ManifestFixerOptions& options) {
+  std::unique_ptr<xml::XmlResource> VerifyWithOptions(StringPiece str,
+                                                      const ManifestFixerOptions& options) {
     std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(str);
     ManifestFixer fixer(options);
     if (fixer.Consume(mContext.get(), doc.get())) {
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index f2a93a8..9dadfb2 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -189,8 +189,7 @@
  public:
   EmptyDeclStack() = default;
 
-  std::optional<xml::ExtractedPackage> TransformPackageAlias(
-      const StringPiece& alias) const override {
+  std::optional<xml::ExtractedPackage> TransformPackageAlias(StringPiece alias) const override {
     if (alias.empty()) {
       return xml::ExtractedPackage{{}, true /*private*/};
     }
@@ -206,8 +205,7 @@
       : alias_namespaces_(std::move(namespaces)) {
   }
 
-  std::optional<xml::ExtractedPackage> TransformPackageAlias(
-      const StringPiece& alias) const override {
+  std::optional<xml::ExtractedPackage> TransformPackageAlias(StringPiece alias) const override {
     if (alias.empty()) {
       return xml::ExtractedPackage{{}, true /*private*/};
     }
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index c9f0964..67a4828 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -66,7 +66,7 @@
 
 // This will merge and mangle resources from a static library. It is assumed that all FileReferences
 // have correctly set their io::IFile*.
-bool TableMerger::MergeAndMangle(const android::Source& src, const StringPiece& package_name,
+bool TableMerger::MergeAndMangle(const android::Source& src, StringPiece package_name,
                                  ResourceTable* table) {
   bool error = false;
   for (auto& package : table->packages) {
@@ -326,8 +326,8 @@
     const std::string& package, const FileReference& file_ref) {
   StringPiece prefix, entry, suffix;
   if (util::ExtractResFilePathParts(*file_ref.path, &prefix, &entry, &suffix)) {
-    std::string mangled_entry = NameMangler::MangleEntry(package, entry.to_string());
-    std::string newPath = prefix.to_string() + mangled_entry + suffix.to_string();
+    std::string mangled_entry = NameMangler::MangleEntry(package, entry);
+    std::string newPath = (std::string(prefix) += mangled_entry) += suffix;
     std::unique_ptr<FileReference> new_file_ref =
         util::make_unique<FileReference>(main_table_->string_pool.MakeRef(newPath));
     new_file_ref->SetComment(file_ref.GetComment());
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
index 2ba2123..37daf42 100644
--- a/tools/aapt2/link/TableMerger.h
+++ b/tools/aapt2/link/TableMerger.h
@@ -61,7 +61,7 @@
   // References are made to this ResourceTable for efficiency reasons.
   TableMerger(IAaptContext* context, ResourceTable* out_table, const TableMergerOptions& options);
 
-  inline const std::set<std::string>& merged_packages() const {
+  inline const std::set<std::string, std::less<>>& merged_packages() const {
     return merged_packages_;
   }
 
@@ -71,7 +71,7 @@
 
   // Merges resources from the given package, mangling the name. This is for static libraries.
   // All FileReference values must have their io::IFile set.
-  bool MergeAndMangle(const android::Source& src, const android::StringPiece& package,
+  bool MergeAndMangle(const android::Source& src, android::StringPiece package,
                       ResourceTable* table);
 
   // Merges a compiled file that belongs to this same or empty package.
@@ -84,7 +84,7 @@
   ResourceTable* main_table_;
   TableMergerOptions options_;
   ResourceTablePackage* main_package_;
-  std::set<std::string> merged_packages_;
+  std::set<std::string, std::less<>> merged_packages_;
 
   bool MergeImpl(const android::Source& src, ResourceTable* src_table, bool overlay,
                  bool allow_new);
diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp
index f994e27..f01db3d 100644
--- a/tools/aapt2/optimize/MultiApkGenerator.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator.cpp
@@ -113,12 +113,12 @@
 };
 
 class SignatureFilter : public IPathFilter {
-  bool Keep(const std::string& path) override {
+  bool Keep(std::string_view path) override {
     static std::regex signature_regex(R"regex(^META-INF/.*\.(RSA|DSA|EC|SF)$)regex");
-    if (std::regex_search(path, signature_regex)) {
+    if (std::regex_search(path.begin(), path.end(), signature_regex)) {
       return false;
     }
-    return !(path == "META-INF/MANIFEST.MF");
+    return path != "META-INF/MANIFEST.MF";
   }
 };
 
diff --git a/tools/aapt2/optimize/Obfuscator.cpp b/tools/aapt2/optimize/Obfuscator.cpp
index f704f26..cc21093 100644
--- a/tools/aapt2/optimize/Obfuscator.cpp
+++ b/tools/aapt2/optimize/Obfuscator.cpp
@@ -16,6 +16,8 @@
 
 #include "optimize/Obfuscator.h"
 
+#include <fstream>
+#include <map>
 #include <set>
 #include <string>
 #include <unordered_set>
@@ -32,10 +34,13 @@
 
 namespace aapt {
 
-Obfuscator::Obfuscator(std::map<std::string, std::string>& path_map_out) : path_map_(path_map_out) {
+Obfuscator::Obfuscator(OptimizeOptions& optimizeOptions)
+    : options_(optimizeOptions.table_flattener_options),
+      shorten_resource_paths_(optimizeOptions.shorten_resource_paths),
+      collapse_key_stringpool_(optimizeOptions.table_flattener_options.collapse_key_stringpool) {
 }
 
-std::string ShortenFileName(const android::StringPiece& file_path, int output_length) {
+std::string ShortenFileName(android::StringPiece file_path, int output_length) {
   std::size_t hash_num = std::hash<android::StringPiece>{}(file_path);
   std::string result = "";
   // Convert to (modified) base64 so that it is a proper file path.
@@ -58,9 +63,9 @@
   }
 }
 
-std::string GetShortenedPath(const android::StringPiece& shortened_filename,
-                             const android::StringPiece& extension, int collision_count) {
-  std::string shortened_path = "res/" + shortened_filename.to_string();
+std::string GetShortenedPath(android::StringPiece shortened_filename,
+                             android::StringPiece extension, int collision_count) {
+  std::string shortened_path = std::string("res/") += shortened_filename;
   if (collision_count > 0) {
     shortened_path += std::to_string(collision_count);
   }
@@ -77,7 +82,8 @@
   }
 };
 
-bool Obfuscator::Consume(IAaptContext* context, ResourceTable* table) {
+static bool HandleShortenFilePaths(ResourceTable* table,
+                                   std::map<std::string, std::string>& shortened_path_map) {
   // used to detect collisions
   std::unordered_set<std::string> shortened_paths;
   std::set<FileReference*, PathComparator> file_refs;
@@ -109,10 +115,117 @@
       shortened_path = GetShortenedPath(shortened_filename, extension, collision_count);
     }
     shortened_paths.insert(shortened_path);
-    path_map_.insert({*file_ref->path, shortened_path});
+    shortened_path_map.insert({*file_ref->path, shortened_path});
     file_ref->path = table->string_pool.MakeRef(shortened_path, file_ref->path.GetContext());
   }
   return true;
 }
 
+void Obfuscator::ObfuscateResourceName(
+    const bool collapse_key_stringpool, const std::set<ResourceName>& name_collapse_exemptions,
+    const ResourceNamedType& type_name, const ResourceTableEntryView& entry,
+    const android::base::function_ref<void(Result obfuscatedResult, const ResourceName&)>
+        onObfuscate) {
+  ResourceName resource_name({}, type_name, entry.name);
+  if (!collapse_key_stringpool ||
+      name_collapse_exemptions.find(resource_name) != name_collapse_exemptions.end()) {
+    onObfuscate(Result::Keep_ExemptionList, resource_name);
+  } else {
+    // resource isn't exempt from collapse, add it as obfuscated value
+    if (entry.overlayable_item) {
+      // if the resource name of the specific entry is obfuscated and this
+      // entry is in the overlayable list, the overlay can't work on this
+      // overlayable at runtime because the name has been obfuscated in
+      // resources.arsc during flatten operation.
+      onObfuscate(Result::Keep_Overlayable, resource_name);
+    } else {
+      onObfuscate(Result::Obfuscated, resource_name);
+    }
+  }
+}
+
+static bool HandleCollapseKeyStringPool(
+    const ResourceTable* table, const bool collapse_key_string_pool,
+    const std::set<ResourceName>& name_collapse_exemptions,
+    std::unordered_map<uint32_t, std::string>& id_resource_map) {
+  if (!collapse_key_string_pool) {
+    return true;
+  }
+
+  int entryResId = 0;
+  auto onObfuscate = [&entryResId, &id_resource_map](const Obfuscator::Result obfuscatedResult,
+                                                     const ResourceName& resource_name) {
+    if (obfuscatedResult == Obfuscator::Result::Obfuscated) {
+      id_resource_map.insert({entryResId, resource_name.entry});
+    }
+  };
+
+  for (auto& package : table->packages) {
+    for (auto& type : package->types) {
+      for (auto& entry : type->entries) {
+        if (!entry->id.has_value() || entry->name.empty()) {
+          continue;
+        }
+        entryResId = entry->id->id;
+        ResourceTableEntryView entry_view{
+            .name = entry->name,
+            .id = entry->id ? entry->id.value().entry_id() : (std::optional<uint16_t>)std::nullopt,
+            .visibility = entry->visibility,
+            .allow_new = entry->allow_new,
+            .overlayable_item = entry->overlayable_item,
+            .staged_id = entry->staged_id};
+
+        Obfuscator::ObfuscateResourceName(collapse_key_string_pool, name_collapse_exemptions,
+                                          type->named_type, entry_view, onObfuscate);
+      }
+    }
+  }
+
+  return true;
+}
+
+bool Obfuscator::Consume(IAaptContext* context, ResourceTable* table) {
+  HandleCollapseKeyStringPool(table, options_.collapse_key_stringpool,
+                              options_.name_collapse_exemptions, options_.id_resource_map);
+  if (shorten_resource_paths_) {
+    return HandleShortenFilePaths(table, options_.shortened_path_map);
+  }
+  return true;
+}
+
+bool Obfuscator::WriteObfuscationMap(const std::string& file_path) const {
+  pb::ResourceMappings resourceMappings;
+  for (const auto& [id, name] : options_.id_resource_map) {
+    auto* collapsedNameMapping = resourceMappings.mutable_collapsed_names()->add_resource_names();
+    collapsedNameMapping->set_id(id);
+    collapsedNameMapping->set_name(name);
+  }
+
+  for (const auto& [original_path, shortened_path] : options_.shortened_path_map) {
+    auto* resource_path = resourceMappings.mutable_shortened_paths()->add_resource_paths();
+    resource_path->set_original_path(original_path);
+    resource_path->set_shortened_path(shortened_path);
+  }
+
+  {  // RAII style, output the pb content to file and close fout in destructor
+    std::ofstream fout(file_path, std::ios::out | std::ios::trunc | std::ios::binary);
+    if (!fout.is_open()) {
+      return false;
+    }
+    return resourceMappings.SerializeToOstream(&fout);
+  }
+}
+
+/**
+ * Tell the optimizer whether it's needed to dump information for de-obfuscating.
+ *
+ * There are two conditions need to dump the information for de-obfuscating.
+ * * the option of shortening file paths is enabled.
+ * * the option of collapsing resource names is enabled.
+ * @return true if the information needed for de-obfuscating, otherwise false
+ */
+bool Obfuscator::IsEnabled() const {
+  return shorten_resource_paths_ || collapse_key_stringpool_;
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/optimize/Obfuscator.h b/tools/aapt2/optimize/Obfuscator.h
index 1ea32db..5ccf5438 100644
--- a/tools/aapt2/optimize/Obfuscator.h
+++ b/tools/aapt2/optimize/Obfuscator.h
@@ -17,10 +17,15 @@
 #ifndef TOOLS_AAPT2_OPTIMIZE_OBFUSCATOR_H_
 #define TOOLS_AAPT2_OPTIMIZE_OBFUSCATOR_H_
 
-#include <map>
+#include <set>
 #include <string>
 
+#include "ResourceMetadata.pb.h"
+#include "ResourceTable.h"
+#include "android-base/function_ref.h"
 #include "android-base/macros.h"
+#include "cmd/Optimize.h"
+#include "format/binary/TableFlattener.h"
 #include "process/IResourceTableConsumer.h"
 
 namespace aapt {
@@ -30,12 +35,28 @@
 // Maps resources in the apk to shortened paths.
 class Obfuscator : public IResourceTableConsumer {
  public:
-  explicit Obfuscator(std::map<std::string, std::string>& path_map_out);
+  explicit Obfuscator(OptimizeOptions& optimizeOptions);
 
   bool Consume(IAaptContext* context, ResourceTable* table) override;
 
+  bool WriteObfuscationMap(const std::string& file_path) const;
+
+  bool IsEnabled() const;
+
+  enum class Result { Obfuscated, Keep_ExemptionList, Keep_Overlayable };
+
+  // hardcoded string uses characters which make it an invalid resource name
+  static constexpr char kObfuscatedResourceName[] = "0_resource_name_obfuscated";
+
+  static void ObfuscateResourceName(
+      const bool collapse_key_stringpool, const std::set<ResourceName>& name_collapse_exemptions,
+      const ResourceNamedType& type_name, const ResourceTableEntryView& entry,
+      const android::base::function_ref<void(Result, const ResourceName&)> onObfuscate);
+
  private:
-  std::map<std::string, std::string>& path_map_;
+  TableFlattenerOptions& options_;
+  const bool shorten_resource_paths_;
+  const bool collapse_key_stringpool_;
   DISALLOW_COPY_AND_ASSIGN(Obfuscator);
 };
 
diff --git a/tools/aapt2/optimize/Obfuscator_test.cpp b/tools/aapt2/optimize/Obfuscator_test.cpp
index a3339d4..7f57b71 100644
--- a/tools/aapt2/optimize/Obfuscator_test.cpp
+++ b/tools/aapt2/optimize/Obfuscator_test.cpp
@@ -16,14 +16,19 @@
 
 #include "optimize/Obfuscator.h"
 
+#include <map>
 #include <memory>
 #include <string>
 
 #include "ResourceTable.h"
+#include "android-base/file.h"
 #include "test/Test.h"
 
 using ::aapt::test::GetValue;
+using ::testing::AnyOf;
 using ::testing::Eq;
+using ::testing::HasSubstr;
+using ::testing::IsTrue;
 using ::testing::Not;
 using ::testing::NotNull;
 
@@ -51,8 +56,9 @@
           .AddString("android:string/string", "res/should/still/be/the/same.png")
           .Build();
 
-  std::map<std::string, std::string> path_map;
-  ASSERT_TRUE(Obfuscator(path_map).Consume(context.get(), table.get()));
+  OptimizeOptions options{.shorten_resource_paths = true};
+  std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map;
+  ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
 
   // Expect that the path map is populated
   ASSERT_THAT(path_map.find("res/drawables/xmlfile.xml"), Not(Eq(path_map.end())));
@@ -87,8 +93,9 @@
                             test::ParseConfigOrDie("mdp-v21"))
           .Build();
 
-  std::map<std::string, std::string> path_map;
-  ASSERT_TRUE(Obfuscator(path_map).Consume(context.get(), table.get()));
+  OptimizeOptions options{.shorten_resource_paths = true};
+  std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map;
+  ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
 
   // Expect that the path map to not contain the ColorStateList
   ASSERT_THAT(path_map.find("res/color/colorlist.xml"), Eq(path_map.end()));
@@ -107,8 +114,9 @@
           .AddFileReference("android:color/pngfile", original_png_path)
           .Build();
 
-  std::map<std::string, std::string> path_map;
-  ASSERT_TRUE(Obfuscator(path_map).Consume(context.get(), table.get()));
+  OptimizeOptions options{.shorten_resource_paths = true};
+  std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map;
+  ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
 
   // Expect that the path map is populated
   ASSERT_THAT(path_map.find("res/drawable/xmlfile.xml"), Not(Eq(path_map.end())));
@@ -133,8 +141,10 @@
   test::ResourceTableBuilder builder1;
   FillTable(builder1, 0, kNumResources);
   std::unique_ptr<ResourceTable> table1 = builder1.Build();
-  std::map<std::string, std::string> expected_mapping;
-  ASSERT_TRUE(Obfuscator(expected_mapping).Consume(context.get(), table1.get()));
+  OptimizeOptions options{.shorten_resource_paths = true};
+  std::map<std::string, std::string>& expected_mapping =
+      options.table_flattener_options.shortened_path_map;
+  ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table1.get()));
 
   // We are trying to ensure lack of non-determinism, it is not simple to prove
   // a negative, thus we must try the test a few times so that the test itself
@@ -153,8 +163,10 @@
     FillTable(builder2, 0, start_index);
     std::unique_ptr<ResourceTable> table2 = builder2.Build();
 
-    std::map<std::string, std::string> actual_mapping;
-    ASSERT_TRUE(Obfuscator(actual_mapping).Consume(context.get(), table2.get()));
+    OptimizeOptions actualOptimizerOptions{.shorten_resource_paths = true};
+    TableFlattenerOptions& actual_options = actualOptimizerOptions.table_flattener_options;
+    std::map<std::string, std::string>& actual_mapping = actual_options.shortened_path_map;
+    ASSERT_TRUE(Obfuscator(actualOptimizerOptions).Consume(context.get(), table2.get()));
 
     for (auto& item : actual_mapping) {
       ASSERT_THAT(expected_mapping[item.first], Eq(item.second));
@@ -162,4 +174,126 @@
   }
 }
 
+TEST(ObfuscatorTest, DumpIdResourceMap) {
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+  OverlayableItem overlayable_item(std::make_shared<Overlayable>("TestName", "overlay://theme"));
+  overlayable_item.policies |= PolicyFlags::PRODUCT_PARTITION;
+  overlayable_item.policies |= PolicyFlags::SYSTEM_PARTITION;
+  overlayable_item.policies |= PolicyFlags::VENDOR_PARTITION;
+
+  std::string original_xml_path = "res/drawable/xmlfile.xml";
+  std::string original_png_path = "res/drawable/pngfile.png";
+
+  std::string name = "com.app.test:string/overlayable";
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder()
+          .AddFileReference("android:color/xmlfile", original_xml_path)
+          .AddFileReference("android:color/pngfile", original_png_path)
+          .AddValue("com.app.test:color/mycolor", aapt::ResourceId(0x7f020000),
+                    aapt::util::make_unique<aapt::BinaryPrimitive>(
+                        uint8_t(android::Res_value::TYPE_INT_COLOR_ARGB8), 0xffaabbcc))
+          .AddString("com.app.test:string/mystring", ResourceId(0x7f030000), "hi")
+          .AddString("com.app.test:string/in_exemption", ResourceId(0x7f030001), "Hi")
+          .AddString(name, ResourceId(0x7f030002), "HI")
+          .SetOverlayable(name, overlayable_item)
+          .Build();
+
+  OptimizeOptions options{.shorten_resource_paths = true};
+  TableFlattenerOptions& flattenerOptions = options.table_flattener_options;
+  flattenerOptions.collapse_key_stringpool = true;
+  flattenerOptions.name_collapse_exemptions.insert(
+      ResourceName({}, ResourceType::kString, "in_exemption"));
+  auto& id_resource_map = flattenerOptions.id_resource_map;
+  ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
+
+  // Expect that the id resource name map is populated
+  EXPECT_THAT(id_resource_map.at(0x7f020000), Eq("mycolor"));
+  EXPECT_THAT(id_resource_map.at(0x7f030000), Eq("mystring"));
+  EXPECT_THAT(id_resource_map.find(0x7f030001), Eq(id_resource_map.end()));
+  EXPECT_THAT(id_resource_map.find(0x7f030002), Eq(id_resource_map.end()));
+}
+
+TEST(ObfuscatorTest, IsEnabledWithDefaultOption) {
+  OptimizeOptions options;
+  Obfuscator obfuscatorWithDefaultOption(options);
+  ASSERT_THAT(obfuscatorWithDefaultOption.IsEnabled(), Eq(false));
+}
+
+TEST(ObfuscatorTest, IsEnabledWithShortenPathOption) {
+  OptimizeOptions options{.shorten_resource_paths = true};
+  Obfuscator obfuscatorWithShortenPathOption(options);
+  ASSERT_THAT(obfuscatorWithShortenPathOption.IsEnabled(), Eq(true));
+}
+
+TEST(ObfuscatorTest, IsEnabledWithCollapseStringPoolOption) {
+  OptimizeOptions options;
+  options.table_flattener_options.collapse_key_stringpool = true;
+  Obfuscator obfuscatorWithCollapseStringPoolOption(options);
+  ASSERT_THAT(obfuscatorWithCollapseStringPoolOption.IsEnabled(), Eq(true));
+}
+
+TEST(ObfuscatorTest, IsEnabledWithShortenPathAndCollapseStringPoolOption) {
+  OptimizeOptions options{.shorten_resource_paths = true};
+  options.table_flattener_options.collapse_key_stringpool = true;
+  Obfuscator obfuscatorWithCollapseStringPoolOption(options);
+  ASSERT_THAT(obfuscatorWithCollapseStringPoolOption.IsEnabled(), Eq(true));
+}
+
+static std::unique_ptr<ResourceTable> getProtocolBufferTableUnderTest() {
+  std::string original_xml_path = "res/drawable/xmlfile.xml";
+  std::string original_png_path = "res/drawable/pngfile.png";
+
+  return test::ResourceTableBuilder()
+      .AddFileReference("com.app.test:drawable/xmlfile", original_xml_path)
+      .AddFileReference("com.app.test:drawable/pngfile", original_png_path)
+      .AddValue("com.app.test:color/mycolor", aapt::ResourceId(0x7f020000),
+                aapt::util::make_unique<aapt::BinaryPrimitive>(
+                    uint8_t(android::Res_value::TYPE_INT_COLOR_ARGB8), 0xffaabbcc))
+      .AddString("com.app.test:string/mystring", ResourceId(0x7f030000), "hello world")
+      .Build();
+}
+
+TEST(ObfuscatorTest, WriteObfuscationMapInProtocolBufferFormat) {
+  OptimizeOptions options{.shorten_resource_paths = true};
+  options.table_flattener_options.collapse_key_stringpool = true;
+  Obfuscator obfuscator(options);
+  ASSERT_TRUE(obfuscator.Consume(test::ContextBuilder().Build().get(),
+                                 getProtocolBufferTableUnderTest().get()));
+
+  obfuscator.WriteObfuscationMap("obfuscated_map.pb");
+
+  std::string pbOut;
+  android::base::ReadFileToString("obfuscated_map.pb", &pbOut, false /* follow_symlinks */);
+  EXPECT_THAT(pbOut, HasSubstr("drawable/xmlfile.xml"));
+  EXPECT_THAT(pbOut, HasSubstr("drawable/pngfile.png"));
+  EXPECT_THAT(pbOut, HasSubstr("mycolor"));
+  EXPECT_THAT(pbOut, HasSubstr("mystring"));
+  pb::ResourceMappings resourceMappings;
+  EXPECT_THAT(resourceMappings.ParseFromString(pbOut), IsTrue());
+  EXPECT_THAT(resourceMappings.collapsed_names().resource_names_size(), Eq(2));
+  auto& resource_names = resourceMappings.collapsed_names().resource_names();
+  EXPECT_THAT(resource_names.at(0).name(), AnyOf(Eq("mycolor"), Eq("mystring")));
+  EXPECT_THAT(resource_names.at(1).name(), AnyOf(Eq("mycolor"), Eq("mystring")));
+  auto& shortened_paths = resourceMappings.shortened_paths();
+  EXPECT_THAT(shortened_paths.resource_paths_size(), Eq(2));
+  EXPECT_THAT(shortened_paths.resource_paths(0).original_path(),
+              AnyOf(Eq("res/drawable/pngfile.png"), Eq("res/drawable/xmlfile.xml")));
+  EXPECT_THAT(shortened_paths.resource_paths(1).original_path(),
+              AnyOf(Eq("res/drawable/pngfile.png"), Eq("res/drawable/xmlfile.xml")));
+}
+
+TEST(ObfuscatorTest, WriteObfuscatingMapWithNonEnabledOption) {
+  OptimizeOptions options;
+  Obfuscator obfuscator(options);
+  ASSERT_TRUE(obfuscator.Consume(test::ContextBuilder().Build().get(),
+                                 getProtocolBufferTableUnderTest().get()));
+
+  obfuscator.WriteObfuscationMap("obfuscated_map.pb");
+
+  std::string pbOut;
+  android::base::ReadFileToString("obfuscated_map.pb", &pbOut, false /* follow_symlinks */);
+  ASSERT_THAT(pbOut, Eq(""));
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/optimize/VersionCollapser_test.cpp b/tools/aapt2/optimize/VersionCollapser_test.cpp
index aa0d0c0..18dcd6b 100644
--- a/tools/aapt2/optimize/VersionCollapser_test.cpp
+++ b/tools/aapt2/optimize/VersionCollapser_test.cpp
@@ -23,7 +23,7 @@
 namespace aapt {
 
 static std::unique_ptr<ResourceTable> BuildTableWithConfigs(
-    const StringPiece& name, std::initializer_list<std::string> list) {
+    StringPiece name, std::initializer_list<std::string> list) {
   test::ResourceTableBuilder builder;
   for (const std::string& item : list) {
     builder.AddSimple(name, test::ParseConfigOrDie(item));
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 92b45c3..bca62da 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -218,7 +218,7 @@
   return symbol;
 }
 
-bool AssetManagerSymbolSource::AddAssetPath(const StringPiece& path) {
+bool AssetManagerSymbolSource::AddAssetPath(StringPiece path) {
   TRACE_CALL();
   if (std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(path.data())) {
     apk_assets_.push_back(std::move(apk));
diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h
index c17837c..b09ff70 100644
--- a/tools/aapt2/process/SymbolTable.h
+++ b/tools/aapt2/process/SymbolTable.h
@@ -192,7 +192,7 @@
  public:
   AssetManagerSymbolSource() = default;
 
-  bool AddAssetPath(const android::StringPiece& path);
+  bool AddAssetPath(android::StringPiece path);
   std::map<size_t, std::string> GetAssignedPackageIds() const;
   bool IsPackageDynamic(uint32_t packageId, const std::string& package_name) const;
 
diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp
index 30336e2..65f63dc 100644
--- a/tools/aapt2/test/Builders.cpp
+++ b/tools/aapt2/test/Builders.cpp
@@ -34,61 +34,53 @@
 namespace aapt {
 namespace test {
 
-ResourceTableBuilder& ResourceTableBuilder::AddSimple(const StringPiece& name,
-                                                      const ResourceId& id) {
+ResourceTableBuilder& ResourceTableBuilder::AddSimple(StringPiece name, const ResourceId& id) {
   return AddValue(name, id, util::make_unique<Id>());
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddSimple(const StringPiece& name,
+ResourceTableBuilder& ResourceTableBuilder::AddSimple(StringPiece name,
                                                       const ConfigDescription& config,
                                                       const ResourceId& id) {
   return AddValue(name, config, id, util::make_unique<Id>());
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddReference(const StringPiece& name,
-                                                         const StringPiece& ref) {
+ResourceTableBuilder& ResourceTableBuilder::AddReference(StringPiece name, StringPiece ref) {
   return AddReference(name, {}, ref);
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddReference(const StringPiece& name,
-                                                         const ResourceId& id,
-                                                         const StringPiece& ref) {
+ResourceTableBuilder& ResourceTableBuilder::AddReference(StringPiece name, const ResourceId& id,
+                                                         StringPiece ref) {
   return AddValue(name, id, util::make_unique<Reference>(ParseNameOrDie(ref)));
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddString(const StringPiece& name,
-                                                      const StringPiece& str) {
+ResourceTableBuilder& ResourceTableBuilder::AddString(StringPiece name, StringPiece str) {
   return AddString(name, {}, str);
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddString(const StringPiece& name, const ResourceId& id,
-                                                      const StringPiece& str) {
+ResourceTableBuilder& ResourceTableBuilder::AddString(StringPiece name, const ResourceId& id,
+                                                      StringPiece str) {
   return AddValue(name, id, util::make_unique<String>(table_->string_pool.MakeRef(str)));
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddString(const StringPiece& name, const ResourceId& id,
+ResourceTableBuilder& ResourceTableBuilder::AddString(StringPiece name, const ResourceId& id,
                                                       const ConfigDescription& config,
-                                                      const StringPiece& str) {
+                                                      StringPiece str) {
   return AddValue(name, config, id, util::make_unique<String>(table_->string_pool.MakeRef(str)));
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name,
-                                                             const StringPiece& path,
+ResourceTableBuilder& ResourceTableBuilder::AddFileReference(StringPiece name, StringPiece path,
                                                              io::IFile* file) {
   return AddFileReference(name, {}, path, file);
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name,
-                                                             const ResourceId& id,
-                                                             const StringPiece& path,
-                                                             io::IFile* file) {
+ResourceTableBuilder& ResourceTableBuilder::AddFileReference(StringPiece name, const ResourceId& id,
+                                                             StringPiece path, io::IFile* file) {
   auto file_ref = util::make_unique<FileReference>(table_->string_pool.MakeRef(path));
   file_ref->file = file;
   return AddValue(name, id, std::move(file_ref));
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name,
-                                                             const StringPiece& path,
+ResourceTableBuilder& ResourceTableBuilder::AddFileReference(StringPiece name, StringPiece path,
                                                              const ConfigDescription& config,
                                                              io::IFile* file) {
   auto file_ref = util::make_unique<FileReference>(table_->string_pool.MakeRef(path));
@@ -96,17 +88,17 @@
   return AddValue(name, config, {}, std::move(file_ref));
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name,
+ResourceTableBuilder& ResourceTableBuilder::AddValue(StringPiece name,
                                                      std::unique_ptr<Value> value) {
   return AddValue(name, {}, std::move(value));
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name, const ResourceId& id,
+ResourceTableBuilder& ResourceTableBuilder::AddValue(StringPiece name, const ResourceId& id,
                                                      std::unique_ptr<Value> value) {
   return AddValue(name, {}, id, std::move(value));
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name,
+ResourceTableBuilder& ResourceTableBuilder::AddValue(StringPiece name,
                                                      const ConfigDescription& config,
                                                      const ResourceId& id,
                                                      std::unique_ptr<Value> value) {
@@ -121,8 +113,7 @@
   return *this;
 }
 
-ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(const StringPiece& name,
-                                                           const ResourceId& id,
+ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(StringPiece name, const ResourceId& id,
                                                            Visibility::Level level,
                                                            bool allow_new) {
   ResourceName res_name = ParseNameOrDie(name);
@@ -136,9 +127,8 @@
   return *this;
 }
 
-ResourceTableBuilder& ResourceTableBuilder::SetOverlayable(const StringPiece& name,
+ResourceTableBuilder& ResourceTableBuilder::SetOverlayable(StringPiece name,
                                                            const OverlayableItem& overlayable) {
-
   ResourceName res_name = ParseNameOrDie(name);
   CHECK(table_->AddResource(
       NewResourceBuilder(res_name).SetOverlayable(overlayable).SetAllowMangled(true).Build(),
@@ -159,8 +149,7 @@
   return std::move(table_);
 }
 
-std::unique_ptr<Reference> BuildReference(const StringPiece& ref,
-                                          const std::optional<ResourceId>& id) {
+std::unique_ptr<Reference> BuildReference(StringPiece ref, const std::optional<ResourceId>& id) {
   std::unique_ptr<Reference> reference = util::make_unique<Reference>(ParseNameOrDie(ref));
   reference->id = id;
   return reference;
@@ -188,7 +177,7 @@
   return *this;
 }
 
-AttributeBuilder& AttributeBuilder::AddItem(const StringPiece& name, uint32_t value) {
+AttributeBuilder& AttributeBuilder::AddItem(StringPiece name, uint32_t value) {
   attr_->symbols.push_back(
       Attribute::Symbol{Reference(ResourceName({}, ResourceType::kId, name)), value});
   return *this;
@@ -198,17 +187,17 @@
   return std::move(attr_);
 }
 
-StyleBuilder& StyleBuilder::SetParent(const StringPiece& str) {
+StyleBuilder& StyleBuilder::SetParent(StringPiece str) {
   style_->parent = Reference(ParseNameOrDie(str));
   return *this;
 }
 
-StyleBuilder& StyleBuilder::AddItem(const StringPiece& str, std::unique_ptr<Item> value) {
+StyleBuilder& StyleBuilder::AddItem(StringPiece str, std::unique_ptr<Item> value) {
   style_->entries.push_back(Style::Entry{Reference(ParseNameOrDie(str)), std::move(value)});
   return *this;
 }
 
-StyleBuilder& StyleBuilder::AddItem(const StringPiece& str, const ResourceId& id,
+StyleBuilder& StyleBuilder::AddItem(StringPiece str, const ResourceId& id,
                                     std::unique_ptr<Item> value) {
   AddItem(str, std::move(value));
   style_->entries.back().key.id = id;
@@ -219,8 +208,7 @@
   return std::move(style_);
 }
 
-StyleableBuilder& StyleableBuilder::AddItem(const StringPiece& str,
-                                            const std::optional<ResourceId>& id) {
+StyleableBuilder& StyleableBuilder::AddItem(StringPiece str, const std::optional<ResourceId>& id) {
   styleable_->entries.push_back(Reference(ParseNameOrDie(str)));
   styleable_->entries.back().id = id;
   return *this;
@@ -230,7 +218,7 @@
   return std::move(styleable_);
 }
 
-std::unique_ptr<xml::XmlResource> BuildXmlDom(const StringPiece& str) {
+std::unique_ptr<xml::XmlResource> BuildXmlDom(StringPiece str) {
   std::string input = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
   input.append(str.data(), str.size());
   StringInputStream in(input);
@@ -241,7 +229,7 @@
 }
 
 std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName(IAaptContext* context,
-                                                            const StringPiece& str) {
+                                                            StringPiece str) {
   std::unique_ptr<xml::XmlResource> doc = BuildXmlDom(str);
   doc->file.name.package = context->GetCompilationPackage();
   return doc;
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 780bd0d..f03d6fc 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -38,40 +38,35 @@
  public:
   ResourceTableBuilder() = default;
 
-  ResourceTableBuilder& AddSimple(const android::StringPiece& name, const ResourceId& id = {});
-  ResourceTableBuilder& AddSimple(const android::StringPiece& name,
+  ResourceTableBuilder& AddSimple(android::StringPiece name, const ResourceId& id = {});
+  ResourceTableBuilder& AddSimple(android::StringPiece name,
                                   const android::ConfigDescription& config,
                                   const ResourceId& id = {});
-  ResourceTableBuilder& AddReference(const android::StringPiece& name,
-                                     const android::StringPiece& ref);
-  ResourceTableBuilder& AddReference(const android::StringPiece& name, const ResourceId& id,
-                                     const android::StringPiece& ref);
-  ResourceTableBuilder& AddString(const android::StringPiece& name,
-                                  const android::StringPiece& str);
-  ResourceTableBuilder& AddString(const android::StringPiece& name, const ResourceId& id,
-                                  const android::StringPiece& str);
-  ResourceTableBuilder& AddString(const android::StringPiece& name, const ResourceId& id,
+  ResourceTableBuilder& AddReference(android::StringPiece name, android::StringPiece ref);
+  ResourceTableBuilder& AddReference(android::StringPiece name, const ResourceId& id,
+                                     android::StringPiece ref);
+  ResourceTableBuilder& AddString(android::StringPiece name, android::StringPiece str);
+  ResourceTableBuilder& AddString(android::StringPiece name, const ResourceId& id,
+                                  android::StringPiece str);
+  ResourceTableBuilder& AddString(android::StringPiece name, const ResourceId& id,
                                   const android::ConfigDescription& config,
-                                  const android::StringPiece& str);
-  ResourceTableBuilder& AddFileReference(const android::StringPiece& name,
-                                         const android::StringPiece& path,
+                                  android::StringPiece str);
+  ResourceTableBuilder& AddFileReference(android::StringPiece name, android::StringPiece path,
                                          io::IFile* file = nullptr);
-  ResourceTableBuilder& AddFileReference(const android::StringPiece& name, const ResourceId& id,
-                                         const android::StringPiece& path,
-                                         io::IFile* file = nullptr);
-  ResourceTableBuilder& AddFileReference(const android::StringPiece& name,
-                                         const android::StringPiece& path,
+  ResourceTableBuilder& AddFileReference(android::StringPiece name, const ResourceId& id,
+                                         android::StringPiece path, io::IFile* file = nullptr);
+  ResourceTableBuilder& AddFileReference(android::StringPiece name, android::StringPiece path,
                                          const android::ConfigDescription& config,
                                          io::IFile* file = nullptr);
-  ResourceTableBuilder& AddValue(const android::StringPiece& name, std::unique_ptr<Value> value);
-  ResourceTableBuilder& AddValue(const android::StringPiece& name, const ResourceId& id,
+  ResourceTableBuilder& AddValue(android::StringPiece name, std::unique_ptr<Value> value);
+  ResourceTableBuilder& AddValue(android::StringPiece name, const ResourceId& id,
                                  std::unique_ptr<Value> value);
-  ResourceTableBuilder& AddValue(const android::StringPiece& name,
-                                 const android::ConfigDescription& config,
-                                 const ResourceId& id, std::unique_ptr<Value> value);
-  ResourceTableBuilder& SetSymbolState(const android::StringPiece& name, const ResourceId& id,
+  ResourceTableBuilder& AddValue(android::StringPiece name,
+                                 const android::ConfigDescription& config, const ResourceId& id,
+                                 std::unique_ptr<Value> value);
+  ResourceTableBuilder& SetSymbolState(android::StringPiece name, const ResourceId& id,
                                        Visibility::Level level, bool allow_new = false);
-  ResourceTableBuilder& SetOverlayable(const android::StringPiece& name,
+  ResourceTableBuilder& SetOverlayable(android::StringPiece name,
                                        const OverlayableItem& overlayable);
   ResourceTableBuilder& Add(NewResource&& res);
 
@@ -84,7 +79,7 @@
   std::unique_ptr<ResourceTable> table_ = util::make_unique<ResourceTable>();
 };
 
-std::unique_ptr<Reference> BuildReference(const android::StringPiece& ref,
+std::unique_ptr<Reference> BuildReference(android::StringPiece ref,
                                           const std::optional<ResourceId>& id = {});
 std::unique_ptr<BinaryPrimitive> BuildPrimitive(uint8_t type, uint32_t data);
 
@@ -101,7 +96,7 @@
     return *this;
   }
 
-  ValueBuilder& SetComment(const android::StringPiece& str) {
+  ValueBuilder& SetComment(android::StringPiece str) {
     value_->SetComment(str);
     return *this;
   }
@@ -121,7 +116,7 @@
   AttributeBuilder();
   AttributeBuilder& SetTypeMask(uint32_t typeMask);
   AttributeBuilder& SetWeak(bool weak);
-  AttributeBuilder& AddItem(const android::StringPiece& name, uint32_t value);
+  AttributeBuilder& AddItem(android::StringPiece name, uint32_t value);
   std::unique_ptr<Attribute> Build();
 
  private:
@@ -133,9 +128,9 @@
 class StyleBuilder {
  public:
   StyleBuilder() = default;
-  StyleBuilder& SetParent(const android::StringPiece& str);
-  StyleBuilder& AddItem(const android::StringPiece& str, std::unique_ptr<Item> value);
-  StyleBuilder& AddItem(const android::StringPiece& str, const ResourceId& id,
+  StyleBuilder& SetParent(android::StringPiece str);
+  StyleBuilder& AddItem(android::StringPiece str, std::unique_ptr<Item> value);
+  StyleBuilder& AddItem(android::StringPiece str, const ResourceId& id,
                         std::unique_ptr<Item> value);
   std::unique_ptr<Style> Build();
 
@@ -148,8 +143,7 @@
 class StyleableBuilder {
  public:
   StyleableBuilder() = default;
-  StyleableBuilder& AddItem(const android::StringPiece& str,
-                            const std::optional<ResourceId>& id = {});
+  StyleableBuilder& AddItem(android::StringPiece str, const std::optional<ResourceId>& id = {});
   std::unique_ptr<Styleable> Build();
 
  private:
@@ -158,9 +152,9 @@
   std::unique_ptr<Styleable> styleable_ = util::make_unique<Styleable>();
 };
 
-std::unique_ptr<xml::XmlResource> BuildXmlDom(const android::StringPiece& str);
+std::unique_ptr<xml::XmlResource> BuildXmlDom(android::StringPiece str);
 std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName(IAaptContext* context,
-                                                            const android::StringPiece& str);
+                                                            android::StringPiece str);
 
 class ArtifactBuilder {
  public:
diff --git a/tools/aapt2/test/Common.cpp b/tools/aapt2/test/Common.cpp
index eca0c1c..cdf24534 100644
--- a/tools/aapt2/test/Common.cpp
+++ b/tools/aapt2/test/Common.cpp
@@ -44,10 +44,9 @@
 }
 
 template <>
-Value* GetValueForConfigAndProduct<Value>(ResourceTable* table,
-                                          const android::StringPiece& res_name,
+Value* GetValueForConfigAndProduct<Value>(ResourceTable* table, android::StringPiece res_name,
                                           const ConfigDescription& config,
-                                          const android::StringPiece& product) {
+                                          android::StringPiece product) {
   std::optional<ResourceTable::SearchResult> result = table->FindResource(ParseNameOrDie(res_name));
   if (result) {
     ResourceConfigValue* config_value = result.value().entry->FindValue(config, product);
diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h
index 3f28361..83a0f3f 100644
--- a/tools/aapt2/test/Common.h
+++ b/tools/aapt2/test/Common.h
@@ -39,22 +39,22 @@
 
 android::IDiagnostics* GetDiagnostics();
 
-inline ResourceName ParseNameOrDie(const android::StringPiece& str) {
+inline ResourceName ParseNameOrDie(android::StringPiece str) {
   ResourceNameRef ref;
   CHECK(ResourceUtils::ParseResourceName(str, &ref)) << "invalid resource name: " << str;
   return ref.ToResourceName();
 }
 
-inline android::ConfigDescription ParseConfigOrDie(const android::StringPiece& str) {
-    android::ConfigDescription config;
+inline android::ConfigDescription ParseConfigOrDie(android::StringPiece str) {
+  android::ConfigDescription config;
   CHECK(android::ConfigDescription::Parse(str, &config)) << "invalid configuration: " << str;
   return config;
 }
 
 template <typename T = Value>
-T* GetValueForConfigAndProduct(ResourceTable* table, const android::StringPiece& res_name,
+T* GetValueForConfigAndProduct(ResourceTable* table, android::StringPiece res_name,
                                const android::ConfigDescription& config,
-                               const android::StringPiece& product) {
+                               android::StringPiece product) {
   std::optional<ResourceTable::SearchResult> result = table->FindResource(ParseNameOrDie(res_name));
   if (result) {
     ResourceConfigValue* config_value = result.value().entry->FindValue(config, product);
@@ -66,25 +66,25 @@
 }
 
 template <>
-Value* GetValueForConfigAndProduct<Value>(ResourceTable* table,
-                                          const android::StringPiece& res_name,
+Value* GetValueForConfigAndProduct<Value>(ResourceTable* table, android::StringPiece res_name,
                                           const android::ConfigDescription& config,
-                                          const android::StringPiece& product);
+                                          android::StringPiece product);
 
 template <typename T = Value>
-T* GetValueForConfig(ResourceTable* table, const android::StringPiece& res_name,
+T* GetValueForConfig(ResourceTable* table, android::StringPiece res_name,
                      const android::ConfigDescription& config) {
   return GetValueForConfigAndProduct<T>(table, res_name, config, {});
 }
 
 template <typename T = Value>
-T* GetValue(ResourceTable* table, const android::StringPiece& res_name) {
+T* GetValue(ResourceTable* table, android::StringPiece res_name) {
   return GetValueForConfig<T>(table, res_name, {});
 }
 
 class TestFile : public io::IFile {
  public:
-  explicit TestFile(const android::StringPiece& path) : source_(path) {}
+  explicit TestFile(android::StringPiece path) : source_(path) {
+  }
 
   std::unique_ptr<io::IData> OpenAsData() override {
     return {};
diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h
index 4e4973e..c5331fb 100644
--- a/tools/aapt2/test/Context.h
+++ b/tools/aapt2/test/Context.h
@@ -52,8 +52,8 @@
     return compilation_package_.value();
   }
 
-  void SetCompilationPackage(const android::StringPiece& package) {
-    compilation_package_ = package.to_string();
+  void SetCompilationPackage(android::StringPiece package) {
+    compilation_package_ = std::string(package);
   }
 
   uint8_t GetPackageId() override {
@@ -111,8 +111,8 @@
     return *this;
   }
 
-  ContextBuilder& SetCompilationPackage(const android::StringPiece& package) {
-    context_->compilation_package_ = package.to_string();
+  ContextBuilder& SetCompilationPackage(android::StringPiece package) {
+    context_->compilation_package_ = std::string(package);
     return *this;
   }
 
@@ -149,7 +149,7 @@
 
 class StaticSymbolSourceBuilder {
  public:
-  StaticSymbolSourceBuilder& AddPublicSymbol(const android::StringPiece& name, ResourceId id,
+  StaticSymbolSourceBuilder& AddPublicSymbol(android::StringPiece name, ResourceId id,
                                              std::unique_ptr<Attribute> attr = {}) {
     std::unique_ptr<SymbolTable::Symbol> symbol =
         util::make_unique<SymbolTable::Symbol>(id, std::move(attr), true);
@@ -159,7 +159,7 @@
     return *this;
   }
 
-  StaticSymbolSourceBuilder& AddSymbol(const android::StringPiece& name, ResourceId id,
+  StaticSymbolSourceBuilder& AddSymbol(android::StringPiece name, ResourceId id,
                                        std::unique_ptr<Attribute> attr = {}) {
     std::unique_ptr<SymbolTable::Symbol> symbol =
         util::make_unique<SymbolTable::Symbol>(id, std::move(attr), false);
diff --git a/tools/aapt2/test/Fixture.cpp b/tools/aapt2/test/Fixture.cpp
index dbc0e36..428372f 100644
--- a/tools/aapt2/test/Fixture.cpp
+++ b/tools/aapt2/test/Fixture.cpp
@@ -38,8 +38,8 @@
 
 const char* CommandTestFixture::kDefaultPackageName = "com.aapt.command.test";
 
-void ClearDirectory(const android::StringPiece& path) {
-  const std::string root_dir = path.to_string();
+void ClearDirectory(android::StringPiece path) {
+  const std::string root_dir(path);
   std::unique_ptr<DIR, decltype(closedir)*> dir(opendir(root_dir.data()), closedir);
   if (!dir) {
     StdErrDiagnostics().Error(android::DiagMessage()
@@ -91,8 +91,7 @@
 }
 
 bool CommandTestFixture::CompileFile(const std::string& path, const std::string& contents,
-                                     const android::StringPiece& out_dir,
-                                     android::IDiagnostics* diag) {
+                                     android::StringPiece out_dir, android::IDiagnostics* diag) {
   WriteFile(path, contents);
   CHECK(file::mkdirs(out_dir.data()));
   return CompileCommand(diag).Execute({path, "-o", out_dir, "-v"}, &std::cerr) == 0;
@@ -113,8 +112,8 @@
   return LinkCommand(diag).Execute(link_args, &std::cerr) == 0;
 }
 
-bool CommandTestFixture::Link(const std::vector<std::string>& args,
-                              const android::StringPiece& flat_dir, android::IDiagnostics* diag) {
+bool CommandTestFixture::Link(const std::vector<std::string>& args, android::StringPiece flat_dir,
+                              android::IDiagnostics* diag) {
   std::vector<android::StringPiece> link_args;
   for(const std::string& arg : args) {
     link_args.emplace_back(arg);
@@ -148,7 +147,7 @@
 }
 
 std::unique_ptr<io::IData> CommandTestFixture::OpenFileAsData(LoadedApk* apk,
-                                                              const android::StringPiece& path) {
+                                                              android::StringPiece path) {
   return apk
       ->GetFileCollection()
       ->FindFile(path)
diff --git a/tools/aapt2/test/Fixture.h b/tools/aapt2/test/Fixture.h
index 61403b7..ba4a734 100644
--- a/tools/aapt2/test/Fixture.h
+++ b/tools/aapt2/test/Fixture.h
@@ -48,7 +48,7 @@
   // Retrieves the absolute path of the specified relative path in the test directory. Directories
   // should be separated using forward slashes ('/'), and these slashes will be translated to
   // backslashes when running Windows tests.
-  std::string GetTestPath(const android::StringPiece& path) {
+  std::string GetTestPath(android::StringPiece path) {
     std::string base = temp_dir_;
     for (android::StringPiece part : util::Split(path, '/')) {
       file::AppendPath(&base, part);
@@ -73,22 +73,21 @@
   // Wries the contents of the file to the specified path. The file is compiled and the flattened
   // file is written to the out directory.
   bool CompileFile(const std::string& path, const std::string& contents,
-                   const android::StringPiece& flat_out_dir, android::IDiagnostics* diag);
+                   android::StringPiece flat_out_dir, android::IDiagnostics* diag);
 
   // Executes the link command with the specified arguments.
   bool Link(const std::vector<std::string>& args, android::IDiagnostics* diag);
 
   // Executes the link command with the specified arguments. The flattened files residing in the
   // flat directory will be added to the link command as file arguments.
-  bool Link(const std::vector<std::string>& args, const android::StringPiece& flat_dir,
+  bool Link(const std::vector<std::string>& args, android::StringPiece flat_dir,
             android::IDiagnostics* diag);
 
   // Creates a minimal android manifest within the test directory and returns the file path.
   std::string GetDefaultManifest(const char* package_name = kDefaultPackageName);
 
   // Returns pointer to data inside APK files
-  std::unique_ptr<io::IData> OpenFileAsData(LoadedApk* apk,
-                                            const android::StringPiece& path);
+  std::unique_ptr<io::IData> OpenFileAsData(LoadedApk* apk, android::StringPiece path);
 
   // Asserts that loading the tree from the specified file in the apk succeeds.
   void AssertLoadXml(LoadedApk* apk, const io::IData* data,
diff --git a/tools/aapt2/text/Printer.cpp b/tools/aapt2/text/Printer.cpp
index 243800c..8e491ac 100644
--- a/tools/aapt2/text/Printer.cpp
+++ b/tools/aapt2/text/Printer.cpp
@@ -26,7 +26,7 @@
 namespace aapt {
 namespace text {
 
-Printer& Printer::Println(const StringPiece& str) {
+Printer& Printer::Println(StringPiece str) {
   Print(str);
   return Print("\n");
 }
@@ -35,7 +35,7 @@
   return Print("\n");
 }
 
-Printer& Printer::Print(const StringPiece& str) {
+Printer& Printer::Print(StringPiece str) {
   if (error_) {
     return *this;
   }
@@ -47,7 +47,7 @@
     const auto new_line_iter = std::find(remaining_str_begin, remaining_str_end, '\n');
 
     // We will copy the string up until the next new-line (or end of string).
-    const StringPiece str_to_copy = str.substr(remaining_str_begin, new_line_iter);
+    const StringPiece str_to_copy(remaining_str_begin, new_line_iter - remaining_str_begin);
     if (!str_to_copy.empty()) {
       if (needs_indent_) {
         for (int i = 0; i < indent_level_; i++) {
diff --git a/tools/aapt2/text/Printer.h b/tools/aapt2/text/Printer.h
index f399f8e..f7ad98b 100644
--- a/tools/aapt2/text/Printer.h
+++ b/tools/aapt2/text/Printer.h
@@ -31,8 +31,8 @@
   explicit Printer(::aapt::io::OutputStream* out) : out_(out) {
   }
 
-  Printer& Print(const ::android::StringPiece& str);
-  Printer& Println(const ::android::StringPiece& str);
+  Printer& Print(android::StringPiece str);
+  Printer& Println(android::StringPiece str);
   Printer& Println();
 
   void Indent();
diff --git a/tools/aapt2/text/Unicode.cpp b/tools/aapt2/text/Unicode.cpp
index 3735b3e..5e25be3 100644
--- a/tools/aapt2/text/Unicode.cpp
+++ b/tools/aapt2/text/Unicode.cpp
@@ -77,7 +77,7 @@
          (codepoint == 0x3000);
 }
 
-bool IsJavaIdentifier(const StringPiece& str) {
+bool IsJavaIdentifier(StringPiece str) {
   Utf8Iterator iter(str);
 
   // Check the first character.
@@ -99,7 +99,7 @@
   return true;
 }
 
-bool IsValidResourceEntryName(const StringPiece& str) {
+bool IsValidResourceEntryName(StringPiece str) {
   Utf8Iterator iter(str);
 
   // Check the first character.
diff --git a/tools/aapt2/text/Unicode.h b/tools/aapt2/text/Unicode.h
index 546714e..ab3e82b 100644
--- a/tools/aapt2/text/Unicode.h
+++ b/tools/aapt2/text/Unicode.h
@@ -46,11 +46,11 @@
 
 // Returns true if the UTF8 string can be used as a Java identifier.
 // NOTE: This does not check against the set of reserved Java keywords.
-bool IsJavaIdentifier(const android::StringPiece& str);
+bool IsJavaIdentifier(android::StringPiece str);
 
 // Returns true if the UTF8 string can be used as the entry name of a resource name.
 // This is the `entry` part of package:type/entry.
-bool IsValidResourceEntryName(const android::StringPiece& str);
+bool IsValidResourceEntryName(android::StringPiece str);
 
 }  // namespace text
 }  // namespace aapt
diff --git a/tools/aapt2/text/Utf8Iterator.cpp b/tools/aapt2/text/Utf8Iterator.cpp
index 20b9073..0bd8a37 100644
--- a/tools/aapt2/text/Utf8Iterator.cpp
+++ b/tools/aapt2/text/Utf8Iterator.cpp
@@ -24,7 +24,7 @@
 namespace aapt {
 namespace text {
 
-Utf8Iterator::Utf8Iterator(const StringPiece& str)
+Utf8Iterator::Utf8Iterator(StringPiece str)
     : str_(str), current_pos_(0), next_pos_(0), current_codepoint_(0) {
   DoNext();
 }
diff --git a/tools/aapt2/text/Utf8Iterator.h b/tools/aapt2/text/Utf8Iterator.h
index 9318401..2bba198 100644
--- a/tools/aapt2/text/Utf8Iterator.h
+++ b/tools/aapt2/text/Utf8Iterator.h
@@ -25,7 +25,7 @@
 
 class Utf8Iterator {
  public:
-  explicit Utf8Iterator(const android::StringPiece& str);
+  explicit Utf8Iterator(android::StringPiece str);
 
   bool HasNext() const;
 
diff --git a/tools/aapt2/trace/TraceBuffer.cpp b/tools/aapt2/trace/TraceBuffer.cpp
index b4b31d9..da53739 100644
--- a/tools/aapt2/trace/TraceBuffer.cpp
+++ b/tools/aapt2/trace/TraceBuffer.cpp
@@ -103,7 +103,7 @@
   s << tag;
   s << " ";
   for (auto& arg : args) {
-    s << arg.to_string();
+    s << arg;
     s << " ";
   }
   tracebuffer::Add(s.str(), tracebuffer::kBegin);
@@ -124,7 +124,7 @@
   s << tag;
   s << " ";
   for (auto& arg : args) {
-    s << arg.to_string();
+    s << arg;
     s << " ";
   }
   tracebuffer::Add(s.str(), tracebuffer::kBegin);
diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp
index 5d5b7cd..93c1b61 100644
--- a/tools/aapt2/util/Files.cpp
+++ b/tools/aapt2/util/Files.cpp
@@ -139,7 +139,7 @@
   return ::android::base::utf8::mkdir(path.c_str(), mode) == 0 || errno == EEXIST;
 }
 
-StringPiece GetStem(const StringPiece& path) {
+StringPiece GetStem(StringPiece path) {
   const char* start = path.begin();
   const char* end = path.end();
   for (const char* current = end - 1; current != start - 1; --current) {
@@ -150,7 +150,7 @@
   return {};
 }
 
-StringPiece GetFilename(const StringPiece& path) {
+StringPiece GetFilename(StringPiece path) {
   const char* end = path.end();
   const char* last_dir_sep = path.begin();
   for (const char* c = path.begin(); c != end; ++c) {
@@ -161,7 +161,7 @@
   return StringPiece(last_dir_sep, end - last_dir_sep);
 }
 
-StringPiece GetExtension(const StringPiece& path) {
+StringPiece GetExtension(StringPiece path) {
   StringPiece filename = GetFilename(path);
   const char* const end = filename.end();
   const char* c = std::find(filename.begin(), end, '.');
@@ -171,7 +171,7 @@
   return {};
 }
 
-bool IsHidden(const android::StringPiece& path) {
+bool IsHidden(android::StringPiece path) {
   return util::StartsWith(GetFilename(path), ".");
 }
 
@@ -193,16 +193,16 @@
   if (args.empty()) {
     return "";
   }
-  std::string out = args[0].to_string();
+  std::string out{args[0]};
   for (int i = 1; i < args.size(); i++) {
     file::AppendPath(&out, args[i]);
   }
   return out;
 }
 
-std::string PackageToPath(const StringPiece& package) {
+std::string PackageToPath(StringPiece package) {
   std::string out_path;
-  for (const StringPiece& part : util::Tokenize(package, '.')) {
+  for (StringPiece part : util::Tokenize(package, '.')) {
     AppendPath(&out_path, part);
   }
   return out_path;
@@ -241,10 +241,10 @@
   return std::move(filemap);
 }
 
-bool AppendArgsFromFile(const StringPiece& path, std::vector<std::string>* out_arglist,
+bool AppendArgsFromFile(StringPiece path, std::vector<std::string>* out_arglist,
                         std::string* out_error) {
   std::string contents;
-  if (!ReadFileToString(path.to_string(), &contents, true /*follow_symlinks*/)) {
+  if (!ReadFileToString(std::string(path), &contents, true /*follow_symlinks*/)) {
     if (out_error) {
       *out_error = "failed to read argument-list file";
     }
@@ -254,16 +254,16 @@
   for (StringPiece line : util::Tokenize(contents, ' ')) {
     line = util::TrimWhitespace(line);
     if (!line.empty()) {
-      out_arglist->push_back(line.to_string());
+      out_arglist->emplace_back(line);
     }
   }
   return true;
 }
 
-bool AppendSetArgsFromFile(const StringPiece& path, std::unordered_set<std::string>* out_argset,
+bool AppendSetArgsFromFile(StringPiece path, std::unordered_set<std::string>* out_argset,
                            std::string* out_error) {
   std::string contents;
-  if(!ReadFileToString(path.to_string(), &contents, true /*follow_symlinks*/)) {
+  if (!ReadFileToString(std::string(path), &contents, true /*follow_symlinks*/)) {
     if (out_error) {
       *out_error = "failed to read argument-list file";
     }
@@ -273,13 +273,13 @@
   for (StringPiece line : util::Tokenize(contents, ' ')) {
     line = util::TrimWhitespace(line);
     if (!line.empty()) {
-      out_argset->insert(line.to_string());
+      out_argset->emplace(line);
     }
   }
   return true;
 }
 
-bool FileFilter::SetPattern(const StringPiece& pattern) {
+bool FileFilter::SetPattern(StringPiece pattern) {
   pattern_tokens_ = util::SplitAndLowercase(pattern, ':');
   return true;
 }
@@ -343,10 +343,10 @@
   return true;
 }
 
-std::optional<std::vector<std::string>> FindFiles(const android::StringPiece& path,
+std::optional<std::vector<std::string>> FindFiles(android::StringPiece path,
                                                   android::IDiagnostics* diag,
                                                   const FileFilter* filter) {
-  const std::string root_dir = path.to_string();
+  const auto& root_dir = path;
   std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir);
   if (!d) {
     diag->Error(android::DiagMessage() << SystemErrorCodeToString(errno) << ": " << root_dir);
@@ -361,7 +361,7 @@
     }
 
     std::string file_name = entry->d_name;
-    std::string full_path = root_dir;
+    std::string full_path{root_dir};
     AppendPath(&full_path, file_name);
     const FileType file_type = GetFileType(full_path);
 
@@ -380,7 +380,7 @@
 
   // Now process subdirs.
   for (const std::string& subdir : subdirs) {
-    std::string full_subdir = root_dir;
+    std::string full_subdir{root_dir};
     AppendPath(&full_subdir, subdir);
     std::optional<std::vector<std::string>> subfiles = FindFiles(full_subdir, diag, filter);
     if (!subfiles) {
diff --git a/tools/aapt2/util/Files.h b/tools/aapt2/util/Files.h
index ee95712..42eeaf2 100644
--- a/tools/aapt2/util/Files.h
+++ b/tools/aapt2/util/Files.h
@@ -66,31 +66,31 @@
 bool mkdirs(const std::string& path);
 
 // Returns all but the last part of the path.
-android::StringPiece GetStem(const android::StringPiece& path);
+android::StringPiece GetStem(android::StringPiece path);
 
 // Returns the last part of the path with extension.
-android::StringPiece GetFilename(const android::StringPiece& path);
+android::StringPiece GetFilename(android::StringPiece path);
 
 // Returns the extension of the path. This is the entire string after the first '.' of the last part
 // of the path.
-android::StringPiece GetExtension(const android::StringPiece& path);
+android::StringPiece GetExtension(android::StringPiece path);
 
 // Returns whether or not the name of the file or directory is a hidden file name
-bool IsHidden(const android::StringPiece& path);
+bool IsHidden(android::StringPiece path);
 
 // Converts a package name (com.android.app) to a path: com/android/app
-std::string PackageToPath(const android::StringPiece& package);
+std::string PackageToPath(android::StringPiece package);
 
 // Creates a FileMap for the file at path.
 std::optional<android::FileMap> MmapPath(const std::string& path, std::string* out_error);
 
 // Reads the file at path and appends each line to the outArgList vector.
-bool AppendArgsFromFile(const android::StringPiece& path, std::vector<std::string>* out_arglist,
+bool AppendArgsFromFile(android::StringPiece path, std::vector<std::string>* out_arglist,
                         std::string* out_error);
 
 // Reads the file at path and appends each line to the outargset set.
-bool AppendSetArgsFromFile(const android::StringPiece& path,
-                        std::unordered_set<std::string>* out_argset, std::string* out_error);
+bool AppendSetArgsFromFile(android::StringPiece path, std::unordered_set<std::string>* out_argset,
+                           std::string* out_error);
 
 // Filter that determines which resource files/directories are
 // processed by AAPT. Takes a pattern string supplied by the user.
@@ -112,7 +112,7 @@
   // - The special filenames "." and ".." are always ignored.
   // - Otherwise the full string is matched.
   // - match is not case-sensitive.
-  bool SetPattern(const android::StringPiece& pattern);
+  bool SetPattern(android::StringPiece pattern);
 
   // Applies the filter, returning true for pass, false for fail.
   bool operator()(const std::string& filename, FileType type) const;
@@ -126,7 +126,7 @@
 
 // Returns a list of files relative to the directory identified by `path`.
 // An optional FileFilter filters out any files that don't pass.
-std::optional<std::vector<std::string>> FindFiles(const android::StringPiece& path,
+std::optional<std::vector<std::string>> FindFiles(android::StringPiece path,
                                                   android::IDiagnostics* diag,
                                                   const FileFilter* filter = nullptr);
 
diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp
index 9b7ebdd..be87766 100644
--- a/tools/aapt2/util/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -43,15 +43,14 @@
 // See frameworks/base/core/java/android/content/pm/parsing/ParsingPackageUtils.java
 constexpr static const size_t kMaxPackageNameSize = 223;
 
-static std::vector<std::string> SplitAndTransform(
-    const StringPiece& str, char sep, const std::function<char(char)>& f) {
+static std::vector<std::string> SplitAndTransform(StringPiece str, char sep, char (*f)(char)) {
   std::vector<std::string> parts;
   const StringPiece::const_iterator end = std::end(str);
   StringPiece::const_iterator start = std::begin(str);
   StringPiece::const_iterator current;
   do {
     current = std::find(start, end, sep);
-    parts.emplace_back(str.substr(start, current).to_string());
+    parts.emplace_back(start, current);
     if (f) {
       std::string& part = parts.back();
       std::transform(part.begin(), part.end(), part.begin(), f);
@@ -61,29 +60,29 @@
   return parts;
 }
 
-std::vector<std::string> Split(const StringPiece& str, char sep) {
+std::vector<std::string> Split(StringPiece str, char sep) {
   return SplitAndTransform(str, sep, nullptr);
 }
 
-std::vector<std::string> SplitAndLowercase(const StringPiece& str, char sep) {
-  return SplitAndTransform(str, sep, ::tolower);
+std::vector<std::string> SplitAndLowercase(StringPiece str, char sep) {
+  return SplitAndTransform(str, sep, [](char c) -> char { return ::tolower(c); });
 }
 
-bool StartsWith(const StringPiece& str, const StringPiece& prefix) {
+bool StartsWith(StringPiece str, StringPiece prefix) {
   if (str.size() < prefix.size()) {
     return false;
   }
   return str.substr(0, prefix.size()) == prefix;
 }
 
-bool EndsWith(const StringPiece& str, const StringPiece& suffix) {
+bool EndsWith(StringPiece str, StringPiece suffix) {
   if (str.size() < suffix.size()) {
     return false;
   }
   return str.substr(str.size() - suffix.size(), suffix.size()) == suffix;
 }
 
-StringPiece TrimLeadingWhitespace(const StringPiece& str) {
+StringPiece TrimLeadingWhitespace(StringPiece str) {
   if (str.size() == 0 || str.data() == nullptr) {
     return str;
   }
@@ -97,7 +96,7 @@
   return StringPiece(start, end - start);
 }
 
-StringPiece TrimTrailingWhitespace(const StringPiece& str) {
+StringPiece TrimTrailingWhitespace(StringPiece str) {
   if (str.size() == 0 || str.data() == nullptr) {
     return str;
   }
@@ -111,7 +110,7 @@
   return StringPiece(start, end - start);
 }
 
-StringPiece TrimWhitespace(const StringPiece& str) {
+StringPiece TrimWhitespace(StringPiece str) {
   if (str.size() == 0 || str.data() == nullptr) {
     return str;
   }
@@ -130,9 +129,9 @@
   return StringPiece(start, end - start);
 }
 
-static int IsJavaNameImpl(const StringPiece& str) {
+static int IsJavaNameImpl(StringPiece str) {
   int pieces = 0;
-  for (const StringPiece& piece : Tokenize(str, '.')) {
+  for (StringPiece piece : Tokenize(str, '.')) {
     pieces++;
     if (!text::IsJavaIdentifier(piece)) {
       return -1;
@@ -141,17 +140,17 @@
   return pieces;
 }
 
-bool IsJavaClassName(const StringPiece& str) {
+bool IsJavaClassName(StringPiece str) {
   return IsJavaNameImpl(str) >= 2;
 }
 
-bool IsJavaPackageName(const StringPiece& str) {
+bool IsJavaPackageName(StringPiece str) {
   return IsJavaNameImpl(str) >= 1;
 }
 
-static int IsAndroidNameImpl(const StringPiece& str) {
+static int IsAndroidNameImpl(StringPiece str) {
   int pieces = 0;
-  for (const StringPiece& piece : Tokenize(str, '.')) {
+  for (StringPiece piece : Tokenize(str, '.')) {
     if (piece.empty()) {
       return -1;
     }
@@ -173,15 +172,14 @@
   return pieces;
 }
 
-bool IsAndroidPackageName(const StringPiece& str) {
+bool IsAndroidPackageName(StringPiece str) {
   if (str.size() > kMaxPackageNameSize) {
     return false;
   }
   return IsAndroidNameImpl(str) > 1 || str == "android";
 }
 
-bool IsAndroidSharedUserId(const android::StringPiece& package_name,
-                           const android::StringPiece& shared_user_id) {
+bool IsAndroidSharedUserId(android::StringPiece package_name, android::StringPiece shared_user_id) {
   if (shared_user_id.size() > kMaxPackageNameSize) {
     return false;
   }
@@ -189,25 +187,24 @@
          package_name == "android";
 }
 
-bool IsAndroidSplitName(const StringPiece& str) {
+bool IsAndroidSplitName(StringPiece str) {
   return IsAndroidNameImpl(str) > 0;
 }
 
-std::optional<std::string> GetFullyQualifiedClassName(const StringPiece& package,
-                                                      const StringPiece& classname) {
+std::optional<std::string> GetFullyQualifiedClassName(StringPiece package, StringPiece classname) {
   if (classname.empty()) {
     return {};
   }
 
   if (util::IsJavaClassName(classname)) {
-    return classname.to_string();
+    return std::string(classname);
   }
 
   if (package.empty()) {
     return {};
   }
 
-  std::string result = package.to_string();
+  std::string result{package};
   if (classname.data()[0] != '.') {
     result += '.';
   }
@@ -251,7 +248,7 @@
   return static_cast<size_t>(c - start);
 }
 
-bool VerifyJavaStringFormat(const StringPiece& str) {
+bool VerifyJavaStringFormat(StringPiece str) {
   const char* c = str.begin();
   const char* const end = str.end();
 
@@ -341,7 +338,7 @@
   return true;
 }
 
-std::u16string Utf8ToUtf16(const StringPiece& utf8) {
+std::u16string Utf8ToUtf16(StringPiece utf8) {
   ssize_t utf16_length = utf8_to_utf16_length(
       reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length());
   if (utf16_length <= 0) {
@@ -381,7 +378,7 @@
   const char* end = str_.end();
   if (start == end) {
     end_ = true;
-    token_.assign(token_.end(), 0);
+    token_ = StringPiece(token_.end(), 0);
     return *this;
   }
 
@@ -389,12 +386,12 @@
   const char* current = start;
   while (current != end) {
     if (*current == separator_) {
-      token_.assign(start, current - start);
+      token_ = StringPiece(start, current - start);
       return *this;
     }
     ++current;
   }
-  token_.assign(start, end - start);
+  token_ = StringPiece(start, end - start);
   return *this;
 }
 
@@ -409,15 +406,17 @@
   return !(*this == rhs);
 }
 
-Tokenizer::iterator::iterator(const StringPiece& s, char sep, const StringPiece& tok, bool end)
-    : str_(s), separator_(sep), token_(tok), end_(end) {}
+Tokenizer::iterator::iterator(StringPiece s, char sep, StringPiece tok, bool end)
+    : str_(s), separator_(sep), token_(tok), end_(end) {
+}
 
-Tokenizer::Tokenizer(const StringPiece& str, char sep)
+Tokenizer::Tokenizer(StringPiece str, char sep)
     : begin_(++iterator(str, sep, StringPiece(str.begin() - 1, 0), false)),
-      end_(str, sep, StringPiece(str.end(), 0), true) {}
+      end_(str, sep, StringPiece(str.end(), 0), true) {
+}
 
-bool ExtractResFilePathParts(const StringPiece& path, StringPiece* out_prefix,
-                             StringPiece* out_entry, StringPiece* out_suffix) {
+bool ExtractResFilePathParts(StringPiece path, StringPiece* out_prefix, StringPiece* out_entry,
+                             StringPiece* out_suffix) {
   const StringPiece res_prefix("res/");
   if (!StartsWith(path, res_prefix)) {
     return false;
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index 8d3b413..40ff5b6 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -48,44 +48,44 @@
   T end;
 };
 
-std::vector<std::string> Split(const android::StringPiece& str, char sep);
-std::vector<std::string> SplitAndLowercase(const android::StringPiece& str, char sep);
+std::vector<std::string> Split(android::StringPiece str, char sep);
+std::vector<std::string> SplitAndLowercase(android::StringPiece str, char sep);
 
 // Returns true if the string starts with prefix.
-bool StartsWith(const android::StringPiece& str, const android::StringPiece& prefix);
+bool StartsWith(android::StringPiece str, android::StringPiece prefix);
 
 // Returns true if the string ends with suffix.
-bool EndsWith(const android::StringPiece& str, const android::StringPiece& suffix);
+bool EndsWith(android::StringPiece str, android::StringPiece suffix);
 
 // Creates a new StringPiece that points to a substring of the original string without leading
 // whitespace.
-android::StringPiece TrimLeadingWhitespace(const android::StringPiece& str);
+android::StringPiece TrimLeadingWhitespace(android::StringPiece str);
 
 // Creates a new StringPiece that points to a substring of the original string without trailing
 // whitespace.
-android::StringPiece TrimTrailingWhitespace(const android::StringPiece& str);
+android::StringPiece TrimTrailingWhitespace(android::StringPiece str);
 
 // Creates a new StringPiece that points to a substring of the original string without leading or
 // trailing whitespace.
-android::StringPiece TrimWhitespace(const android::StringPiece& str);
+android::StringPiece TrimWhitespace(android::StringPiece str);
 
 // Tests that the string is a valid Java class name.
-bool IsJavaClassName(const android::StringPiece& str);
+bool IsJavaClassName(android::StringPiece str);
 
 // Tests that the string is a valid Java package name.
-bool IsJavaPackageName(const android::StringPiece& str);
+bool IsJavaPackageName(android::StringPiece str);
 
 // Tests that the string is a valid Android package name. More strict than a Java package name.
 // - First character of each component (separated by '.') must be an ASCII letter.
 // - Subsequent characters of a component can be ASCII alphanumeric or an underscore.
 // - Package must contain at least two components, unless it is 'android'.
 // - The maximum package name length is 223.
-bool IsAndroidPackageName(const android::StringPiece& str);
+bool IsAndroidPackageName(android::StringPiece str);
 
 // Tests that the string is a valid Android split name.
 // - First character of each component (separated by '.') must be an ASCII letter.
 // - Subsequent characters of a component can be ASCII alphanumeric or an underscore.
-bool IsAndroidSplitName(const android::StringPiece& str);
+bool IsAndroidSplitName(android::StringPiece str);
 
 // Tests that the string is a valid Android shared user id.
 // - First character of each component (separated by '.') must be an ASCII letter.
@@ -93,8 +93,7 @@
 // - Must contain at least two components, unless package name is 'android'.
 // - The maximum shared user id length is 223.
 // - Treat empty string as valid, it's the case of no shared user id.
-bool IsAndroidSharedUserId(const android::StringPiece& package_name,
-                           const android::StringPiece& shared_user_id);
+bool IsAndroidSharedUserId(android::StringPiece package_name, android::StringPiece shared_user_id);
 
 // Converts the class name to a fully qualified class name from the given
 // `package`. Ex:
@@ -103,8 +102,8 @@
 // .asdf        --> package.asdf
 // .a.b         --> package.a.b
 // asdf.adsf    --> asdf.adsf
-std::optional<std::string> GetFullyQualifiedClassName(const android::StringPiece& package,
-                                                      const android::StringPiece& class_name);
+std::optional<std::string> GetFullyQualifiedClassName(android::StringPiece package,
+                                                      android::StringPiece class_name);
 
 // Retrieves the formatted name of aapt2.
 const char* GetToolName();
@@ -152,16 +151,16 @@
 // explicitly specifying an index) when there are more than one argument. This is an error
 // because translations may rearrange the order of the arguments in the string, which will
 // break the string interpolation.
-bool VerifyJavaStringFormat(const android::StringPiece& str);
+bool VerifyJavaStringFormat(android::StringPiece str);
 
-bool AppendStyledString(const android::StringPiece& input, bool preserve_spaces,
-                        std::string* out_str, std::string* out_error);
+bool AppendStyledString(android::StringPiece input, bool preserve_spaces, std::string* out_str,
+                        std::string* out_error);
 
 class StringBuilder {
  public:
   StringBuilder() = default;
 
-  StringBuilder& Append(const android::StringPiece& str);
+  StringBuilder& Append(android::StringPiece str);
   const std::string& ToString() const;
   const std::string& Error() const;
   bool IsEmpty() const;
@@ -229,7 +228,7 @@
    private:
     friend class Tokenizer;
 
-    iterator(const android::StringPiece& s, char sep, const android::StringPiece& tok, bool end);
+    iterator(android::StringPiece s, char sep, android::StringPiece tok, bool end);
 
     android::StringPiece str_;
     char separator_;
@@ -237,7 +236,7 @@
     bool end_;
   };
 
-  Tokenizer(const android::StringPiece& str, char sep);
+  Tokenizer(android::StringPiece str, char sep);
 
   iterator begin() const {
     return begin_;
@@ -252,7 +251,7 @@
   const iterator end_;
 };
 
-inline Tokenizer Tokenize(const android::StringPiece& str, char sep) {
+inline Tokenizer Tokenize(android::StringPiece str, char sep) {
   return Tokenizer(str, sep);
 }
 
@@ -263,7 +262,7 @@
 // Extracts ".xml" into outSuffix.
 //
 // Returns true if successful.
-bool ExtractResFilePathParts(const android::StringPiece& path, android::StringPiece* out_prefix,
+bool ExtractResFilePathParts(android::StringPiece path, android::StringPiece* out_prefix,
                              android::StringPiece* out_entry, android::StringPiece* out_suffix);
 
 }  // namespace util
diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp
index 4ebcb11..15135690 100644
--- a/tools/aapt2/util/Util_test.cpp
+++ b/tools/aapt2/util/Util_test.cpp
@@ -84,6 +84,14 @@
   ASSERT_THAT(*iter, Eq(StringPiece()));
 }
 
+TEST(UtilTest, TokenizeNone) {
+  auto tokenizer = util::Tokenize(StringPiece("none"), '.');
+  auto iter = tokenizer.begin();
+  ASSERT_THAT(*iter, Eq("none"));
+  ++iter;
+  ASSERT_THAT(iter, Eq(tokenizer.end()));
+}
+
 TEST(UtilTest, IsJavaClassName) {
   EXPECT_TRUE(util::IsJavaClassName("android.test.Class"));
   EXPECT_TRUE(util::IsJavaClassName("android.test.Class$Inner"));
diff --git a/tools/aapt2/xml/XmlActionExecutor.cpp b/tools/aapt2/xml/XmlActionExecutor.cpp
index 9bdbd22..3ccbaa2 100644
--- a/tools/aapt2/xml/XmlActionExecutor.cpp
+++ b/tools/aapt2/xml/XmlActionExecutor.cpp
@@ -84,7 +84,7 @@
         error_msg << "unexpected element ";
         PrintElementToDiagMessage(child_el, &error_msg);
         error_msg << " found in ";
-        for (const StringPiece& element : *bread_crumb) {
+        for (StringPiece element : *bread_crumb) {
           error_msg << "<" << element << ">";
         }
         if (policy == XmlActionExecutorPolicy::kAllowListWarning) {
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index f51e8a4..8dea8ea 100644
--- a/tools/aapt2/xml/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -169,7 +169,7 @@
   stack->last_text_node = util::make_unique<Text>();
   stack->last_text_node->line_number = XML_GetCurrentLineNumber(parser);
   stack->last_text_node->column_number = XML_GetCurrentColumnNumber(parser);
-  stack->last_text_node->text = str.to_string();
+  stack->last_text_node->text.assign(str);
 }
 
 static void XMLCALL CommentDataHandler(void* user_data, const char* comment) {
@@ -417,11 +417,11 @@
   children.insert(children.begin() + index, std::move(child));
 }
 
-Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece& name) {
+Attribute* Element::FindAttribute(StringPiece ns, StringPiece name) {
   return const_cast<Attribute*>(static_cast<const Element*>(this)->FindAttribute(ns, name));
 }
 
-const Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece& name) const {
+const Attribute* Element::FindAttribute(StringPiece ns, StringPiece name) const {
   for (const auto& attr : attributes) {
     if (ns == attr.namespace_uri && name == attr.name) {
       return &attr;
@@ -430,7 +430,7 @@
   return nullptr;
 }
 
-void Element::RemoveAttribute(const StringPiece& ns, const StringPiece& name) {
+void Element::RemoveAttribute(StringPiece ns, StringPiece name) {
   auto new_attr_end = std::remove_if(attributes.begin(), attributes.end(),
     [&](const Attribute& attr) -> bool {
       return ns == attr.namespace_uri && name == attr.name;
@@ -439,34 +439,32 @@
   attributes.erase(new_attr_end, attributes.end());
 }
 
-Attribute* Element::FindOrCreateAttribute(const StringPiece& ns, const StringPiece& name) {
+Attribute* Element::FindOrCreateAttribute(StringPiece ns, StringPiece name) {
   Attribute* attr = FindAttribute(ns, name);
   if (attr == nullptr) {
-    attributes.push_back(Attribute{ns.to_string(), name.to_string()});
+    attributes.push_back(Attribute{std::string(ns), std::string(name)});
     attr = &attributes.back();
   }
   return attr;
 }
 
-Element* Element::FindChild(const StringPiece& ns, const StringPiece& name) {
+Element* Element::FindChild(StringPiece ns, StringPiece name) {
   return FindChildWithAttribute(ns, name, {}, {}, {});
 }
 
-const Element* Element::FindChild(const StringPiece& ns, const StringPiece& name) const {
+const Element* Element::FindChild(StringPiece ns, StringPiece name) const {
   return FindChildWithAttribute(ns, name, {}, {}, {});
 }
 
-Element* Element::FindChildWithAttribute(const StringPiece& ns, const StringPiece& name,
-                                         const StringPiece& attr_ns, const StringPiece& attr_name,
-                                         const StringPiece& attr_value) {
+Element* Element::FindChildWithAttribute(StringPiece ns, StringPiece name, StringPiece attr_ns,
+                                         StringPiece attr_name, StringPiece attr_value) {
   return const_cast<Element*>(static_cast<const Element*>(this)->FindChildWithAttribute(
       ns, name, attr_ns, attr_name, attr_value));
 }
 
-const Element* Element::FindChildWithAttribute(const StringPiece& ns, const StringPiece& name,
-                                               const StringPiece& attr_ns,
-                                               const StringPiece& attr_name,
-                                               const StringPiece& attr_value) const {
+const Element* Element::FindChildWithAttribute(StringPiece ns, StringPiece name,
+                                               StringPiece attr_ns, StringPiece attr_name,
+                                               StringPiece attr_value) const {
   for (const auto& child : children) {
     if (const Element* el = NodeCast<Element>(child.get())) {
       if (ns == el->namespace_uri && name == el->name) {
@@ -559,7 +557,7 @@
 }
 
 std::optional<ExtractedPackage> PackageAwareVisitor::TransformPackageAlias(
-    const StringPiece& alias) const {
+    StringPiece alias) const {
   if (alias.empty()) {
     return ExtractedPackage{{}, false /*private*/};
   }
diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h
index 5bc55b6..c253b0a 100644
--- a/tools/aapt2/xml/XmlDom.h
+++ b/tools/aapt2/xml/XmlDom.h
@@ -96,27 +96,22 @@
   void AppendChild(std::unique_ptr<Node> child);
   void InsertChild(size_t index, std::unique_ptr<Node> child);
 
-  Attribute* FindAttribute(const android::StringPiece& ns, const android::StringPiece& name);
-  const Attribute* FindAttribute(const android::StringPiece& ns,
-                                 const android::StringPiece& name) const;
-  Attribute* FindOrCreateAttribute(const android::StringPiece& ns,
-                                   const android::StringPiece& name);
-  void RemoveAttribute(const android::StringPiece& ns,
-                       const android::StringPiece& name);
+  Attribute* FindAttribute(android::StringPiece ns, android::StringPiece name);
+  const Attribute* FindAttribute(android::StringPiece ns, android::StringPiece name) const;
+  Attribute* FindOrCreateAttribute(android::StringPiece ns, android::StringPiece name);
+  void RemoveAttribute(android::StringPiece ns, android::StringPiece name);
 
-  Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name);
-  const Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name) const;
+  Element* FindChild(android::StringPiece ns, android::StringPiece name);
+  const Element* FindChild(android::StringPiece ns, android::StringPiece name) const;
 
-  Element* FindChildWithAttribute(const android::StringPiece& ns, const android::StringPiece& name,
-                                  const android::StringPiece& attr_ns,
-                                  const android::StringPiece& attr_name,
-                                  const android::StringPiece& attr_value);
+  Element* FindChildWithAttribute(android::StringPiece ns, android::StringPiece name,
+                                  android::StringPiece attr_ns, android::StringPiece attr_name,
+                                  android::StringPiece attr_value);
 
-  const Element* FindChildWithAttribute(const android::StringPiece& ns,
-                                        const android::StringPiece& name,
-                                        const android::StringPiece& attr_ns,
-                                        const android::StringPiece& attr_name,
-                                        const android::StringPiece& attr_value) const;
+  const Element* FindChildWithAttribute(android::StringPiece ns, android::StringPiece name,
+                                        android::StringPiece attr_ns,
+                                        android::StringPiece attr_name,
+                                        android::StringPiece attr_value) const;
 
   std::vector<Element*> GetChildElements();
 
@@ -235,8 +230,7 @@
  public:
   using Visitor::Visit;
 
-  std::optional<ExtractedPackage> TransformPackageAlias(
-      const android::StringPiece& alias) const override;
+  std::optional<ExtractedPackage> TransformPackageAlias(android::StringPiece alias) const override;
 
  protected:
   PackageAwareVisitor() = default;
diff --git a/tools/aapt2/xml/XmlPullParser.cpp b/tools/aapt2/xml/XmlPullParser.cpp
index bfa0749..d79446b 100644
--- a/tools/aapt2/xml/XmlPullParser.cpp
+++ b/tools/aapt2/xml/XmlPullParser.cpp
@@ -140,8 +140,7 @@
   return event_queue_.front().data2;
 }
 
-std::optional<ExtractedPackage> XmlPullParser::TransformPackageAlias(
-    const StringPiece& alias) const {
+std::optional<ExtractedPackage> XmlPullParser::TransformPackageAlias(StringPiece alias) const {
   if (alias.empty()) {
     return ExtractedPackage{{}, false /*private*/};
   }
@@ -307,7 +306,7 @@
                                       parser->depth_ });
 }
 
-std::optional<StringPiece> FindAttribute(const XmlPullParser* parser, const StringPiece& name) {
+std::optional<StringPiece> FindAttribute(const XmlPullParser* parser, StringPiece name) {
   auto iter = parser->FindAttribute("", name);
   if (iter != parser->end_attributes()) {
     return StringPiece(util::TrimWhitespace(iter->value));
@@ -315,8 +314,7 @@
   return {};
 }
 
-std::optional<StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser,
-                                                 const StringPiece& name) {
+std::optional<StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser, StringPiece name) {
   auto iter = parser->FindAttribute("", name);
   if (iter != parser->end_attributes()) {
     StringPiece trimmed = util::TrimWhitespace(iter->value);
diff --git a/tools/aapt2/xml/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h
index ab34772..fe4cd01 100644
--- a/tools/aapt2/xml/XmlPullParser.h
+++ b/tools/aapt2/xml/XmlPullParser.h
@@ -120,8 +120,7 @@
    * If xmlns:app="http://schemas.android.com/apk/res-auto", then
    * 'package' will be set to 'defaultPackage'.
    */
-  std::optional<ExtractedPackage> TransformPackageAlias(
-      const android::StringPiece& alias) const override;
+  std::optional<ExtractedPackage> TransformPackageAlias(android::StringPiece alias) const override;
 
   struct PackageDecl {
     std::string prefix;
@@ -194,7 +193,7 @@
  * Finds the attribute in the current element within the global namespace.
  */
 std::optional<android::StringPiece> FindAttribute(const XmlPullParser* parser,
-                                                  const android::StringPiece& name);
+                                                  android::StringPiece name);
 
 /**
  * Finds the attribute in the current element within the global namespace. The
@@ -202,7 +201,7 @@
  * must not be the empty string.
  */
 std::optional<android::StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser,
-                                                          const android::StringPiece& name);
+                                                          android::StringPiece name);
 
 //
 // Implementation
diff --git a/tools/aapt2/xml/XmlUtil.cpp b/tools/aapt2/xml/XmlUtil.cpp
index 114b5ba..709755e 100644
--- a/tools/aapt2/xml/XmlUtil.cpp
+++ b/tools/aapt2/xml/XmlUtil.cpp
@@ -27,7 +27,7 @@
 namespace aapt {
 namespace xml {
 
-std::string BuildPackageNamespace(const StringPiece& package, bool private_reference) {
+std::string BuildPackageNamespace(StringPiece package, bool private_reference) {
   std::string result = private_reference ? kSchemaPrivatePrefix : kSchemaPublicPrefix;
   result.append(package.data(), package.size());
   return result;
@@ -41,7 +41,7 @@
     if (package.empty()) {
       return {};
     }
-    return ExtractedPackage{package.to_string(), false /* is_private */};
+    return ExtractedPackage{std::string(package), false /* is_private */};
 
   } else if (util::StartsWith(namespace_uri, kSchemaPrivatePrefix)) {
     StringPiece schema_prefix = kSchemaPrivatePrefix;
@@ -50,7 +50,7 @@
     if (package.empty()) {
       return {};
     }
-    return ExtractedPackage{package.to_string(), true /* is_private */};
+    return ExtractedPackage{std::string(package), true /* is_private */};
 
   } else if (namespace_uri == kSchemaAuto) {
     return ExtractedPackage{std::string(), true /* is_private */};
diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h
index 1ab05a9..ad676ca 100644
--- a/tools/aapt2/xml/XmlUtil.h
+++ b/tools/aapt2/xml/XmlUtil.h
@@ -59,8 +59,7 @@
 //
 // If privateReference == true, the package will be of the form:
 //   http://schemas.android.com/apk/prv/res/<package>
-std::string BuildPackageNamespace(const android::StringPiece& package,
-                                  bool private_reference = false);
+std::string BuildPackageNamespace(android::StringPiece package, bool private_reference = false);
 
 // Interface representing a stack of XML namespace declarations. When looking up the package for a
 // namespace prefix, the stack is checked from top to bottom.
@@ -69,7 +68,7 @@
 
   // Returns an ExtractedPackage struct if the alias given corresponds with a package declaration.
   virtual std::optional<ExtractedPackage> TransformPackageAlias(
-      const android::StringPiece& alias) const = 0;
+      android::StringPiece alias) const = 0;
 };
 
 // Helper function for transforming the original Reference inRef to a fully qualified reference
diff --git a/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt b/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt
index f29d9b2..c6f6d45 100644
--- a/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt
+++ b/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt
@@ -54,6 +54,7 @@
             "java.lang.Short",
             "java.lang.String",
             "java.lang.Void",
+            "java.util.UUID",
             "android.os.Parcelable.Creator",
         )