Merge "CDM: increase char limit for nearby_device_streaming"
diff --git a/core/api/current.txt b/core/api/current.txt
index 5f3b1f3..dd3d6eb 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -125,6 +125,18 @@
     field public static final String MANAGE_DEVICE_POLICY_ACROSS_USERS = "android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS";
     field public static final String MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL = "android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL";
     field public static final String MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL = "android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL";
+    field public static final String MANAGE_DEVICE_POLICY_APPS_CONTROL = "android.permission.MANAGE_DEVICE_POLICY_APPS_CONTROL";
+    field public static final String MANAGE_DEVICE_POLICY_APP_RESTRICTIONS = "android.permission.MANAGE_DEVICE_POLICY_APP_RESTRICTIONS";
+    field public static final String MANAGE_DEVICE_POLICY_BACKUP_SERVICE = "android.permission.MANAGE_DEVICE_POLICY_BACKUP_SERVICE";
+    field public static final String MANAGE_DEVICE_POLICY_CALLS = "android.permission.MANAGE_DEVICE_POLICY_CALLS";
+    field public static final String MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES = "android.permission.MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES";
+    field public static final String MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES = "android.permission.MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES";
+    field public static final String MANAGE_DEVICE_POLICY_LOCK_TASK = "android.permission.MANAGE_DEVICE_POLICY_LOCK_TASK";
+    field public static final String MANAGE_DEVICE_POLICY_MODIFY_USERS = "android.permission.MANAGE_DEVICE_POLICY_MODIFY_USERS";
+    field public static final String MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY = "android.permission.MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY";
+    field public static final String MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS = "android.permission.MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS";
+    field public static final String MANAGE_DEVICE_POLICY_SAFE_BOOT = "android.permission.MANAGE_DEVICE_POLICY_SAFE_BOOT";
+    field public static final String MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE = "android.permission.MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE";
     field public static final String MANAGE_DEVICE_POLICY_TIME = "android.permission.MANAGE_DEVICE_POLICY_TIME";
     field public static final String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS";
     field public static final String MANAGE_EXTERNAL_STORAGE = "android.permission.MANAGE_EXTERNAL_STORAGE";
@@ -7535,6 +7547,7 @@
     method @Nullable public android.app.admin.PackagePolicy getManagedProfileCallerIdAccessPolicy();
     method @Nullable public android.app.admin.PackagePolicy getManagedProfileContactsAccessPolicy();
     method public long getManagedProfileMaximumTimeOff(@NonNull android.content.ComponentName);
+    method @NonNull public android.app.admin.ManagedSubscriptionsPolicy getManagedSubscriptionsPolicy();
     method public int getMaximumFailedPasswordsForWipe(@Nullable android.content.ComponentName);
     method public long getMaximumTimeToLock(@Nullable android.content.ComponentName);
     method @NonNull public java.util.List<java.lang.String> getMeteredDataDisabledPackages(@NonNull android.content.ComponentName);
@@ -7685,6 +7698,7 @@
     method public void setManagedProfileCallerIdAccessPolicy(@Nullable android.app.admin.PackagePolicy);
     method public void setManagedProfileContactsAccessPolicy(@Nullable android.app.admin.PackagePolicy);
     method public void setManagedProfileMaximumTimeOff(@NonNull android.content.ComponentName, long);
+    method public void setManagedSubscriptionsPolicy(@Nullable android.app.admin.ManagedSubscriptionsPolicy);
     method public void setMasterVolumeMuted(@NonNull android.content.ComponentName, boolean);
     method public void setMaximumFailedPasswordsForWipe(@NonNull android.content.ComponentName, int);
     method public void setMaximumTimeToLock(@NonNull android.content.ComponentName, long);
@@ -7987,6 +8001,16 @@
     method public java.time.MonthDay getStart();
   }
 
+  public final class ManagedSubscriptionsPolicy implements android.os.Parcelable {
+    ctor public ManagedSubscriptionsPolicy(int);
+    method public int describeContents();
+    method public int getPolicyType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.ManagedSubscriptionsPolicy> CREATOR;
+    field public static final int TYPE_ALL_MANAGED_SUBSCRIPTIONS = 1; // 0x1
+    field public static final int TYPE_ALL_PERSONAL_SUBSCRIPTIONS = 0; // 0x0
+  }
+
   public abstract class NetworkEvent implements android.os.Parcelable {
     method public int describeContents();
     method public long getId();
@@ -23773,6 +23797,8 @@
     method @NonNull public android.media.MediaRoute2Info.Builder setDescription(@Nullable CharSequence);
     method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle);
     method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri);
+    method @NonNull public android.media.MediaRoute2Info.Builder setVisibilityPublic();
+    method @NonNull public android.media.MediaRoute2Info.Builder setVisibilityRestricted(@NonNull java.util.Set<java.lang.String>);
     method @NonNull public android.media.MediaRoute2Info.Builder setVolume(int);
     method @NonNull public android.media.MediaRoute2Info.Builder setVolumeHandling(int);
     method @NonNull public android.media.MediaRoute2Info.Builder setVolumeMax(int);
@@ -24314,15 +24340,19 @@
 
   public final class RouteListingPreference implements android.os.Parcelable {
     method public int describeContents();
+    method @Nullable public android.content.ComponentName getInAppOnlyItemRoutingReceiver();
     method @NonNull public java.util.List<android.media.RouteListingPreference.Item> getItems();
     method public boolean getUseSystemOrdering();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final String ACTION_TRANSFER_MEDIA = "android.media.action.TRANSFER_MEDIA";
     field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteListingPreference> CREATOR;
+    field public static final String EXTRA_ROUTE_ID = "android.media.extra.ROUTE_ID";
   }
 
   public static final class RouteListingPreference.Builder {
     ctor public RouteListingPreference.Builder();
     method @NonNull public android.media.RouteListingPreference build();
+    method @NonNull public android.media.RouteListingPreference.Builder setInAppOnlyItemRoutingReceiver(@Nullable android.content.ComponentName);
     method @NonNull public android.media.RouteListingPreference.Builder setItems(@NonNull java.util.List<android.media.RouteListingPreference.Item>);
     method @NonNull public android.media.RouteListingPreference.Builder setUseSystemOrdering(boolean);
   }
@@ -24337,6 +24367,7 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteListingPreference.Item> CREATOR;
     field public static final int DISABLE_REASON_AD = 3; // 0x3
     field public static final int DISABLE_REASON_DOWNLOADED_CONTENT = 2; // 0x2
+    field public static final int DISABLE_REASON_IN_APP_ONLY = 4; // 0x4
     field public static final int DISABLE_REASON_NONE = 0; // 0x0
     field public static final int DISABLE_REASON_SUBSCRIPTION_REQUIRED = 1; // 0x1
     field public static final int FLAG_ONGOING_SESSION = 1; // 0x1
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index de03ba4..484ef4f 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -10997,6 +10997,51 @@
     }
 
     /**
+     * Called by a profile owner of an organization-owned device to specify {@link
+     * ManagedSubscriptionsPolicy}
+     *
+     * <p>Managed subscriptions policy controls how SIMs would be associated with the
+     * managed profile. For example a policy of type
+     * {@link ManagedSubscriptionsPolicy#TYPE_ALL_MANAGED_SUBSCRIPTIONS} assigns all
+     * SIM-based subscriptions to the managed profile. In this case OEM default
+     * dialer and messages app are automatically installed in the managed profile
+     * and all incoming and outgoing calls and text messages are handled by them.
+     * <p>This API can only be called during device setup.
+     *
+     * @param policy {@link ManagedSubscriptionsPolicy} policy, passing null for this resets the
+     *               policy to be the default.
+     * @throws SecurityException     if the caller is not a profile owner on an organization-owned
+     *                               managed profile.
+     * @throws IllegalStateException if called after the device setup has been completed.
+     * @see ManagedSubscriptionsPolicy
+     */
+    public void setManagedSubscriptionsPolicy(@Nullable ManagedSubscriptionsPolicy policy) {
+        throwIfParentInstance("setManagedSubscriptionsPolicy");
+        try {
+            mService.setManagedSubscriptionsPolicy(policy);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the current {@link ManagedSubscriptionsPolicy}.
+     * If the policy has not been set, it will return a default policy of Type {@link
+     * ManagedSubscriptionsPolicy#TYPE_ALL_PERSONAL_SUBSCRIPTIONS}.
+     *
+     * @see #setManagedSubscriptionsPolicy(ManagedSubscriptionsPolicy)
+     */
+    @NonNull
+    public ManagedSubscriptionsPolicy getManagedSubscriptionsPolicy() {
+        throwIfParentInstance("getManagedSubscriptionsPolicy");
+        try {
+            return mService.getManagedSubscriptionsPolicy();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Similar to {@link #logoutUser(ComponentName)}, except:
      *
      * <ul>
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 26d7667..20695ca 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -34,6 +34,7 @@
 import android.app.admin.FactoryResetProtectionPolicy;
 import android.app.admin.ManagedProfileProvisioningParams;
 import android.app.admin.FullyManagedDeviceProvisioningParams;
+import android.app.admin.ManagedSubscriptionsPolicy;
 import android.app.admin.WifiSsidPolicy;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -583,4 +584,7 @@
 
     void setMtePolicy(int flag);
     int getMtePolicy();
+
+    void setManagedSubscriptionsPolicy(in ManagedSubscriptionsPolicy policy);
+    ManagedSubscriptionsPolicy getManagedSubscriptionsPolicy();
 }
diff --git a/core/java/android/app/timedetector/TimePoint.aidl b/core/java/android/app/admin/ManagedSubscriptionsPolicy.aidl
similarity index 74%
rename from core/java/android/app/timedetector/TimePoint.aidl
rename to core/java/android/app/admin/ManagedSubscriptionsPolicy.aidl
index 80d4bc1..f634f11 100644
--- a/core/java/android/app/timedetector/TimePoint.aidl
+++ b/core/java/android/app/admin/ManagedSubscriptionsPolicy.aidl
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2022, The Android Open Source Project
+ * 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
+ *      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,
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package android.app.admin;
 
-parcelable TimePoint;
+parcelable ManagedSubscriptionsPolicy;
\ No newline at end of file
diff --git a/core/java/android/app/admin/ManagedSubscriptionsPolicy.java b/core/java/android/app/admin/ManagedSubscriptionsPolicy.java
new file mode 100644
index 0000000..1098c38
--- /dev/null
+++ b/core/java/android/app/admin/ManagedSubscriptionsPolicy.java
@@ -0,0 +1,156 @@
+/*
+ * 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.app.admin;
+
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * A policy class that describes how managed SIM subscriptions should behave on the device.
+ */
+public final class ManagedSubscriptionsPolicy implements Parcelable {
+    private static final String TAG = "ManagedSubscriptionsPolicy";
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"TYPE_"}, value = {TYPE_ALL_PERSONAL_SUBSCRIPTIONS,
+            TYPE_ALL_MANAGED_SUBSCRIPTIONS})
+    @interface ManagedSubscriptionsPolicyType {
+    }
+
+    /**
+     * Represents default policy to not have any managed subscriptions on the device.
+     */
+    public static final int TYPE_ALL_PERSONAL_SUBSCRIPTIONS = 0;
+
+    /**
+     * Represents policy to have only managed subscriptions on the device, any existing and
+     * future subscriptions on the device are exclusively associated with the managed profile.
+     *
+     * <p>When a subscription is associated with the managed profile, incoming/outgoing calls and
+     * text message using that subscription would only work via apps on managed profile.
+     * Also, Call logs and messages would be accessible only from the managed profile.
+     */
+    public static final int TYPE_ALL_MANAGED_SUBSCRIPTIONS = 1;
+
+    @ManagedSubscriptionsPolicyType
+    private final int mPolicyType;
+
+    private static final String KEY_POLICY_TYPE = "policy_type";
+
+    public ManagedSubscriptionsPolicy(@ManagedSubscriptionsPolicyType int policyType) {
+        if (policyType != TYPE_ALL_PERSONAL_SUBSCRIPTIONS
+                && policyType != TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
+            throw new IllegalArgumentException("Invalid policy type");
+        }
+        mPolicyType = policyType;
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<ManagedSubscriptionsPolicy> CREATOR =
+            new Parcelable.Creator<ManagedSubscriptionsPolicy>() {
+                public ManagedSubscriptionsPolicy createFromParcel(Parcel in) {
+                    ManagedSubscriptionsPolicy policy = new ManagedSubscriptionsPolicy(
+                            in.readInt());
+                    return policy;
+                }
+
+                @Override
+                public ManagedSubscriptionsPolicy[] newArray(int size) {
+                    return new ManagedSubscriptionsPolicy[size];
+                }
+            };
+
+    /**
+     * Returns the type of managed subscriptions policy, or {@link #TYPE_ALL_PERSONAL_SUBSCRIPTIONS}
+     * if no policy has been set.
+     *
+     * @return The policy type.
+     */
+    @ManagedSubscriptionsPolicyType
+    public int getPolicyType() {
+        return mPolicyType;
+    }
+
+    @Override
+    public String toString() {
+        return TextUtils.formatSimple("ManagedSubscriptionsPolicy (type: %d)", mPolicyType);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mPolicyType);
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof ManagedSubscriptionsPolicy)) {
+            return false;
+        }
+        ManagedSubscriptionsPolicy that = (ManagedSubscriptionsPolicy) thatObject;
+        return mPolicyType == that.mPolicyType;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mPolicyType);
+    }
+
+    /** @hide */
+    @Nullable
+    public static ManagedSubscriptionsPolicy readFromXml(@NonNull TypedXmlPullParser parser) {
+        try {
+            ManagedSubscriptionsPolicy policy = new ManagedSubscriptionsPolicy(
+                    parser.getAttributeInt(null, KEY_POLICY_TYPE, -1));
+
+            return policy;
+        } catch (IllegalArgumentException e) {
+            // Fail through
+            Log.w(TAG, "Load xml failed", e);
+        }
+        return null;
+    }
+
+    /**
+     * @hide
+     */
+    public void saveToXml(TypedXmlSerializer out) throws IOException {
+        out.attributeInt(null, KEY_POLICY_TYPE, mPolicyType);
+    }
+}
diff --git a/core/java/android/app/timedetector/ITimeDetectorService.aidl b/core/java/android/app/timedetector/ITimeDetectorService.aidl
index a0c898e..96eddd2 100644
--- a/core/java/android/app/timedetector/ITimeDetectorService.aidl
+++ b/core/java/android/app/timedetector/ITimeDetectorService.aidl
@@ -24,7 +24,6 @@
 import android.app.time.UnixEpochTime;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.TelephonyTimeSuggestion;
-import android.app.timedetector.TimePoint;
 
 /**
  * Binder APIs to communicate with the time detector service.
@@ -55,5 +54,5 @@
   boolean suggestManualTime(in ManualTimeSuggestion timeSuggestion);
   void suggestTelephonyTime(in TelephonyTimeSuggestion timeSuggestion);
 
-  TimePoint latestNetworkTime();
+  UnixEpochTime latestNetworkTime();
 }
diff --git a/core/java/android/app/timedetector/TimePoint.java b/core/java/android/app/timedetector/TimePoint.java
deleted file mode 100644
index aa079a9..0000000
--- a/core/java/android/app/timedetector/TimePoint.java
+++ /dev/null
@@ -1,103 +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 android.app.timedetector;
-
-import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.util.Objects;
-
-/**
- * Data class for passing a Unix epoch time anchored to the elapsed realtime clock.
- *
- * @hide
- */
-public final class TimePoint implements Parcelable {
-
-    private final long mUnixEpochTimeMillis;
-    private final long mElapsedRealtimeMillis;
-
-    public TimePoint(long unixEpochTimeMillis, long elapsedRealtimeMillis) {
-        mUnixEpochTimeMillis = unixEpochTimeMillis;
-        mElapsedRealtimeMillis = elapsedRealtimeMillis;
-    }
-
-    /**
-     * The current Unix epoch time, according to the external source.
-     */
-    public long getUnixEpochTimeMillis() {
-        return mUnixEpochTimeMillis;
-    }
-
-    /**
-     * The elapsed millis since boot when {@link #getUnixEpochTimeMillis} was computed.
-     */
-    public long getElapsedRealtimeMillis() {
-        return mElapsedRealtimeMillis;
-    }
-
-    @Override
-    public void writeToParcel(Parcel out, int flags) {
-        out.writeLong(mUnixEpochTimeMillis);
-        out.writeLong(mElapsedRealtimeMillis);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (!(o instanceof TimePoint)) {
-            return false;
-        }
-        TimePoint timePoint = (TimePoint) o;
-        return mUnixEpochTimeMillis == timePoint.mUnixEpochTimeMillis
-                && mElapsedRealtimeMillis == timePoint.mElapsedRealtimeMillis;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mUnixEpochTimeMillis, mElapsedRealtimeMillis);
-    }
-
-    @Override
-    public String toString() {
-        return "TimePoint{"
-                + "mUnixEpochTimeMillis=" + mUnixEpochTimeMillis
-                + ", mElapsedRealtimeMillis=" + mElapsedRealtimeMillis
-                + '}';
-    }
-
-    public static final @NonNull Creator<TimePoint> CREATOR =
-            new Creator<TimePoint>() {
-                public TimePoint createFromParcel(Parcel in) {
-                    long unixEpochTime = in.readLong();
-                    long elapsedRealtimeMillis = in.readLong();
-                    return new TimePoint(unixEpochTime, elapsedRealtimeMillis);
-                }
-
-                public TimePoint[] newArray(int size) {
-                    return new TimePoint[size];
-                }
-            };
-}
diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java
index 2b75a23..831ca86 100644
--- a/core/java/android/os/SystemClock.java
+++ b/core/java/android/os/SystemClock.java
@@ -18,8 +18,8 @@
 
 import android.annotation.NonNull;
 import android.app.IAlarmManager;
+import android.app.time.UnixEpochTime;
 import android.app.timedetector.ITimeDetectorService;
-import android.app.timedetector.TimePoint;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.location.ILocationManager;
@@ -300,7 +300,7 @@
         ITimeDetectorService timeDetectorService = ITimeDetectorService.Stub
                 .asInterface(ServiceManager.getService(Context.TIME_DETECTOR_SERVICE));
         if (timeDetectorService != null) {
-            TimePoint time;
+            UnixEpochTime time;
             try {
                 time = timeDetectorService.latestNetworkTime();
             } catch (ParcelableException e) {
diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java
index 40c0fee..f53737a 100644
--- a/core/java/android/window/BackEvent.java
+++ b/core/java/android/window/BackEvent.java
@@ -74,6 +74,9 @@
      * and animation should seek to its end state. Exact end value may vary depending on
      * screen size.
      * </ol>
+     * <li> After the gesture finishes in cancel state, this method keeps getting invoked until the
+     * progress value animates back to 0.
+     * </ol>
      * In-between locations are linearly interpolated based on horizontal distance from the starting
      * edge and smooth clamped to 1 when the distance exceeds a system-wide threshold.
      */
diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java
index 14a57e0..618670a 100644
--- a/core/java/android/window/BackProgressAnimator.java
+++ b/core/java/android/window/BackProgressAnimator.java
@@ -16,8 +16,10 @@
 
 package android.window;
 
+import android.annotation.NonNull;
 import android.util.FloatProperty;
 
+import com.android.internal.dynamicanimation.animation.DynamicAnimation;
 import com.android.internal.dynamicanimation.animation.SpringAnimation;
 import com.android.internal.dynamicanimation.animation.SpringForce;
 
@@ -126,6 +128,27 @@
         mProgress = 0;
     }
 
+    /**
+     * Animate the back progress animation from current progress to start position.
+     * This should be called when back is cancelled.
+     *
+     * @param finishCallback the callback to be invoked when the progress is reach to 0.
+     */
+    public void onBackCancelled(@NonNull Runnable finishCallback) {
+        final DynamicAnimation.OnAnimationEndListener listener =
+                new DynamicAnimation.OnAnimationEndListener() {
+            @Override
+            public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
+                    float velocity) {
+                mSpring.removeEndListener(this);
+                finishCallback.run();
+                reset();
+            }
+        };
+        mSpring.addEndListener(listener);
+        mSpring.animateToFinalPosition(0);
+    }
+
     private void updateProgressValue(float progress) {
         if (mLastBackEvent == null || mCallback == null || !mStarted) {
             return;
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 64992b9..7a5510c 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -266,11 +266,12 @@
         @Override
         public void onBackCancelled() {
             Handler.getMain().post(() -> {
-                mProgressAnimator.reset();
-                final OnBackAnimationCallback callback = getBackAnimationCallback();
-                if (callback != null) {
-                    callback.onBackCancelled();
-                }
+                mProgressAnimator.onBackCancelled(() -> {
+                    final OnBackAnimationCallback callback = getBackAnimationCallback();
+                    if (callback != null) {
+                        callback.onBackCancelled();
+                    }
+                });
             });
         }
 
diff --git a/core/java/com/android/internal/util/FunctionalUtils.java b/core/java/com/android/internal/util/FunctionalUtils.java
index 91cc4b9..d64a474 100644
--- a/core/java/com/android/internal/util/FunctionalUtils.java
+++ b/core/java/com/android/internal/util/FunctionalUtils.java
@@ -248,6 +248,19 @@
     }
 
     /**
+     * A {@link Supplier} that allows the caller to specify a custom checked {@link Exception} that
+     * can be thrown by the implementer. This is usually used when proxying/wrapping calls between
+     * different classes.
+     *
+     * @param <Output> Method return type
+     * @param <ExceptionType> Checked exception type
+     */
+    @FunctionalInterface
+    public interface ThrowingCheckedSupplier<Output, ExceptionType extends Exception> {
+        Output get() throws ExceptionType;
+    }
+
+    /**
      * A {@link Consumer} that allows the caller to specify a custom checked {@link Exception} that
      * can be thrown by the implementer. This is usually used when proxying/wrapping calls between
      * different classes.
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index bfa5301..76fbffd 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3119,12 +3119,99 @@
 
     <!-- @SystemApi @hide Allows an application to exempt apps from platform restrictions.-->
     <permission android:name="android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS"
-                android:protectionLevel="signature|role" />
+                android:protectionLevel="internal|role" />
 
-    <!-- Allows an application to manage date and time device policy. -->
+    <!-- Allows an application to manage device policy relating to time.
+        <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+        APIs protected by this permission on users different to the calling user.-->
     <permission android:name="android.permission.MANAGE_DEVICE_POLICY_TIME"
                 android:protectionLevel="internal|role" />
 
+    <!-- Allows an application to set the grant state of runtime permissions on packages.
+        <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+        APIs protected by this permission on users different to the calling user.
+    -->
+    <permission android:name="android.permission.MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS"
+                android:protectionLevel="internal|role" />
+
+    <!-- Allows an application to manage the identity of the managing organization.
+        <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+        APIs protected by this permission on users different to the calling user.
+    -->
+    <permission android:name="android.permission.MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY"
+                android:protectionLevel="internal|role" />
+
+    <!-- Allows an application to set support messages for when a user action is affected by an
+        active policy.
+        <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+        APIs protected by this permission on users different to the calling user.
+    -->
+    <permission android:name="android.permission.MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE"
+                android:protectionLevel="internal|role" />
+
+    <!-- Allows an application to manage backup service policy.
+        <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+        APIs protected by this permission on users different to the calling user.
+    -->
+    <permission android:name="android.permission.MANAGE_DEVICE_POLICY_BACKUP_SERVICE"
+                android:protectionLevel="internal|role" />
+
+    <!-- Allows an application to manage lock task policy.
+        <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+        APIs protected by this permission on users different to the calling user.
+    -->
+    <permission android:name="android.permission.MANAGE_DEVICE_POLICY_LOCK_TASK"
+                android:protectionLevel="internal|role" />
+
+    <!-- Allows an application to manage policy regarding modifying applications.
+        <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+        APIs protected by this permission on users different to the calling user.
+    -->
+    <permission android:name="android.permission.MANAGE_DEVICE_POLICY_APPS_CONTROL"
+                android:protectionLevel="internal|role" />
+
+    <!-- Allows an application to manage installing from unknown sources policy.
+        <p>MANAGE_SECURITY_CRITICAL_DEVICE_POLICY_ACROSS_USERS is required to call APIs protected
+        by this permission on users different to the calling user.
+    -->
+    <permission android:name="android.permission.MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES"
+                android:protectionLevel="internal|role" />
+
+    <!-- Allows an application to manage application restrictions.
+        <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+        APIs protected by this permission on users different to the calling user.
+    -->
+    <permission android:name="android.permission.MANAGE_DEVICE_POLICY_APP_RESTRICTIONS"
+                android:protectionLevel="internal|role" />
+
+    <!-- Allows an application to manage calling policy.
+        <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+        APIs protected by this permission on users different to the calling user.
+    -->
+    <permission android:name="android.permission.MANAGE_DEVICE_POLICY_CALLS"
+                android:protectionLevel="internal|role" />
+
+    <!-- Allows an application to manage debugging features policy.
+        <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+        APIs protected by this permission on users different to the calling user.
+    -->
+    <permission android:name="android.permission.MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES"
+                android:protectionLevel="internal|role" />
+
+    <!-- Allows an application to manage policy preventing users from modifying users.
+        <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+        APIs protected by this permission on users different to the calling user
+    -->
+    <permission android:name="android.permission.MANAGE_DEVICE_POLICY_MODIFY_USERS"
+                android:protectionLevel="internal|role" />
+
+    <!-- Allows an application to manage safe boot policy.
+        <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+        APIs protected by this permission on users different to the calling user.
+    -->
+    <permission android:name="android.permission.MANAGE_DEVICE_POLICY_SAFE_BOOT"
+                android:protectionLevel="internal|role" />
+
     <!-- Allows an application to set device policies outside the current user
         that are critical for securing data within the current user.
         <p>Holding this permission allows the use of other held MANAGE_DEVICE_POLICY_*
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
index e36e16c..5d38494 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
@@ -171,6 +171,7 @@
         mInitialTouchPos.set(0, 0);
         mEnteringWindowShow = false;
         mEnteringMargin = 0;
+        mEnteringAnimator = null;
 
         if (mFinishCallback != null) {
             try {
@@ -276,7 +277,7 @@
         }
 
         // End the fade in animation.
-        if (mEnteringAnimator.isRunning()) {
+        if (mEnteringAnimator != null && mEnteringAnimator.isRunning()) {
             mEnteringAnimator.cancel();
         }
 
@@ -329,12 +330,10 @@
         @Override
         public void onBackCancelled() {
             // End the fade in animation.
-            if (mEnteringAnimator.isRunning()) {
+            if (mEnteringAnimator != null && mEnteringAnimator.isRunning()) {
                 mEnteringAnimator.cancel();
             }
-            // TODO (b259608500): Let BackProgressAnimator could play cancel animation.
-            mProgressAnimator.reset();
-            finishAnimation();
+            mProgressAnimator.onBackCancelled(CrossActivityAnimation.this::finishAnimation);
         }
 
         @Override
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 676e259..99a434a 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
@@ -329,8 +329,7 @@
 
         @Override
         public void onBackCancelled() {
-            mProgressAnimator.reset();
-            finishAnimation();
+            mProgressAnimator.onBackCancelled(CrossTaskBackAnimation.this::finishAnimation);
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
new file mode 100644
index 0000000..3608474
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.window.BackEvent.EDGE_LEFT;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.window.BackEvent;
+import android.window.BackMotionEvent;
+import android.window.BackProgressAnimator;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class BackProgressAnimatorTest {
+    private BackProgressAnimator mProgressAnimator;
+    private BackEvent mReceivedBackEvent;
+    private float mTargetProgress = 0.5f;
+    private CountDownLatch mTargetProgressCalled = new CountDownLatch(1);
+    private Handler mMainThreadHandler;
+
+    @Before
+    public void setUp() throws Exception {
+        mMainThreadHandler = new Handler(Looper.getMainLooper());
+        final BackMotionEvent backEvent = new BackMotionEvent(
+                0, 0,
+                0, EDGE_LEFT, null);
+        mMainThreadHandler.post(
+                () -> {
+                    mProgressAnimator = new BackProgressAnimator();
+                    mProgressAnimator.onBackStarted(backEvent, this::onGestureProgress);
+                });
+    }
+
+    @Test
+    public void testBackProgressed() throws InterruptedException {
+        final BackMotionEvent backEvent = new BackMotionEvent(
+                100, 0,
+                mTargetProgress, EDGE_LEFT, null);
+        mMainThreadHandler.post(
+                () -> mProgressAnimator.onBackProgressed(backEvent));
+
+        mTargetProgressCalled.await(1, TimeUnit.SECONDS);
+
+        assertNotNull(mReceivedBackEvent);
+        assertEquals(mReceivedBackEvent.getProgress(), mTargetProgress, 0 /* delta */);
+    }
+
+    @Test
+    public void testBackCancelled() throws InterruptedException {
+        // Give the animator some progress.
+        final BackMotionEvent backEvent = new BackMotionEvent(
+                100, 0,
+                mTargetProgress, EDGE_LEFT, null);
+        mMainThreadHandler.post(
+                () -> mProgressAnimator.onBackProgressed(backEvent));
+        mTargetProgressCalled.await(1, TimeUnit.SECONDS);
+        assertNotNull(mReceivedBackEvent);
+
+        // Trigger animation cancel, the target progress should be 0.
+        mTargetProgress = 0;
+        mTargetProgressCalled = new CountDownLatch(1);
+        CountDownLatch cancelCallbackCalled = new CountDownLatch(1);
+        mMainThreadHandler.post(
+                () -> mProgressAnimator.onBackCancelled(() -> cancelCallbackCalled.countDown()));
+        cancelCallbackCalled.await(1, TimeUnit.SECONDS);
+        mTargetProgressCalled.await(1, TimeUnit.SECONDS);
+        assertNotNull(mReceivedBackEvent);
+        assertEquals(mReceivedBackEvent.getProgress(), mTargetProgress, 0 /* delta */);
+    }
+
+    private void onGestureProgress(BackEvent backEvent) {
+        if (mTargetProgress == backEvent.getProgress()) {
+            mReceivedBackEvent = backEvent;
+            mTargetProgressCalled.countDown();
+        }
+    }
+}
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index e8648cc..1022d93 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -353,6 +353,8 @@
     final Set<String> mDeduplicationIds;
     final Bundle mExtras;
     final String mProviderId;
+    final boolean mIsVisibilityRestricted;
+    final Set<String> mAllowedPackages;
 
     MediaRoute2Info(@NonNull Builder builder) {
         mId = builder.mId;
@@ -372,6 +374,8 @@
         mDeduplicationIds = builder.mDeduplicationIds;
         mExtras = builder.mExtras;
         mProviderId = builder.mProviderId;
+        mIsVisibilityRestricted = builder.mIsVisibilityRestricted;
+        mAllowedPackages = builder.mAllowedPackages;
     }
 
     MediaRoute2Info(@NonNull Parcel in) {
@@ -393,6 +397,8 @@
         mDeduplicationIds = Set.of(in.readStringArray());
         mExtras = in.readBundle();
         mProviderId = in.readString();
+        mIsVisibilityRestricted = in.readBoolean();
+        mAllowedPackages = Set.of(in.createString8Array());
     }
 
     /**
@@ -628,6 +634,15 @@
     }
 
     /**
+     * Returns whether this route is visible to the package with the given name.
+     * @hide
+     */
+    public boolean isVisibleTo(String packageName) {
+        return !mIsVisibilityRestricted || getPackageName().equals(packageName)
+                || mAllowedPackages.contains(packageName);
+    }
+
+    /**
      * Dumps the current state of the object to the given {@code pw} as a human-readable string.
      *
      * <p> Used in the context of dumpsys. </p>
@@ -655,6 +670,8 @@
         pw.println(indent + "mDeduplicationIds=" + mDeduplicationIds);
         pw.println(indent + "mExtras=" + mExtras);
         pw.println(indent + "mProviderId=" + mProviderId);
+        pw.println(indent + "mIsVisibilityRestricted=" + mIsVisibilityRestricted);
+        pw.println(indent + "mAllowedPackages=" + mAllowedPackages);
     }
 
     private void dumpVolume(@NonNull PrintWriter pw, @NonNull String prefix) {
@@ -705,7 +722,9 @@
                 && (mVolume == other.mVolume)
                 && Objects.equals(mAddress, other.mAddress)
                 && Objects.equals(mDeduplicationIds, other.mDeduplicationIds)
-                && Objects.equals(mProviderId, other.mProviderId);
+                && Objects.equals(mProviderId, other.mProviderId)
+                && (mIsVisibilityRestricted == other.mIsVisibilityRestricted)
+                && Objects.equals(mAllowedPackages, other.mAllowedPackages);
     }
 
     @Override
@@ -713,7 +732,8 @@
         // Note: mExtras is not included.
         return Objects.hash(mId, mName, mFeatures, mType, mIsSystem, mIconUri, mDescription,
                 mConnectionState, mClientPackageName, mPackageName, mVolumeHandling, mVolumeMax,
-                mVolume, mAddress, mDeduplicationIds, mProviderId);
+                mVolume, mAddress, mDeduplicationIds, mProviderId, mIsVisibilityRestricted,
+                mAllowedPackages);
     }
 
     @Override
@@ -733,6 +753,8 @@
                 .append(", volume=").append(getVolume())
                 .append(", deduplicationIds=").append(String.join(",", getDeduplicationIds()))
                 .append(", providerId=").append(getProviderId())
+                .append(", isVisibilityRestricted=").append(mIsVisibilityRestricted)
+                .append(", allowedPackages=").append(String.join(",", mAllowedPackages))
                 .append(" }");
         return result.toString();
     }
@@ -761,6 +783,8 @@
         dest.writeStringArray(mDeduplicationIds.toArray(new String[mDeduplicationIds.size()]));
         dest.writeBundle(mExtras);
         dest.writeString(mProviderId);
+        dest.writeBoolean(mIsVisibilityRestricted);
+        dest.writeString8Array(mAllowedPackages.toArray(new String[0]));
     }
 
     /**
@@ -787,6 +811,8 @@
         Set<String> mDeduplicationIds;
         Bundle mExtras;
         String mProviderId;
+        boolean mIsVisibilityRestricted;
+        Set<String> mAllowedPackages;
 
         /**
          * Constructor for builder to create {@link MediaRoute2Info}.
@@ -809,6 +835,7 @@
             mName = name;
             mFeatures = new ArrayList<>();
             mDeduplicationIds = Set.of();
+            mAllowedPackages = Set.of();
         }
 
         /**
@@ -854,6 +881,8 @@
                 mExtras = new Bundle(routeInfo.mExtras);
             }
             mProviderId = routeInfo.mProviderId;
+            mIsVisibilityRestricted = routeInfo.mIsVisibilityRestricted;
+            mAllowedPackages = routeInfo.mAllowedPackages;
         }
 
         /**
@@ -1057,6 +1086,29 @@
         }
 
         /**
+         * Sets the visibility of this route to public. This is the default
+         * visibility for routes that are public to all other apps.
+         */
+        @NonNull
+        public Builder setVisibilityPublic() {
+            mIsVisibilityRestricted = false;
+            mAllowedPackages = Set.of();
+            return this;
+        }
+
+        /**
+         * Sets the visibility of this route to restricted. This means that the
+         * route is only visible to a set of package name.
+         * @param allowedPackages set of package names which are allowed to see this route.
+         */
+        @NonNull
+        public Builder setVisibilityRestricted(@NonNull Set<String> allowedPackages) {
+            mIsVisibilityRestricted = true;
+            mAllowedPackages = Set.copyOf(allowedPackages);
+            return this;
+        }
+
+        /**
          * Builds the {@link MediaRoute2Info media route info}.
          *
          * @throws IllegalArgumentException if no features are added.
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index aea6bcb..3abfc629 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -247,7 +247,6 @@
         return getTransferableRoutes(sessions.get(sessions.size() - 1));
     }
 
-
     /**
      * Gets available routes for the given routing session.
      * The returned routes can be passed to
@@ -313,9 +312,15 @@
                 mDiscoveryPreferenceMap.getOrDefault(packageName, RouteDiscoveryPreference.EMPTY);
 
         for (MediaRoute2Info route : getSortedRoutes(discoveryPreference)) {
-            if (sessionInfo.getTransferableRoutes().contains(route.getId())
-                    || (includeSelectedRoutes
-                    && sessionInfo.getSelectedRoutes().contains(route.getId()))) {
+            if (!route.isVisibleTo(packageName)) {
+                continue;
+            }
+            boolean transferableRoutesContainRoute =
+                    sessionInfo.getTransferableRoutes().contains(route.getId());
+            boolean selectedRoutesContainRoute =
+                    sessionInfo.getSelectedRoutes().contains(route.getId());
+            if (transferableRoutesContainRoute
+                    || (includeSelectedRoutes && selectedRoutesContainRoute)) {
                 routes.add(route);
                 continue;
             }
diff --git a/media/java/android/media/RouteListingPreference.java b/media/java/android/media/RouteListingPreference.java
index b1d74d4..b03653c0 100644
--- a/media/java/android/media/RouteListingPreference.java
+++ b/media/java/android/media/RouteListingPreference.java
@@ -19,6 +19,9 @@
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Intent;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -40,6 +43,18 @@
  */
 public final class RouteListingPreference implements Parcelable {
 
+    /**
+     * {@link Intent} action for apps to take the user to a screen for transferring media playback
+     * to the route with the id provided by the extra with key {@link #EXTRA_ROUTE_ID}.
+     */
+    public static final String ACTION_TRANSFER_MEDIA = "android.media.action.TRANSFER_MEDIA";
+
+    /**
+     * {@link Intent} string extra key that contains the {@link Item#getRouteId() id} of the route
+     * to transfer to, as part of an {@link #ACTION_TRANSFER_MEDIA} intent.
+     */
+    public static final String EXTRA_ROUTE_ID = "android.media.extra.ROUTE_ID";
+
     @NonNull
     public static final Creator<RouteListingPreference> CREATOR =
             new Creator<>() {
@@ -56,10 +71,12 @@
 
     @NonNull private final List<Item> mItems;
     private final boolean mUseSystemOrdering;
+    @Nullable private final ComponentName mInAppOnlyItemRoutingReceiver;
 
     private RouteListingPreference(Builder builder) {
         mItems = builder.mItems;
         mUseSystemOrdering = builder.mUseSystemOrdering;
+        mInAppOnlyItemRoutingReceiver = builder.mInAppOnlyItemRoutingReceiver;
     }
 
     private RouteListingPreference(Parcel in) {
@@ -67,6 +84,7 @@
                 in.readParcelableList(new ArrayList<>(), Item.class.getClassLoader(), Item.class);
         mItems = List.copyOf(items);
         mUseSystemOrdering = in.readBoolean();
+        mInAppOnlyItemRoutingReceiver = ComponentName.readFromParcel(in);
     }
 
     /**
@@ -90,6 +108,21 @@
         return mUseSystemOrdering;
     }
 
+    /**
+     * Returns a {@link ComponentName} for handling routes disabled via {@link
+     * Item#DISABLE_REASON_IN_APP_ONLY}, or null if the user needs to manually navigate to the app
+     * in order to route to select the corresponding routes.
+     *
+     * <p>If the user selects an {@link Item} disabled via {@link Item#DISABLE_REASON_IN_APP_ONLY},
+     * and this method returns a non-null {@link ComponentName}, the system takes the user back to
+     * the app by launching an intent to the returned {@link ComponentName}, using action {@link
+     * #ACTION_TRANSFER_MEDIA}, with the extra {@link #EXTRA_ROUTE_ID}.
+     */
+    @Nullable
+    public ComponentName getInAppOnlyItemRoutingReceiver() {
+        return mInAppOnlyItemRoutingReceiver;
+    }
+
     // RouteListingPreference Parcelable implementation.
 
     @Override
@@ -101,6 +134,7 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeParcelableList(mItems, flags);
         dest.writeBoolean(mUseSystemOrdering);
+        ComponentName.writeToParcel(mInAppOnlyItemRoutingReceiver, dest);
     }
 
     // Equals and hashCode.
@@ -114,12 +148,15 @@
             return false;
         }
         RouteListingPreference that = (RouteListingPreference) other;
-        return mItems.equals(that.mItems) && mUseSystemOrdering == that.mUseSystemOrdering;
+        return mItems.equals(that.mItems)
+                && mUseSystemOrdering == that.mUseSystemOrdering
+                && Objects.equals(
+                        mInAppOnlyItemRoutingReceiver, that.mInAppOnlyItemRoutingReceiver);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mItems, mUseSystemOrdering);
+        return Objects.hash(mItems, mUseSystemOrdering, mInAppOnlyItemRoutingReceiver);
     }
 
     /** Builder for {@link RouteListingPreference}. */
@@ -127,6 +164,7 @@
 
         private List<Item> mItems;
         private boolean mUseSystemOrdering;
+        private ComponentName mInAppOnlyItemRoutingReceiver;
 
         /** Creates a new instance with default values (documented in the setters). */
         public Builder() {
@@ -159,6 +197,18 @@
         }
 
         /**
+         * See {@link #getInAppOnlyItemRoutingReceiver()}.
+         *
+         * <p>The default value is {@code null}.
+         */
+        @NonNull
+        public Builder setInAppOnlyItemRoutingReceiver(
+                @Nullable ComponentName inAppOnlyItemRoutingReceiver) {
+            mInAppOnlyItemRoutingReceiver = inAppOnlyItemRoutingReceiver;
+            return this;
+        }
+
+        /**
          * Creates and returns a new {@link RouteListingPreference} instance with the given
          * parameters.
          */
@@ -203,7 +253,8 @@
                     DISABLE_REASON_NONE,
                     DISABLE_REASON_SUBSCRIPTION_REQUIRED,
                     DISABLE_REASON_DOWNLOADED_CONTENT,
-                    DISABLE_REASON_AD
+                    DISABLE_REASON_AD,
+                    DISABLE_REASON_IN_APP_ONLY
                 })
         public @interface DisableReason {}
 
@@ -221,6 +272,14 @@
         public static final int DISABLE_REASON_DOWNLOADED_CONTENT = 2;
         /** The corresponding route is not available because an ad is in progress. */
         public static final int DISABLE_REASON_AD = 3;
+        /**
+         * The corresponding route is only available for routing from within the app.
+         *
+         * <p>The user may still select the corresponding route if the app provides an {@link
+         * #getInAppOnlyItemRoutingReceiver() in-app routing receiver}, in which case the system
+         * will take the user to the app.
+         */
+        public static final int DISABLE_REASON_IN_APP_ONLY = 4;
 
         @NonNull
         public static final Creator<Item> CREATOR =
@@ -257,7 +316,11 @@
             Preconditions.checkArgument(mSessionParticipantCount >= 0);
         }
 
-        /** Returns the id of the route that corresponds to this route listing preference item. */
+        /**
+         * Returns the id of the route that corresponds to this route listing preference item.
+         *
+         * @see MediaRoute2Info#getId()
+         */
         @NonNull
         public String getRouteId() {
             return mRouteId;
@@ -282,6 +345,7 @@
          * @see #DISABLE_REASON_SUBSCRIPTION_REQUIRED
          * @see #DISABLE_REASON_DOWNLOADED_CONTENT
          * @see #DISABLE_REASON_AD
+         * @see #DISABLE_REASON_IN_APP_ONLY
          */
         @DisableReason
         public int getDisableReason() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
index 3903404..cd6609e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
@@ -39,7 +39,7 @@
 
     BluetoothMediaDevice(Context context, CachedBluetoothDevice device,
             MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName) {
-        super(context, routerManager, info, packageName);
+        super(context, routerManager, info, packageName, null);
         mCachedDevice = device;
         mAudioManager = context.getSystemService(AudioManager.class);
         initDeviceRecord();
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
index 1b5ce8fe..6fb5555 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
@@ -25,6 +25,7 @@
 import android.graphics.drawable.Drawable;
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2Manager;
+import android.media.RouteListingPreference;
 
 import androidx.annotation.VisibleForTesting;
 
@@ -40,11 +41,16 @@
     private static final String TAG = "InfoMediaDevice";
 
     InfoMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
-            String packageName) {
-        super(context, routerManager, info, packageName);
+            String packageName, RouteListingPreference.Item item) {
+        super(context, routerManager, info, packageName, item);
         initDeviceRecord();
     }
 
+    InfoMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
+            String packageName) {
+        this(context, routerManager, info, packageName, null);
+    }
+
     @Override
     public String getName() {
         return mRouteInfo.getName().toString();
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index c7bfe12..2bdbb16 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -58,7 +58,9 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.stream.Collectors;
@@ -82,6 +84,8 @@
 
     private MediaDevice mCurrentConnectedDevice;
     private LocalBluetoothManager mBluetoothManager;
+    private final Map<String, RouteListingPreference.Item> mPreferenceItemMap =
+            new ConcurrentHashMap<>();
 
     public InfoMediaManager(Context context, String packageName, Notification notification,
             LocalBluetoothManager localBluetoothManager) {
@@ -248,7 +252,7 @@
         if (info != null) {
             for (MediaRoute2Info route : mRouterManager.getSelectableRoutes(info)) {
                 deviceList.add(new InfoMediaDevice(mContext, mRouterManager,
-                        route, mPackageName));
+                        route, mPackageName, mPreferenceItemMap.get(route.getId())));
             }
             return deviceList;
         }
@@ -275,7 +279,7 @@
         if (info != null) {
             for (MediaRoute2Info route : mRouterManager.getDeselectableRoutes(info)) {
                 deviceList.add(new InfoMediaDevice(mContext, mRouterManager,
-                        route, mPackageName));
+                        route, mPackageName, mPreferenceItemMap.get(route.getId())));
                 Log.d(TAG, route.getName() + " is deselectable for " + mPackageName);
             }
             return deviceList;
@@ -302,7 +306,7 @@
         if (info != null) {
             for (MediaRoute2Info route : mRouterManager.getSelectedRoutes(info)) {
                 deviceList.add(new InfoMediaDevice(mContext, mRouterManager,
-                        route, mPackageName));
+                        route, mPackageName, mPreferenceItemMap.get(route.getId())));
             }
             return deviceList;
         }
@@ -510,7 +514,7 @@
             case TYPE_GROUP:
                 //TODO(b/148765806): use correct device type once api is ready.
                 mediaDevice = new InfoMediaDevice(mContext, mRouterManager, route,
-                        mPackageName);
+                        mPackageName, mPreferenceItemMap.get(route.getId()));
                 if (!TextUtils.isEmpty(mPackageName)
                         && getRoutingSessionInfo().getSelectedRoutes().contains(route.getId())) {
                     mediaDevice.setState(STATE_SELECTED);
@@ -601,6 +605,16 @@
         public void onSessionUpdated(RoutingSessionInfo sessionInfo) {
             refreshDevices();
         }
+
+        @Override
+        public void onRouteListingPreferenceUpdated(
+                String packageName,
+                RouteListingPreference routeListingPreference) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+                Api34Impl.onRouteListingPreferenceUpdated(packageName, routeListingPreference,
+                        mPreferenceItemMap);
+            }
+        }
     }
 
     @RequiresApi(34)
@@ -666,5 +680,18 @@
             return routeListingPreference != null
                     && !routeListingPreference.getUseSystemOrdering();
         }
+
+        @DoNotInline
+        static void onRouteListingPreferenceUpdated(
+                String packageName,
+                RouteListingPreference routeListingPreference,
+                Map<String, RouteListingPreference.Item> preferenceItemMap) {
+            preferenceItemMap.clear();
+            if (routeListingPreference != null) {
+                routeListingPreference.getItems().forEach((item) -> {
+                    preferenceItemMap.put(item.getRouteId(), item);
+                });
+            }
+        }
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index 3ba51d2..b73e7a3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -30,6 +30,7 @@
 import static android.media.MediaRoute2Info.TYPE_USB_HEADSET;
 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
+import static android.media.RouteListingPreference.Item.FLAG_SUGGESTED_ROUTE;
 
 import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED;
 
@@ -39,10 +40,14 @@
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2Manager;
 import android.media.NearbyDevice;
+import android.media.RouteListingPreference;
+import android.os.Build;
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.DoNotInline;
 import androidx.annotation.IntDef;
+import androidx.annotation.RequiresApi;
 import androidx.annotation.VisibleForTesting;
 
 import java.lang.annotation.Retention;
@@ -87,14 +92,16 @@
     protected final Context mContext;
     protected final MediaRoute2Info mRouteInfo;
     protected final MediaRouter2Manager mRouterManager;
+    protected final RouteListingPreference.Item mItem;
     protected final String mPackageName;
 
     MediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
-            String packageName) {
+            String packageName, RouteListingPreference.Item item) {
         mContext = context;
         mRouteInfo = info;
         mRouterManager = routerManager;
         mPackageName = packageName;
+        mItem = item;
         setType(info);
     }
 
@@ -180,10 +187,21 @@
 
     /**
      * Get unique ID that represent MediaDevice
+     *
      * @return unique id of MediaDevice
      */
     public abstract String getId();
 
+    /**
+     * Checks if device is suggested device from application
+     *
+     * @return true if device is suggested device
+     */
+    public boolean isSuggestedDevice() {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+                && Api34Impl.isSuggestedDevice(mItem);
+    }
+
     void setConnectedRecord() {
         mConnectedRecord++;
         ConnectionRecordManager.getInstance().setConnectionRecord(mContext, getId(),
@@ -466,4 +484,12 @@
         final MediaDevice otherDevice = (MediaDevice) obj;
         return otherDevice.getId().equals(getId());
     }
+
+    @RequiresApi(34)
+    private static class Api34Impl {
+        @DoNotInline
+        static boolean isSuggestedDevice(RouteListingPreference.Item item) {
+            return item != null && item.getFlags() == FLAG_SUGGESTED_ROUTE;
+        }
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index 921c245..de16d4a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -51,7 +51,7 @@
 
     PhoneMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
             String packageName) {
-        super(context, routerManager, info, packageName);
+        super(context, routerManager, info, packageName, null);
         mDeviceIconUtil = new DeviceIconUtil();
         initDeviceRecord();
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 9260013..2c8aa26 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -257,14 +257,17 @@
                 Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
         final List<RouteListingPreference.Item> preferenceItemList = new ArrayList<>();
         RouteListingPreference.Item item1 = new RouteListingPreference.Item.Builder(
-                TEST_ID_4).build();
+                TEST_ID_4).setFlags(RouteListingPreference.Item.FLAG_SUGGESTED_ROUTE).build();
         RouteListingPreference.Item item2 = new RouteListingPreference.Item.Builder(
                 TEST_ID_3).build();
         preferenceItemList.add(item1);
         preferenceItemList.add(item2);
+
+        RouteListingPreference routeListingPreference =
+                new RouteListingPreference.Builder().setItems(
+                        preferenceItemList).setUseSystemOrdering(false).build();
         when(mRouterManager.getRouteListingPreference(TEST_PACKAGE_NAME))
-                .thenReturn(new RouteListingPreference.Builder().setItems(
-                        preferenceItemList).setUseSystemOrdering(false).build());
+                .thenReturn(routeListingPreference);
 
         final List<MediaRoute2Info> selectedRoutes = new ArrayList<>();
         final MediaRoute2Info info = mock(MediaRoute2Info.class);
@@ -284,11 +287,13 @@
         setTransferableRoutesList();
 
         mInfoMediaManager.mRouterManager = mRouterManager;
-
+        mInfoMediaManager.mMediaRouterCallback.onRouteListingPreferenceUpdated(TEST_PACKAGE_NAME,
+                routeListingPreference);
         mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
 
         assertThat(mInfoMediaManager.mMediaDevices.get(0).getId()).isEqualTo(TEST_ID);
         assertThat(mInfoMediaManager.mMediaDevices.get(1).getId()).isEqualTo(TEST_ID_4);
+        assertThat(mInfoMediaManager.mMediaDevices.get(1).isSuggestedDevice()).isTrue();
         assertThat(mInfoMediaManager.mMediaDevices.get(2).getId()).isEqualTo(TEST_ID_3);
         assertThat(mInfoMediaManager.mMediaDevices).hasSize(3);
     }
@@ -620,6 +625,7 @@
         mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
         mShadowRouter2Manager.setDeselectableRoutes(mediaRoute2Infos);
         when(mediaRoute2Info.getName()).thenReturn(TEST_NAME);
+        when(mediaRoute2Info.getId()).thenReturn(TEST_ID);
 
         final List<MediaDevice> mediaDevices = mInfoMediaManager.getDeselectableMediaDevice();
 
@@ -873,21 +879,25 @@
         final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
 
         when(route2Info.getType()).thenReturn(TYPE_REMOTE_SPEAKER);
+        when(route2Info.getId()).thenReturn(TEST_ID);
         mInfoMediaManager.addMediaDevice(route2Info);
         assertThat(mInfoMediaManager.mMediaDevices.get(0) instanceof InfoMediaDevice).isTrue();
 
         when(route2Info.getType()).thenReturn(TYPE_USB_DEVICE);
+        when(route2Info.getId()).thenReturn(TEST_ID);
         mInfoMediaManager.mMediaDevices.clear();
         mInfoMediaManager.addMediaDevice(route2Info);
         assertThat(mInfoMediaManager.mMediaDevices.get(0) instanceof PhoneMediaDevice).isTrue();
 
         when(route2Info.getType()).thenReturn(TYPE_WIRED_HEADSET);
+        when(route2Info.getId()).thenReturn(TEST_ID);
         mInfoMediaManager.mMediaDevices.clear();
         mInfoMediaManager.addMediaDevice(route2Info);
         assertThat(mInfoMediaManager.mMediaDevices.get(0) instanceof PhoneMediaDevice).isTrue();
 
         when(route2Info.getType()).thenReturn(TYPE_BLUETOOTH_A2DP);
         when(route2Info.getAddress()).thenReturn("00:00:00:00:00:00");
+        when(route2Info.getId()).thenReturn(TEST_ID);
         when(mLocalBluetoothManager.getCachedDeviceManager())
                 .thenReturn(cachedBluetoothDeviceManager);
         when(cachedBluetoothDeviceManager.findDevice(any(BluetoothDevice.class)))
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index 1fdbc99..d5558b2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -184,6 +184,7 @@
 
     private val configListener =
         object : ConfigurationController.ConfigurationListener {
+            var lastOrientation = -1
 
             override fun onDensityOrFontScaleChanged() {
                 // System font changes should only happen when UMO is offscreen or a flicker may
@@ -200,7 +201,13 @@
             override fun onConfigChanged(newConfig: Configuration?) {
                 if (newConfig == null) return
                 isRtl = newConfig.layoutDirection == View.LAYOUT_DIRECTION_RTL
-                updatePlayers(recreateMedia = true)
+                val newOrientation = newConfig.orientation
+                if (lastOrientation != newOrientation) {
+                    // The players actually depend on the orientation possibly, so we have to
+                    // recreate them (at least on large screen devices)
+                    lastOrientation = newOrientation
+                    updatePlayers(recreateMedia = true)
+                }
             }
 
             override fun onUiModeChanged() {
@@ -717,6 +724,9 @@
     private fun updatePlayers(recreateMedia: Boolean) {
         pageIndicator.tintList =
             ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator))
+        val previousVisibleKey =
+            MediaPlayerData.visiblePlayerKeys()
+                .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
 
         MediaPlayerData.mediaData().forEach { (key, data, isSsMediaRec) ->
             if (isSsMediaRec) {
@@ -741,6 +751,9 @@
                     isSsReactivated = isSsReactivated
                 )
             }
+            if (recreateMedia) {
+                reorderAllPlayers(previousVisibleKey)
+            }
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index 6ca34e0..039dd4d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.media.controls.ui
 
 import android.app.PendingIntent
+import android.content.res.Configuration
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
@@ -86,6 +87,9 @@
     @Mock lateinit var mediaViewController: MediaViewController
     @Mock lateinit var smartspaceMediaData: SmartspaceMediaData
     @Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
+    @Captor
+    lateinit var configListener: ArgumentCaptor<ConfigurationController.ConfigurationListener>
+    @Captor lateinit var newConfig: ArgumentCaptor<Configuration>
     @Captor lateinit var visualStabilityCallback: ArgumentCaptor<OnReorderingAllowedListener>
 
     private val clock = FakeSystemClock()
@@ -111,6 +115,7 @@
                 logger,
                 debugLogger
             )
+        verify(configurationController).addCallback(capture(configListener))
         verify(mediaDataManager).addListener(capture(listener))
         verify(visualStabilityProvider)
             .addPersistentReorderingAllowedListener(capture(visualStabilityCallback))
@@ -662,4 +667,39 @@
         mediaCarouselController.updatePageIndicatorAlpha()
         assertEquals(mediaCarouselController.pageIndicator.alpha, 1.0F, delta)
     }
+
+    @Ignore("b/253229241")
+    @Test
+    fun testOnConfigChanged_playersAreAddedBack() {
+        listener.value.onMediaDataLoaded(
+            "playing local",
+            null,
+            DATA.copy(
+                active = true,
+                isPlaying = true,
+                playbackLocation = MediaData.PLAYBACK_LOCAL,
+                resumption = false
+            )
+        )
+        listener.value.onMediaDataLoaded(
+            "paused local",
+            null,
+            DATA.copy(
+                active = true,
+                isPlaying = false,
+                playbackLocation = MediaData.PLAYBACK_LOCAL,
+                resumption = false
+            )
+        )
+
+        val playersSize = MediaPlayerData.players().size
+
+        configListener.value.onConfigChanged(capture(newConfig))
+
+        assertEquals(playersSize, MediaPlayerData.players().size)
+        assertEquals(
+            MediaPlayerData.getMediaPlayerIndex("playing local"),
+            mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
+        )
+    }
 }
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index cc485ba..cde4ea9 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -31,6 +31,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -285,6 +286,15 @@
     public void setRouteListingPreference(
             @NonNull IMediaRouter2 router,
             @Nullable RouteListingPreference routeListingPreference) {
+        ComponentName inAppOnlyItemRoutingReceiver =
+                routeListingPreference != null
+                        ? routeListingPreference.getInAppOnlyItemRoutingReceiver()
+                        : null;
+        if (inAppOnlyItemRoutingReceiver != null) {
+            MediaServerUtils.enforcePackageName(
+                    inAppOnlyItemRoutingReceiver.getPackageName(), Binder.getCallingUid());
+        }
+
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
@@ -787,6 +797,12 @@
                 obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManagers,
                         routerRecord.mUserRecord.mHandler,
                         routerRecord.mPackageName, null));
+        routerRecord.mUserRecord.mHandler.sendMessage(
+                obtainMessage(
+                        UserHandler::notifyRouteListingPreferenceChangeToManagers,
+                        routerRecord.mUserRecord.mHandler,
+                        routerRecord.mPackageName,
+                        /* routeListingPreference= */ null));
         userRecord.mHandler.sendMessage(
                 obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler,
                         userRecord.mHandler));
@@ -1626,7 +1642,7 @@
          * <p>This list contains all routes exposed by route providers. This includes routes from
          * both system route providers and user route providers.
          *
-         * <p>See {@link #getRouters(boolean hasModifyAudioRoutingPermission)}.
+         * <p>See {@link #getRouterRecords(boolean hasModifyAudioRoutingPermission)}.
          */
         private final Map<String, MediaRoute2Info> mLastNotifiedRoutesToPrivilegedRouters =
                 new ArrayMap<>();
@@ -1896,9 +1912,10 @@
             if (!hasAddedOrModifiedRoutes && !hasRemovedRoutes) {
                 return;
             }
-
-            List<IMediaRouter2> routersWithModifyAudioRoutingPermission = getRouters(true);
-            List<IMediaRouter2> routersWithoutModifyAudioRoutingPermission = getRouters(false);
+            List<RouterRecord> routerRecordsWithModifyAudioRoutingPermission =
+                    getRouterRecords(true);
+            List<RouterRecord> routerRecordsWithoutModifyAudioRoutingPermission =
+                    getRouterRecords(false);
             List<IMediaRouter2Manager> managers = getManagers();
 
             // Managers receive all provider updates with all routes.
@@ -1907,22 +1924,22 @@
 
             // Routers with modify audio permission (usually system routers) receive all provider
             // updates with all routes.
-            notifyRoutesUpdatedToRouters(
-                    routersWithModifyAudioRoutingPermission,
+            notifyRoutesUpdatedToRouterRecords(
+                    routerRecordsWithModifyAudioRoutingPermission,
                     new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
 
             if (!isSystemProvider) {
                 // Regular routers receive updates from all non-system providers with all non-system
                 // routes.
-                notifyRoutesUpdatedToRouters(
-                        routersWithoutModifyAudioRoutingPermission,
+                notifyRoutesUpdatedToRouterRecords(
+                        routerRecordsWithoutModifyAudioRoutingPermission,
                         new ArrayList<>(mLastNotifiedRoutesToNonPrivilegedRouters.values()));
             } else if (hasAddedOrModifiedRoutes) {
                 // On system provider updates, regular routers receive the updated default route.
                 // This is the only system route they should receive.
                 mLastNotifiedRoutesToNonPrivilegedRouters.put(defaultRoute.getId(), defaultRoute);
-                notifyRoutesUpdatedToRouters(
-                        routersWithoutModifyAudioRoutingPermission,
+                notifyRoutesUpdatedToRouterRecords(
+                        routerRecordsWithoutModifyAudioRoutingPermission,
                         new ArrayList<>(mLastNotifiedRoutesToNonPrivilegedRouters.values()));
             }
         }
@@ -2191,8 +2208,8 @@
                 if (mServiceRef.get() == null) {
                     return;
                 }
-                notifySessionInfoChangedToRouters(getRouters(true), sessionInfo);
-                notifySessionInfoChangedToRouters(getRouters(false),
+                notifySessionInfoChangedToRouters(getRouterRecords(true), sessionInfo);
+                notifySessionInfoChangedToRouters(getRouterRecords(false),
                         mSystemProvider.getDefaultSessionInfo());
                 return;
             }
@@ -2203,7 +2220,7 @@
                         + sessionInfo);
                 return;
             }
-            notifySessionInfoChangedToRouters(Arrays.asList(routerRecord.mRouter), sessionInfo);
+            notifySessionInfoChangedToRouters(Arrays.asList(routerRecord), sessionInfo);
         }
 
         private void onSessionReleasedOnHandler(@NonNull MediaRoute2Provider provider,
@@ -2316,6 +2333,7 @@
                             == routerRecord.mHasModifyAudioRoutingPermission) {
                         routers.add(routerRecord.mRouter);
                     }
+                    routers.add(routerRecord.mRouter);
                 }
             }
             return routers;
@@ -2345,6 +2363,23 @@
             }
         }
 
+        private List<RouterRecord> getRouterRecords(boolean hasModifyAudioRoutingPermission) {
+            MediaRouter2ServiceImpl service = mServiceRef.get();
+            List<RouterRecord> routerRecords = new ArrayList<>();
+            if (service == null) {
+                return routerRecords;
+            }
+            synchronized (service.mLock) {
+                for (RouterRecord routerRecord : mUserRecord.mRouterRecords) {
+                    if (hasModifyAudioRoutingPermission
+                            == routerRecord.mHasModifyAudioRoutingPermission) {
+                        routerRecords.add(routerRecord);
+                    }
+                }
+                return routerRecords;
+            }
+        }
+
         private List<ManagerRecord> getManagerRecords() {
             MediaRouter2ServiceImpl service = mServiceRef.get();
             if (service == null) {
@@ -2395,22 +2430,45 @@
             }
         }
 
-        private void notifyRoutesUpdatedToRouters(
-                @NonNull List<IMediaRouter2> routers, @NonNull List<MediaRoute2Info> routes) {
-            for (IMediaRouter2 router : routers) {
+        private static void notifyRoutesUpdatedToRouterRecords(
+                @NonNull List<RouterRecord> routerRecords,
+                @NonNull List<MediaRoute2Info> routes) {
+            for (RouterRecord routerRecord: routerRecords) {
+                List<MediaRoute2Info> filteredRoutes = getFilteredRoutesForPackageName(routes,
+                        routerRecord.mPackageName);
                 try {
-                    router.notifyRoutesUpdated(routes);
+                    routerRecord.mRouter.notifyRoutesUpdated(filteredRoutes);
                 } catch (RemoteException ex) {
                     Slog.w(TAG, "Failed to notify routes updated. Router probably died.", ex);
                 }
             }
         }
 
+        /**
+         * Filters list of routes to return only public routes or routes provided by
+         * the same package name or routes containing this package name in its allow list.
+         * @param routes initial list of routes to be filtered.
+         * @param packageName router's package name to filter routes for it.
+         * @return only the routes that this package name is allowed to see.
+         */
+        private static List<MediaRoute2Info> getFilteredRoutesForPackageName(
+                @NonNull List<MediaRoute2Info> routes,
+                @NonNull String packageName) {
+            List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
+            for (MediaRoute2Info route : routes) {
+                if (route.isVisibleTo(packageName)) {
+                    filteredRoutes.add(route);
+                }
+            }
+            return filteredRoutes;
+        }
+
         private void notifySessionInfoChangedToRouters(
-                @NonNull List<IMediaRouter2> routers, @NonNull RoutingSessionInfo sessionInfo) {
-            for (IMediaRouter2 router : routers) {
+                @NonNull List<RouterRecord> routerRecords,
+                @NonNull RoutingSessionInfo sessionInfo) {
+            for (RouterRecord routerRecord : routerRecords) {
                 try {
-                    router.notifySessionInfoChanged(sessionInfo);
+                    routerRecord.mRouter.notifySessionInfoChanged(sessionInfo);
                 } catch (RemoteException ex) {
                     Slog.w(TAG, "Failed to notify session info changed. Router probably died.", ex);
                 }
diff --git a/services/core/java/com/android/server/media/MediaServerUtils.java b/services/core/java/com/android/server/media/MediaServerUtils.java
index 5fa2b1c..a4a99af 100644
--- a/services/core/java/com/android/server/media/MediaServerUtils.java
+++ b/services/core/java/com/android/server/media/MediaServerUtils.java
@@ -18,7 +18,13 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.os.Binder;
+import android.os.Process;
+import android.os.UserHandle;
+import android.text.TextUtils;
+
+import com.android.server.LocalServices;
 
 import java.io.PrintWriter;
 
@@ -26,6 +32,39 @@
  * Util class for media server.
  */
 class MediaServerUtils {
+
+    /**
+     * Throws if the given {@code packageName} does not correspond to the given {@code uid}.
+     *
+     * <p>This method trusts calls from {@link Process#ROOT_UID} and {@link Process#SHELL_UID}.
+     *
+     * @param packageName A package name to verify (usually sent over binder by an app).
+     * @param uid The calling uid, obtained via {@link Binder#getCallingUid()}.
+     * @throws IllegalArgumentException If the given {@code packageName} does not correspond to the
+     *     given {@code uid}, and {@code uid} is not the root uid, or the shell uid.
+     */
+    public static void enforcePackageName(String packageName, int uid) {
+        if (uid == Process.ROOT_UID || uid == Process.SHELL_UID) {
+            return;
+        }
+        if (TextUtils.isEmpty(packageName)) {
+            throw new IllegalArgumentException("packageName may not be empty");
+        }
+        final PackageManagerInternal packageManagerInternal =
+                LocalServices.getService(PackageManagerInternal.class);
+        final int actualUid =
+                packageManagerInternal.getPackageUid(
+                        packageName, 0 /* flags */, UserHandle.getUserId(uid));
+        if (!UserHandle.isSameApp(uid, actualUid)) {
+            throw new IllegalArgumentException(
+                    "packageName does not belong to the calling uid; "
+                            + "pkg="
+                            + packageName
+                            + ", uid="
+                            + uid);
+        }
+    }
+
     /**
      * Verify that caller holds {@link android.Manifest.permission#DUMP}.
      */
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index e51ed1b..3a20cd9 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -538,30 +538,11 @@
         mHandler.postSessionsChanged(session);
     }
 
-    private void enforcePackageName(String packageName, int uid) {
-        if (TextUtils.isEmpty(packageName)) {
-            throw new IllegalArgumentException("packageName may not be empty");
-        }
-        if (uid == Process.ROOT_UID || uid == Process.SHELL_UID) {
-            // If the caller is shell, then trust the packageName given and allow it
-            // to proceed.
-            return;
-        }
-        final PackageManagerInternal packageManagerInternal =
-                LocalServices.getService(PackageManagerInternal.class);
-        final int actualUid = packageManagerInternal.getPackageUid(
-                packageName, 0 /* flags */, UserHandle.getUserId(uid));
-        if (!UserHandle.isSameApp(uid, actualUid)) {
-            throw new IllegalArgumentException("packageName does not belong to the calling uid; "
-                    + "pkg=" + packageName + ", uid=" + uid);
-        }
-    }
-
     void tempAllowlistTargetPkgIfPossible(int targetUid, String targetPackage,
             int callingPid, int callingUid, String callingPackage, String reason) {
         final long token = Binder.clearCallingIdentity();
         try {
-            enforcePackageName(callingPackage, callingUid);
+            MediaServerUtils.enforcePackageName(callingPackage, callingUid);
             if (targetUid != callingUid) {
                 boolean canAllowWhileInUse = mActivityManagerLocal
                         .canAllowWhileInUsePermissionInFgs(callingPid, callingUid, callingPackage);
@@ -1206,7 +1187,7 @@
             final int uid = Binder.getCallingUid();
             final long token = Binder.clearCallingIdentity();
             try {
-                enforcePackageName(packageName, uid);
+                MediaServerUtils.enforcePackageName(packageName, uid);
                 int resolvedUserId = handleIncomingUser(pid, uid, userId, packageName);
                 if (cb == null) {
                     throw new IllegalArgumentException("Controller callback cannot be null");
@@ -1258,7 +1239,7 @@
             final int userId = userHandle.getIdentifier();
             final long token = Binder.clearCallingIdentity();
             try {
-                enforcePackageName(packageName, uid);
+                MediaServerUtils.enforcePackageName(packageName, uid);
                 enforceMediaPermissions(packageName, pid, uid, userId);
 
                 MediaSessionRecordImpl record;
@@ -1289,7 +1270,7 @@
             final int userId = userHandle.getIdentifier();
             final long token = Binder.clearCallingIdentity();
             try {
-                enforcePackageName(packageName, uid);
+                MediaServerUtils.enforcePackageName(packageName, uid);
                 enforceMediaPermissions(packageName, pid, uid, userId);
 
                 MediaSessionRecordImpl record;
@@ -1615,7 +1596,7 @@
             final int userId = userHandle.getIdentifier();
             final long token = Binder.clearCallingIdentity();
             try {
-                enforcePackageName(packageName, uid);
+                MediaServerUtils.enforcePackageName(packageName, uid);
                 enforceMediaPermissions(packageName, pid, uid, userId);
 
                 synchronized (mLock) {
@@ -2129,7 +2110,7 @@
                 // If they gave us a component name verify they own the
                 // package
                 packageName = componentName.getPackageName();
-                enforcePackageName(packageName, uid);
+                MediaServerUtils.enforcePackageName(packageName, uid);
             }
             // Check that they can make calls on behalf of the user and get the final user id
             int resolvedUserId = handleIncomingUser(pid, uid, userId, packageName);
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index ed31187..abaff4a 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -18,6 +18,7 @@
 
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 
+import static com.android.server.pm.DexOptHelper.useArtService;
 import static com.android.server.pm.PackageManagerService.TAG;
 import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
 
@@ -45,6 +46,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
 import com.android.server.SystemServerInitThreadPool;
+import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.dex.ArtManagerService;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.pkg.AndroidPackage;
@@ -242,33 +244,39 @@
                 }
             }
 
-            // Prepare the application profiles only for upgrades and
-            // first boot (so that we don't repeat the same operation at
-            // each boot).
-            //
-            // We only have to cover the upgrade and first boot here
-            // because for app installs we prepare the profiles before
-            // invoking dexopt (in installPackageLI).
-            //
-            // We also have to cover non system users because we do not
-            // call the usual install package methods for them.
-            //
-            // NOTE: in order to speed up first boot time we only create
-            // the current profile and do not update the content of the
-            // reference profile. A system image should already be
-            // configured with the right profile keys and the profiles
-            // for the speed-profile prebuilds should already be copied.
-            // That's done in #performDexOptUpgrade.
-            //
-            // TODO(calin, mathieuc): We should use .dm files for
-            // prebuilds profiles instead of manually copying them in
-            // #performDexOptUpgrade. When we do that we should have a
-            // more granular check here and only update the existing
-            // profiles.
-            if (mPm.isDeviceUpgrading() || mPm.isFirstBoot()
-                    || (userId != UserHandle.USER_SYSTEM)) {
-                mArtManagerService.prepareAppProfiles(pkg, userId,
-                        /* updateReferenceProfileContent= */ false);
+            if (!useArtService()) { // ART Service handles this on demand instead.
+                // Prepare the application profiles only for upgrades and
+                // first boot (so that we don't repeat the same operation at
+                // each boot).
+                //
+                // We only have to cover the upgrade and first boot here
+                // because for app installs we prepare the profiles before
+                // invoking dexopt (in installPackageLI).
+                //
+                // We also have to cover non system users because we do not
+                // call the usual install package methods for them.
+                //
+                // NOTE: in order to speed up first boot time we only create
+                // the current profile and do not update the content of the
+                // reference profile. A system image should already be
+                // configured with the right profile keys and the profiles
+                // for the speed-profile prebuilds should already be copied.
+                // That's done in #performDexOptUpgrade.
+                //
+                // TODO(calin, mathieuc): We should use .dm files for
+                // prebuilds profiles instead of manually copying them in
+                // #performDexOptUpgrade. When we do that we should have a
+                // more granular check here and only update the existing
+                // profiles.
+                if (mPm.isDeviceUpgrading() || mPm.isFirstBoot()
+                        || (userId != UserHandle.USER_SYSTEM)) {
+                    try {
+                        mArtManagerService.prepareAppProfiles(pkg, userId,
+                                /* updateReferenceProfileContent= */ false);
+                    } catch (LegacyDexoptDisabledException e2) {
+                        throw new RuntimeException(e2);
+                    }
+                }
             }
 
             if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && ceDataInode != -1) {
@@ -578,7 +586,12 @@
             Slog.wtf(TAG, "Package was null!", new Throwable());
             return;
         }
-        mArtManagerService.clearAppProfiles(pkg);
+        // TODO(b/251903639): Call into ART Service.
+        try {
+            mArtManagerService.clearAppProfiles(pkg);
+        } catch (LegacyDexoptDisabledException e) {
+            throw new RuntimeException(e);
+        }
     }
 
     public void destroyAppDataLIF(AndroidPackage pkg, int userId, int flags) {
@@ -615,8 +628,11 @@
     }
 
     private void destroyAppProfilesLeafLIF(AndroidPackage pkg) {
+        // TODO(b/251903639): Call into ART Service.
         try {
             mInstaller.destroyAppProfiles(pkg.getPackageName());
+        } catch (LegacyDexoptDisabledException e) {
+            throw new RuntimeException(e);
         } catch (Installer.InstallerException e) {
             Slog.w(TAG, String.valueOf(e));
         }
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index fe122f8..9785d47 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -55,9 +55,11 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.FunctionalUtils.ThrowingCheckedSupplier;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
 import com.android.server.PinnerService;
+import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.PackageDexOptimizer.DexOptResult;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DexoptOptions;
@@ -71,7 +73,6 @@
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
-import java.util.function.Supplier;
 
 /**
  * Controls background dex optimization run as idle job or command line.
@@ -202,7 +203,8 @@
     }
 
     /** Start scheduling job after boot completion */
-    public void systemReady() {
+    public void systemReady() throws LegacyDexoptDisabledException {
+        Installer.checkLegacyDexoptDisabled();
         if (mInjector.isBackgroundDexOptDisabled()) {
             return;
         }
@@ -272,7 +274,8 @@
      * @return true if dex optimization is complete. false if the task is cancelled or if there was
      *         an error.
      */
-    public boolean runBackgroundDexoptJob(@Nullable List<String> packageNames) {
+    public boolean runBackgroundDexoptJob(@Nullable List<String> packageNames)
+            throws LegacyDexoptDisabledException {
         enforceRootOrShell();
         long identity = Binder.clearCallingIdentity();
         try {
@@ -301,7 +304,8 @@
      *
      * <p>This is only for shell command and only root or shell user can use this.
      */
-    public void cancelBackgroundDexoptJob() {
+    public void cancelBackgroundDexoptJob() throws LegacyDexoptDisabledException {
+        Installer.checkLegacyDexoptDisabled();
         enforceRootOrShell();
         Binder.withCleanCallingIdentity(() -> cancelDexOptAndWaitForCompletion());
     }
@@ -315,7 +319,8 @@
      *
      * @param disable True if JobScheduler invocations should be disabled, false otherwise.
      */
-    public void setDisableJobSchedulerJobs(boolean disable) {
+    public void setDisableJobSchedulerJobs(boolean disable) throws LegacyDexoptDisabledException {
+        Installer.checkLegacyDexoptDisabled();
         enforceRootOrShell();
         synchronized (mLock) {
             mDisableJobSchedulerJobs = disable;
@@ -323,14 +328,18 @@
     }
 
     /** Adds listener for package update */
-    public void addPackagesUpdatedListener(PackagesUpdatedListener listener) {
+    public void addPackagesUpdatedListener(PackagesUpdatedListener listener)
+            throws LegacyDexoptDisabledException {
+        Installer.checkLegacyDexoptDisabled();
         synchronized (mLock) {
             mPackagesUpdatedListeners.add(listener);
         }
     }
 
     /** Removes package update listener */
-    public void removePackagesUpdatedListener(PackagesUpdatedListener listener) {
+    public void removePackagesUpdatedListener(PackagesUpdatedListener listener)
+            throws LegacyDexoptDisabledException {
+        Installer.checkLegacyDexoptDisabled();
         synchronized (mLock) {
             mPackagesUpdatedListeners.remove(listener);
         }
@@ -340,7 +349,8 @@
      * Notifies package change and removes the package from the failed package list so that
      * the package can run dexopt again.
      */
-    public void notifyPackageChanged(String packageName) {
+    public void notifyPackageChanged(String packageName) throws LegacyDexoptDisabledException {
+        Installer.checkLegacyDexoptDisabled();
         // The idle maintenance job skips packages which previously failed to
         // compile. The given package has changed and may successfully compile
         // now. Remove it from the list of known failing packages.
@@ -387,37 +397,43 @@
                 // Post boot job not finished yet. Run post boot job first.
                 return false;
             }
-            resetStatesForNewDexOptRunLocked(mInjector.createAndStartThread(
-                    "BackgroundDexOptService_" + (isPostBootUpdateJob ? "PostBoot" : "Idle"),
-                    () -> {
-                        TimingsTraceAndSlog tr =
-                                new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_PACKAGE_MANAGER);
-                        tr.traceBegin("jobExecution");
-                        boolean completed = false;
-                        try {
-                            completed = runIdleOptimization(
-                                    pm, pkgs, params.getJobId() == JOB_POST_BOOT_UPDATE);
-                        } finally { // Those cleanup should be done always.
-                            tr.traceEnd();
-                            Slog.i(TAG,
-                                    "dexopt finishing. jobid:" + params.getJobId()
-                                            + " completed:" + completed);
+            try {
+                resetStatesForNewDexOptRunLocked(mInjector.createAndStartThread(
+                        "BackgroundDexOptService_" + (isPostBootUpdateJob ? "PostBoot" : "Idle"),
+                        () -> {
+                            TimingsTraceAndSlog tr =
+                                    new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_PACKAGE_MANAGER);
+                            tr.traceBegin("jobExecution");
+                            boolean completed = false;
+                            try {
+                                completed = runIdleOptimization(
+                                        pm, pkgs, params.getJobId() == JOB_POST_BOOT_UPDATE);
+                            } catch (LegacyDexoptDisabledException e) {
+                                Slog.wtf(TAG, e);
+                            } finally { // Those cleanup should be done always.
+                                tr.traceEnd();
+                                Slog.i(TAG,
+                                        "dexopt finishing. jobid:" + params.getJobId()
+                                                + " completed:" + completed);
 
-                            writeStatsLog(params);
+                                writeStatsLog(params);
 
-                            if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
-                                if (completed) {
-                                    markPostBootUpdateCompleted(params);
+                                if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
+                                    if (completed) {
+                                        markPostBootUpdateCompleted(params);
+                                    }
+                                    // Reschedule when cancelled
+                                    job.jobFinished(params, !completed);
+                                } else {
+                                    // Periodic job
+                                    job.jobFinished(params, false /* reschedule */);
                                 }
-                                // Reschedule when cancelled
-                                job.jobFinished(params, !completed);
-                            } else {
-                                // Periodic job
-                                job.jobFinished(params, false /* reschedule */);
+                                markDexOptCompleted();
                             }
-                            markDexOptCompleted();
-                        }
-                    }));
+                        }));
+            } catch (LegacyDexoptDisabledException e) {
+                Slog.wtf(TAG, e);
+            }
         }
         return true;
     }
@@ -425,11 +441,16 @@
     /** For BackgroundDexOptJobService to dispatch onStopJob event */
     /* package */ boolean onStopJob(BackgroundDexOptJobService job, JobParameters params) {
         Slog.i(TAG, "onStopJob:" + params.getJobId());
-        // This cannot block as it is in main thread, thus dispatch to a newly created thread and
-        // cancel it from there.
-        // As this event does not happen often, creating a new thread is justified rather than
-        // having one thread kept permanently.
-        mInjector.createAndStartThread("DexOptCancel", this::cancelDexOptAndWaitForCompletion);
+        // This cannot block as it is in main thread, thus dispatch to a newly created thread
+        // and cancel it from there. As this event does not happen often, creating a new thread
+        // is justified rather than having one thread kept permanently.
+        mInjector.createAndStartThread("DexOptCancel", () -> {
+            try {
+                cancelDexOptAndWaitForCompletion();
+            } catch (LegacyDexoptDisabledException e) {
+                Slog.wtf(TAG, e);
+            }
+        });
         // Always reschedule for cancellation.
         return true;
     }
@@ -438,7 +459,7 @@
      * Cancels pending dexopt and wait for completion of the cancellation. This can block the caller
      * until cancellation is done.
      */
-    private void cancelDexOptAndWaitForCompletion() {
+    private void cancelDexOptAndWaitForCompletion() throws LegacyDexoptDisabledException {
         synchronized (mLock) {
             if (mDexOptThread == null) {
                 return;
@@ -496,7 +517,8 @@
     }
 
     @GuardedBy("mLock")
-    private void resetStatesForNewDexOptRunLocked(Thread thread) {
+    private void resetStatesForNewDexOptRunLocked(Thread thread)
+            throws LegacyDexoptDisabledException {
         mDexOptThread = thread;
         mLastCancelledPackages.clear();
         controlDexOptBlockingLocked(false);
@@ -510,7 +532,7 @@
     }
 
     @GuardedBy("mLock")
-    private void controlDexOptBlockingLocked(boolean block) {
+    private void controlDexOptBlockingLocked(boolean block) throws LegacyDexoptDisabledException {
         PackageManagerService pm = mInjector.getPackageManagerService();
         mDexOptHelper.controlDexOptBlocking(block);
     }
@@ -564,8 +586,8 @@
      * Returns whether we've successfully run the job. Note that it will return true even if some
      * packages may have failed compiling.
      */
-    private boolean runIdleOptimization(
-            PackageManagerService pm, List<String> pkgs, boolean isPostBootUpdate) {
+    private boolean runIdleOptimization(PackageManagerService pm, List<String> pkgs,
+            boolean isPostBootUpdate) throws LegacyDexoptDisabledException {
         synchronized (mLock) {
             mLastExecutionStatus = STATUS_UNSPECIFIED;
             mLastExecutionStartUptimeMs = SystemClock.uptimeMillis();
@@ -631,7 +653,8 @@
 
     @Status
     private int idleOptimizePackages(PackageManagerService pm, List<String> pkgs,
-            long lowStorageThreshold, boolean isPostBootUpdate) {
+            long lowStorageThreshold, boolean isPostBootUpdate)
+            throws LegacyDexoptDisabledException {
         ArraySet<String> updatedPackages = new ArraySet<>();
 
         try {
@@ -707,7 +730,8 @@
 
     @Status
     private int optimizePackages(List<String> pkgs, long lowStorageThreshold,
-            ArraySet<String> updatedPackages, boolean isPostBootUpdate) {
+            ArraySet<String> updatedPackages, boolean isPostBootUpdate)
+            throws LegacyDexoptDisabledException {
         boolean supportSecondaryDex = mInjector.supportSecondaryDex();
 
         // Keep the error if there is any error from any package.
@@ -760,7 +784,8 @@
      */
     @DexOptResult
     private int downgradePackage(@NonNull Computer snapshot, PackageManagerService pm, String pkg,
-            boolean isForPrimaryDex, boolean isPostBootUpdate) {
+            boolean isForPrimaryDex, boolean isPostBootUpdate)
+            throws LegacyDexoptDisabledException {
         if (DEBUG) {
             Slog.d(TAG, "Downgrading " + pkg);
         }
@@ -808,7 +833,7 @@
     }
 
     @Status
-    private int reconcileSecondaryDexFiles() {
+    private int reconcileSecondaryDexFiles() throws LegacyDexoptDisabledException {
         // TODO(calin): should we denylist packages for which we fail to reconcile?
         for (String p : mInjector.getDexManager().getAllPackagesWithSecondaryDexFiles()) {
             if (isCancelling()) {
@@ -829,7 +854,8 @@
      * @return PackageDexOptimizer#DEX_OPT_*
      */
     @DexOptResult
-    private int optimizePackage(String pkg, boolean isForPrimaryDex, boolean isPostBootUpdate) {
+    private int optimizePackage(String pkg, boolean isForPrimaryDex, boolean isPostBootUpdate)
+            throws LegacyDexoptDisabledException {
         int reason = isPostBootUpdate ? PackageManagerService.REASON_POST_BOOT
                                       : PackageManagerService.REASON_BACKGROUND_DEXOPT;
         String filter = getCompilerFilterForReason(reason);
@@ -857,7 +883,8 @@
     }
 
     @DexOptResult
-    private int performDexOptPrimary(String pkg, int reason, String filter, int dexoptFlags) {
+    private int performDexOptPrimary(String pkg, int reason, String filter, int dexoptFlags)
+            throws LegacyDexoptDisabledException {
         DexoptOptions dexoptOptions =
                 new DexoptOptions(pkg, reason, filter, /*splitName=*/null, dexoptFlags);
         return trackPerformDexOpt(pkg, /*isForPrimaryDex=*/true,
@@ -865,7 +892,8 @@
     }
 
     @DexOptResult
-    private int performDexOptSecondary(String pkg, int reason, String filter, int dexoptFlags) {
+    private int performDexOptSecondary(String pkg, int reason, String filter, int dexoptFlags)
+            throws LegacyDexoptDisabledException {
         DexoptOptions dexoptOptions = new DexoptOptions(pkg, reason, filter, /*splitName=*/null,
                 dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX);
         return trackPerformDexOpt(pkg, /*isForPrimaryDex=*/false,
@@ -885,8 +913,9 @@
      *  {@link PackageDexOptimizer#DEX_OPT_FAILED}
      */
     @DexOptResult
-    private int trackPerformDexOpt(
-            String pkg, boolean isForPrimaryDex, Supplier<Integer> performDexOptWrapper) {
+    private int trackPerformDexOpt(String pkg, boolean isForPrimaryDex,
+            ThrowingCheckedSupplier<Integer, LegacyDexoptDisabledException> performDexOptWrapper)
+            throws LegacyDexoptDisabledException {
         ArraySet<String> failedPackageNames;
         synchronized (mLock) {
             failedPackageNames =
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index f433d6ad..d321b09 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -124,6 +124,7 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.PackageDexUsage;
 import com.android.server.pm.parsing.PackageInfoUtils;
@@ -3009,8 +3010,13 @@
                     ipw.println("[" + pkgName + "]");
                     ipw.increaseIndent();
 
-                    mPackageDexOptimizer.dumpDexoptState(ipw, pkg, pkgSetting,
-                            mDexManager.getPackageUseInfoOrDefault(pkgName));
+                    // TODO(b/251903639): Call into ART Service.
+                    try {
+                        mPackageDexOptimizer.dumpDexoptState(ipw, pkg, pkgSetting,
+                                mDexManager.getPackageUseInfoOrDefault(pkgName));
+                    } catch (LegacyDexoptDisabledException e) {
+                        throw new RuntimeException(e);
+                    }
                     ipw.decreaseIndent();
                 }
                 ipw.println("BgDexopt state:");
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 69aaa0d..39aa9c1 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -66,6 +66,8 @@
 import com.android.server.art.model.ArtFlags;
 import com.android.server.art.model.OptimizeParams;
 import com.android.server.art.model.OptimizeResult;
+import com.android.server.pm.Installer.InstallerException;
+import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.PackageDexOptimizer.DexOptResult;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DexoptOptions;
@@ -125,8 +127,9 @@
      * and {@code numberOfPackagesFailed}.
      */
     public int[] performDexOptUpgrade(List<PackageStateInternal> packageStates, boolean showDialog,
-            final int compilationReason, boolean bootComplete) {
-
+            final int compilationReason, boolean bootComplete)
+            throws LegacyDexoptDisabledException {
+        Installer.checkLegacyDexoptDisabled();
         int numberOfPackagesVisited = 0;
         int numberOfPackagesOptimized = 0;
         int numberOfPackagesSkipped = 0;
@@ -158,7 +161,7 @@
                             // even if things are already compiled.
                             // useProfileForDexopt = true;
                         }
-                    } catch (Exception e) {
+                    } catch (InstallerException | RuntimeException e) {
                         Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath() + " ",
                                 e);
                     }
@@ -193,7 +196,7 @@
                                 } else {
                                     useProfileForDexopt = true;
                                 }
-                            } catch (Exception e) {
+                            } catch (InstallerException | RuntimeException e) {
                                 Log.e(TAG, "Failed to copy profile "
                                         + profileFile.getAbsolutePath() + " ", e);
                             }
@@ -284,7 +287,8 @@
      * Checks if system UI package (typically "com.android.systemui") needs to be re-compiled, and
      * compiles it if needed.
      */
-    private void checkAndDexOptSystemUi() {
+    private void checkAndDexOptSystemUi() throws LegacyDexoptDisabledException {
+        Installer.checkLegacyDexoptDisabled();
         Computer snapshot = mPm.snapshotComputer();
         String sysUiPackageName =
                 mPm.mContext.getString(com.android.internal.R.string.config_systemUi);
@@ -320,7 +324,7 @@
                             Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath());
                         }
                     }
-                } catch (Exception e) {
+                } catch (InstallerException | RuntimeException e) {
                     Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath(), e);
                 }
             }
@@ -341,7 +345,7 @@
     }
 
     @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
-    public void performPackageDexOptUpgradeIfNeeded() {
+    public void performPackageDexOptUpgradeIfNeeded() throws LegacyDexoptDisabledException {
         PackageManagerServiceUtils.enforceSystemOrRoot(
                 "Only the system can request package update");
 
@@ -413,7 +417,12 @@
         }
 
         if (options.isDexoptOnlySecondaryDex()) {
-            return mPm.getDexManager().dexoptSecondaryDex(options);
+            // TODO(b/251903639): Call into ART Service.
+            try {
+                return mPm.getDexManager().dexoptSecondaryDex(options);
+            } catch (LegacyDexoptDisabledException e) {
+                throw new RuntimeException(e);
+            }
         } else {
             int dexoptStatus = performDexOptWithStatus(options);
             return dexoptStatus != PackageDexOptimizer.DEX_OPT_FAILED;
@@ -470,6 +479,8 @@
         final long callingId = Binder.clearCallingIdentity();
         try {
             return performDexOptInternalWithDependenciesLI(p, pkgSetting, options);
+        } catch (LegacyDexoptDisabledException e) {
+            throw new RuntimeException(e);
         } finally {
             Binder.restoreCallingIdentity(callingId);
         }
@@ -542,7 +553,8 @@
 
     @DexOptResult
     private int performDexOptInternalWithDependenciesLI(
-            AndroidPackage p, @NonNull PackageStateInternal pkgSetting, DexoptOptions options) {
+            AndroidPackage p, @NonNull PackageStateInternal pkgSetting, DexoptOptions options)
+            throws LegacyDexoptDisabledException {
         // System server gets a special path.
         if (PLATFORM_PACKAGE_NAME.equals(p.getPackageName())) {
             return mPm.getDexManager().dexoptSystemServer(options);
@@ -621,7 +633,11 @@
         if (artSrvRes.isPresent()) {
             res = artSrvRes.get();
         } else {
-            res = performDexOptInternalWithDependenciesLI(pkg, packageState, options);
+            try {
+                res = performDexOptInternalWithDependenciesLI(pkg, packageState, options);
+            } catch (LegacyDexoptDisabledException e) {
+                throw new RuntimeException(e);
+            }
         }
 
         Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
@@ -666,8 +682,8 @@
         return installerPkg.getUid() == Binder.getCallingUid();
     }
 
-    public boolean performDexOptSecondary(String packageName, String compilerFilter,
-            boolean force) {
+    public boolean performDexOptSecondary(
+            String packageName, String compilerFilter, boolean force) {
         int flags = DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX
                 | DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
                 | DexoptOptions.DEXOPT_BOOT_COMPLETE
@@ -869,7 +885,7 @@
         }
     }
 
-    /*package*/ void controlDexOptBlocking(boolean block) {
+    /*package*/ void controlDexOptBlocking(boolean block) throws LegacyDexoptDisabledException {
         mPm.mPackageDexOptimizer.controlDexOptBlocking(block);
     }
 
@@ -929,10 +945,17 @@
     }
 
     /**
+     * Returns true if ART Service should be used for package optimization.
+     */
+    public static boolean useArtService() {
+        return SystemProperties.getBoolean("dalvik.vm.useartservice", false);
+    }
+
+    /**
      * Returns {@link DexUseManagerLocal} if ART Service should be used for package optimization.
      */
     public static @Nullable DexUseManagerLocal getDexUseManagerLocal() {
-        if (!"true".equals(SystemProperties.get("dalvik.vm.useartservice", ""))) {
+        if (!useArtService()) {
             return null;
         }
         try {
@@ -946,7 +969,7 @@
      * Returns {@link ArtManagerLocal} if ART Service should be used for package optimization.
      */
     private static @Nullable ArtManagerLocal getArtManagerLocal() {
-        if (!"true".equals(SystemProperties.get("dalvik.vm.useartservice", ""))) {
+        if (!useArtService()) {
             return null;
         }
         try {
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 16bf0fe..6f7484e 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -46,6 +46,7 @@
 import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
 import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
 
+import static com.android.server.pm.DexOptHelper.useArtService;
 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet;
 import static com.android.server.pm.InstructionSets.getPreferredInstructionSet;
@@ -157,6 +158,7 @@
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.EventLogTags;
+import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.dex.ArtManagerService;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DexoptOptions;
@@ -261,7 +263,8 @@
         final PackageSetting oldPkgSetting = request.getScanRequestOldPackageSetting();
         final PackageSetting originalPkgSetting = request.getScanRequestOriginalPackageSetting();
         final String realPkgName = request.getRealPackageName();
-        final List<String> changedAbiCodePath = request.getChangedAbiCodePath();
+        final List<String> changedAbiCodePath =
+                useArtService() ? null : request.getChangedAbiCodePath();
         final PackageSetting pkgSetting;
         if (request.getScanRequestPackageSetting() != null) {
             SharedUserSetting requestSharedUserSetting = mPm.mSettings.getSharedUserSettingLPr(
@@ -362,6 +365,8 @@
         }
         pkgSetting.setSigningDetails(reconciledPkg.mSigningDetails);
 
+        // The conditional on useArtService() for changedAbiCodePath above means this is skipped
+        // when ART Service is in use, since it has its own dex file GC.
         if (changedAbiCodePath != null && changedAbiCodePath.size() > 0) {
             for (int i = changedAbiCodePath.size() - 1; i >= 0; --i) {
                 final String codePathString = changedAbiCodePath.get(i);
@@ -370,6 +375,8 @@
                         mPm.mInstaller.rmdex(codePathString,
                                 getDexCodeInstructionSet(getPreferredInstructionSet()));
                     }
+                } catch (LegacyDexoptDisabledException e) {
+                    throw new RuntimeException(e);
                 } catch (Installer.InstallerException ignored) {
                 }
             }
@@ -2282,12 +2289,18 @@
                         pkg.getBaseApkPath(), pkg.getSplitCodePaths());
             }
 
-            // Prepare the application profiles for the new code paths.
-            // This needs to be done before invoking dexopt so that any install-time profile
-            // can be used for optimizations.
-            mArtManagerService.prepareAppProfiles(
-                    pkg, mPm.resolveUserIds(installRequest.getUserId()),
-                    /* updateReferenceProfileContent= */ true);
+            if (!useArtService()) { // ART Service handles this on demand instead.
+                // Prepare the application profiles for the new code paths.
+                // This needs to be done before invoking dexopt so that any install-time profile
+                // can be used for optimizations.
+                try {
+                    mArtManagerService.prepareAppProfiles(pkg,
+                            mPm.resolveUserIds(installRequest.getUserId()),
+                            /* updateReferenceProfileContent= */ true);
+                } catch (LegacyDexoptDisabledException e) {
+                    throw new RuntimeException(e);
+                }
+            }
 
             // Compute the compilation reason from the installation scenario.
             final int compilationReason =
@@ -2365,19 +2378,30 @@
 
                 realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp);
 
-                mPackageDexOptimizer.performDexOpt(pkg, realPkgSetting,
-                        null /* instructionSets */,
-                        mPm.getOrCreateCompilerPackageStats(pkg),
-                        mDexManager.getPackageUseInfoOrDefault(packageName),
-                        dexoptOptions);
+                // TODO(b/251903639): Call into ART Service.
+                try {
+                    mPackageDexOptimizer.performDexOpt(pkg, realPkgSetting,
+                            null /* instructionSets */, mPm.getOrCreateCompilerPackageStats(pkg),
+                            mDexManager.getPackageUseInfoOrDefault(packageName), dexoptOptions);
+                } catch (LegacyDexoptDisabledException e) {
+                    throw new RuntimeException(e);
+                }
                 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
             }
 
-            // Notify BackgroundDexOptService that the package has been changed.
-            // If this is an update of a package which used to fail to compile,
-            // BackgroundDexOptService will remove it from its denylist.
-            // TODO: Layering violation
-            BackgroundDexOptService.getService().notifyPackageChanged(packageName);
+            if (!useArtService()) {
+                // Notify BackgroundDexOptService that the package has been changed.
+                // If this is an update of a package which used to fail to compile,
+                // BackgroundDexOptService will remove it from its denylist.
+                // ART Service currently doesn't support this and will retry packages in every
+                // background dexopt.
+                // TODO: Layering violation
+                try {
+                    BackgroundDexOptService.getService().notifyPackageChanged(packageName);
+                } catch (LegacyDexoptDisabledException e) {
+                    throw new RuntimeException(e);
+                }
+            }
         }
         PackageManagerServiceUtils.waitForNativeBinariesExtractionForIncremental(
                 incrementalStorages);
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index a078b5d..9329f06 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -16,6 +16,8 @@
 
 package com.android.server.pm;
 
+import static com.android.server.pm.DexOptHelper.useArtService;
+
 import android.annotation.AppIdInt;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -609,11 +611,13 @@
      * @throws InstallerException if {@code dexopt} fails.
      */
     public boolean dexopt(String apkPath, int uid, String pkgName, String instructionSet,
-            int dexoptNeeded, @Nullable String outputPath, int dexFlags,
-            String compilerFilter, @Nullable String volumeUuid, @Nullable String classLoaderContext,
+            int dexoptNeeded, @Nullable String outputPath, int dexFlags, String compilerFilter,
+            @Nullable String volumeUuid, @Nullable String classLoaderContext,
             @Nullable String seInfo, boolean downgrade, int targetSdkVersion,
             @Nullable String profileName, @Nullable String dexMetadataPath,
-            @Nullable String compilationReason) throws InstallerException {
+            @Nullable String compilationReason)
+            throws InstallerException, LegacyDexoptDisabledException {
+        checkLegacyDexoptDisabled();
         assertValidInstructionSet(instructionSet);
         BlockGuard.getVmPolicy().onPathAccess(apkPath);
         BlockGuard.getVmPolicy().onPathAccess(outputPath);
@@ -637,7 +641,8 @@
      *
      * @param block set to true to enable blocking / false to disable blocking.
      */
-    public void controlDexOptBlocking(boolean block) {
+    public void controlDexOptBlocking(boolean block) throws LegacyDexoptDisabledException {
+        checkLegacyDexoptDisabled();
         try {
             mInstalld.controlDexOptBlocking(block);
         } catch (Exception e) {
@@ -655,7 +660,8 @@
      *         {@link #PROFILE_ANALYSIS_DONT_OPTIMIZE_EMPTY_PROFILES}
      */
     public int mergeProfiles(int uid, String packageName, String profileName)
-            throws InstallerException {
+            throws InstallerException, LegacyDexoptDisabledException {
+        checkLegacyDexoptDisabled();
         if (!checkBeforeRemote()) return PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA;
         try {
             return mInstalld.mergeProfiles(uid, packageName, profileName);
@@ -668,8 +674,9 @@
      * Dumps profiles associated with a package in a human readable format.
      */
     public boolean dumpProfiles(int uid, String packageName, String profileName, String codePath,
-                                boolean dumpClassesAndMethods)
-            throws InstallerException {
+            boolean dumpClassesAndMethods)
+            throws InstallerException, LegacyDexoptDisabledException {
+        checkLegacyDexoptDisabled();
         if (!checkBeforeRemote()) return false;
         BlockGuard.getVmPolicy().onPathAccess(codePath);
         try {
@@ -681,7 +688,8 @@
     }
 
     public boolean copySystemProfile(String systemProfile, int uid, String packageName,
-                String profileName) throws InstallerException {
+            String profileName) throws InstallerException, LegacyDexoptDisabledException {
+        checkLegacyDexoptDisabled();
         if (!checkBeforeRemote()) return false;
         try {
             return mInstalld.copySystemProfile(systemProfile, uid, packageName, profileName);
@@ -690,7 +698,9 @@
         }
     }
 
-    public void rmdex(String codePath, String instructionSet) throws InstallerException {
+    public void rmdex(String codePath, String instructionSet)
+            throws InstallerException, LegacyDexoptDisabledException {
+        checkLegacyDexoptDisabled();
         assertValidInstructionSet(instructionSet);
         if (!checkBeforeRemote()) return;
         BlockGuard.getVmPolicy().onPathAccess(codePath);
@@ -714,7 +724,9 @@
         }
     }
 
-    public void clearAppProfiles(String packageName, String profileName) throws InstallerException {
+    public void clearAppProfiles(String packageName, String profileName)
+            throws InstallerException, LegacyDexoptDisabledException {
+        checkLegacyDexoptDisabled();
         if (!checkBeforeRemote()) return;
         try {
             mInstalld.clearAppProfiles(packageName, profileName);
@@ -723,7 +735,9 @@
         }
     }
 
-    public void destroyAppProfiles(String packageName) throws InstallerException {
+    public void destroyAppProfiles(String packageName)
+            throws InstallerException, LegacyDexoptDisabledException {
+        checkLegacyDexoptDisabled();
         if (!checkBeforeRemote()) return;
         try {
             mInstalld.destroyAppProfiles(packageName);
@@ -737,7 +751,8 @@
      * @throws InstallerException if the deletion fails.
      */
     public void deleteReferenceProfile(String packageName, String profileName)
-            throws InstallerException {
+            throws InstallerException, LegacyDexoptDisabledException {
+        checkLegacyDexoptDisabled();
         if (!checkBeforeRemote()) return;
         try {
             mInstalld.deleteReferenceProfile(packageName, profileName);
@@ -799,7 +814,8 @@
      * Creates an oat dir for given package and instruction set.
      */
     public void createOatDir(String packageName, String oatDir, String dexInstructionSet)
-            throws InstallerException {
+            throws InstallerException, LegacyDexoptDisabledException {
+        checkLegacyDexoptDisabled();
         if (!checkBeforeRemote()) return;
         try {
             mInstalld.createOatDir(packageName, oatDir, dexInstructionSet);
@@ -843,7 +859,8 @@
      * of freed bytes.
      */
     public long deleteOdex(String packageName, String apkPath, String instructionSet,
-            String outputPath) throws InstallerException {
+            String outputPath) throws InstallerException, LegacyDexoptDisabledException {
+        checkLegacyDexoptDisabled();
         if (!checkBeforeRemote()) return -1;
         BlockGuard.getVmPolicy().onPathAccess(apkPath);
         BlockGuard.getVmPolicy().onPathAccess(outputPath);
@@ -855,7 +872,9 @@
     }
 
     public boolean reconcileSecondaryDexFile(String apkPath, String packageName, int uid,
-            String[] isas, @Nullable String volumeUuid, int flags) throws InstallerException {
+            String[] isas, @Nullable String volumeUuid, int flags)
+            throws InstallerException, LegacyDexoptDisabledException {
+        checkLegacyDexoptDisabled();
         for (int i = 0; i < isas.length; i++) {
             assertValidInstructionSet(isas[i]);
         }
@@ -881,7 +900,8 @@
     }
 
     public boolean createProfileSnapshot(int appId, String packageName, String profileName,
-            String classpath) throws InstallerException {
+            String classpath) throws InstallerException, LegacyDexoptDisabledException {
+        checkLegacyDexoptDisabled();
         if (!checkBeforeRemote()) return false;
         try {
             return mInstalld.createProfileSnapshot(appId, packageName, profileName, classpath);
@@ -891,7 +911,8 @@
     }
 
     public void destroyProfileSnapshot(String packageName, String profileName)
-            throws InstallerException {
+            throws InstallerException, LegacyDexoptDisabledException {
+        checkLegacyDexoptDisabled();
         if (!checkBeforeRemote()) return;
         try {
             mInstalld.destroyProfileSnapshot(packageName, profileName);
@@ -952,7 +973,9 @@
      * </ul>
      */
     public boolean prepareAppProfile(String pkg, @UserIdInt int userId, @AppIdInt int appId,
-            String profileName, String codePath, String dexMetadataPath) throws InstallerException {
+            String profileName, String codePath, String dexMetadataPath)
+            throws InstallerException, LegacyDexoptDisabledException {
+        checkLegacyDexoptDisabled();
         if (!checkBeforeRemote()) return false;
         BlockGuard.getVmPolicy().onPathAccess(codePath);
         BlockGuard.getVmPolicy().onPathAccess(dexMetadataPath);
@@ -1117,7 +1140,8 @@
      * @throws InstallerException if failed to get the visibility of the optimized artifacts.
      */
     public int getOdexVisibility(String packageName, String apkPath, String instructionSet,
-            String outputPath) throws InstallerException {
+            String outputPath) throws InstallerException, LegacyDexoptDisabledException {
+        checkLegacyDexoptDisabled();
         if (!checkBeforeRemote()) return -1;
         BlockGuard.getVmPolicy().onPathAccess(apkPath);
         BlockGuard.getVmPolicy().onPathAccess(outputPath);
@@ -1137,4 +1161,26 @@
             throw new InstallerException(e.toString());
         }
     }
+
+    /**
+     * A checked exception that is thrown in legacy dexopt code paths when ART Service should be
+     * used instead.
+     */
+    public static class LegacyDexoptDisabledException extends Exception {
+        // TODO(b/260124949): Remove the legacy dexopt code paths, i.e. this exception and all code
+        // that may throw it.
+        public LegacyDexoptDisabledException() {
+            super("Invalid call to legacy dexopt installd method while ART Service is in use.");
+        }
+    }
+
+    /**
+     * Throws LegacyDexoptDisabledException if ART Service should be used instead of the
+     * {@link android.os.IInstalld} method that follows this method call.
+     */
+    public static void checkLegacyDexoptDisabled() throws LegacyDexoptDisabledException {
+        if (useArtService()) {
+            throw new LegacyDexoptDisabledException();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 56f2493..490b2a9 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -37,6 +37,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.server.LocalServices;
 import com.android.server.pm.Installer.InstallerException;
+import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.dex.DexoptOptions;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.pkg.AndroidPackage;
@@ -167,7 +168,13 @@
             Log.i(TAG, "Low on space, deleting oat files in an attempt to free up space: "
                     + DexOptHelper.packagesToString(others));
             for (PackageStateInternal pkg : others) {
-                mPackageManagerService.deleteOatArtifactsOfPackage(snapshot, pkg.getPackageName());
+                // TODO(b/251903639): Call into ART Service.
+                try {
+                    mPackageManagerService.deleteOatArtifactsOfPackage(
+                            snapshot, pkg.getPackageName());
+                } catch (LegacyDexoptDisabledException e) {
+                    throw new RuntimeException(e);
+                }
             }
         }
         long spaceAvailableNow = getAvailableSpace();
@@ -188,7 +195,7 @@
                             + pkgSetting.getTransientState()
                             .getLatestForegroundPackageUseTimeInMills());
                 }
-            } catch (Exception ignored) {
+            } catch (RuntimeException ignored) {
             }
         }
     }
@@ -352,13 +359,17 @@
         PackageDexOptimizer optimizer = new OTADexoptPackageDexOptimizer(
                 collectingInstaller, mPackageManagerService.mInstallLock, mContext);
 
-        optimizer.performDexOpt(pkg, pkgSetting,
-                null /* ISAs */,
-                null /* CompilerStats.PackageStats */,
-                mPackageManagerService.getDexManager().getPackageUseInfoOrDefault(
-                        pkg.getPackageName()),
-                new DexoptOptions(pkg.getPackageName(), compilationReason,
-                        DexoptOptions.DEXOPT_BOOT_COMPLETE));
+        // TODO(b/251903639): Allow this use of legacy dexopt code even when ART Service is enabled.
+        try {
+            optimizer.performDexOpt(pkg, pkgSetting, null /* ISAs */,
+                    null /* CompilerStats.PackageStats */,
+                    mPackageManagerService.getDexManager().getPackageUseInfoOrDefault(
+                            pkg.getPackageName()),
+                    new DexoptOptions(pkg.getPackageName(), compilationReason,
+                            DexoptOptions.DEXOPT_BOOT_COMPLETE));
+        } catch (LegacyDexoptDisabledException e) {
+            throw new RuntimeException(e);
+        }
 
         return commands;
     }
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index aaf8755..0a71892 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -71,6 +71,7 @@
 import com.android.server.LocalServices;
 import com.android.server.apphibernation.AppHibernationManagerInternal;
 import com.android.server.pm.Installer.InstallerException;
+import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.dex.ArtManagerService;
 import com.android.server.pm.dex.ArtStatsLogUtils;
 import com.android.server.pm.dex.ArtStatsLogUtils.ArtStatsLogger;
@@ -219,7 +220,8 @@
     @DexOptResult
     int performDexOpt(AndroidPackage pkg, @NonNull PackageStateInternal pkgSetting,
             String[] instructionSets, CompilerStats.PackageStats packageStats,
-            PackageDexUsage.PackageUseInfo packageUseInfo, DexoptOptions options) {
+            PackageDexUsage.PackageUseInfo packageUseInfo, DexoptOptions options)
+            throws LegacyDexoptDisabledException {
         if (PLATFORM_PACKAGE_NAME.equals(pkg.getPackageName())) {
             throw new IllegalArgumentException("System server dexopting should be done via "
                     + " DexManager and PackageDexOptimizer#dexoptSystemServerPath");
@@ -245,7 +247,7 @@
     /**
      * Cancels currently running dex optimization.
      */
-    void controlDexOptBlocking(boolean block) {
+    void controlDexOptBlocking(boolean block) throws LegacyDexoptDisabledException {
         // This method should not hold mInstallLock as cancelling should be possible while
         // the lock is held by other thread running performDexOpt.
         getInstallerWithoutLock().controlDexOptBlocking(block);
@@ -259,7 +261,8 @@
     @DexOptResult
     private int performDexOptLI(AndroidPackage pkg, @NonNull PackageStateInternal pkgSetting,
             String[] targetInstructionSets, CompilerStats.PackageStats packageStats,
-            PackageDexUsage.PackageUseInfo packageUseInfo, DexoptOptions options) {
+            PackageDexUsage.PackageUseInfo packageUseInfo, DexoptOptions options)
+            throws LegacyDexoptDisabledException {
         // ClassLoader only refers non-native (jar) shared libraries and must ignore
         // native (so) shared libraries. See also LoadedApk#createSharedLibraryLoader().
         final List<SharedLibraryInfo> sharedLibraries = pkgSetting.getTransientState()
@@ -439,7 +442,7 @@
      */
     @GuardedBy("mInstallLock")
     private boolean prepareCloudProfile(AndroidPackage pkg, String profileName, String path,
-            @Nullable String dexMetadataPath) {
+            @Nullable String dexMetadataPath) throws LegacyDexoptDisabledException {
         if (dexMetadataPath != null) {
             try {
                 // Make sure we don't keep any existing contents.
@@ -472,7 +475,7 @@
             String path, String isa, String compilerFilter, int profileAnalysisResult,
             String classLoaderContext, int dexoptFlags, int uid,
             CompilerStats.PackageStats packageStats, boolean downgrade, String profileName,
-            String dexMetadataPath, int compilationReason) {
+            String dexMetadataPath, int compilationReason) throws LegacyDexoptDisabledException {
         String oatDir = getPackageOatDirIfSupported(pkgSetting, pkg);
 
         int dexoptNeeded = getDexoptNeeded(pkg.getPackageName(), path, isa, compilerFilter,
@@ -525,8 +528,8 @@
      */
     @GuardedBy("mInstallLock")
     @DexOptResult
-    public int dexoptSystemServerPath(
-            String dexPath, PackageDexUsage.DexUseInfo dexUseInfo, DexoptOptions options) {
+    public int dexoptSystemServerPath(String dexPath, PackageDexUsage.DexUseInfo dexUseInfo,
+            DexoptOptions options) throws LegacyDexoptDisabledException {
         int dexoptFlags = DEXOPT_PUBLIC
                 | (options.isBootComplete() ? DEXOPT_BOOTCOMPLETE : 0)
                 | (options.isDexoptIdleBackgroundJob() ? DEXOPT_IDLE_BACKGROUND_JOB : 0);
@@ -601,7 +604,8 @@
      */
     @DexOptResult
     public int dexOptSecondaryDexPath(ApplicationInfo info, String path,
-            PackageDexUsage.DexUseInfo dexUseInfo, DexoptOptions options) {
+            PackageDexUsage.DexUseInfo dexUseInfo, DexoptOptions options)
+            throws LegacyDexoptDisabledException {
         if (info.uid == -1) {
             throw new IllegalArgumentException("Dexopt for path " + path + " has invalid uid.");
         }
@@ -643,7 +647,7 @@
                         + " time out. Operation took " + duration + " ms. Thread: "
                         + Thread.currentThread().getName());
             }
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             Slog.wtf(TAG, "Error while releasing " + mDexoptWakeLock.getTag() + " lock", e);
         }
     }
@@ -651,7 +655,8 @@
     @GuardedBy("mInstallLock")
     @DexOptResult
     private int dexOptSecondaryDexPathLI(ApplicationInfo info, String path,
-            PackageDexUsage.DexUseInfo dexUseInfo, DexoptOptions options) {
+            PackageDexUsage.DexUseInfo dexUseInfo, DexoptOptions options)
+            throws LegacyDexoptDisabledException {
         String compilerFilter = getRealCompilerFilter(info, options.getCompilerFilter(),
                 dexUseInfo.isUsedByOtherApps());
         // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags.
@@ -729,7 +734,8 @@
      * Dumps the dexopt state of the given package {@code pkg} to the given {@code PrintWriter}.
      */
     void dumpDexoptState(IndentingPrintWriter pw, AndroidPackage pkg,
-            PackageStateInternal pkgSetting, PackageDexUsage.PackageUseInfo useInfo) {
+            PackageStateInternal pkgSetting, PackageDexUsage.PackageUseInfo useInfo)
+            throws LegacyDexoptDisabledException {
         final String[] instructionSets = getAppDexInstructionSets(pkgSetting.getPrimaryCpuAbi(),
                 pkgSetting.getSecondaryCpuAbi());
         final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
@@ -929,7 +935,8 @@
     @GuardedBy("mInstallLock")
     private int getDexoptNeeded(String packageName, String path, String isa, String compilerFilter,
             String classLoaderContext, int profileAnalysisResult, boolean downgrade,
-            int dexoptFlags, String oatDir) {
+            int dexoptFlags, String oatDir) throws LegacyDexoptDisabledException {
+        Installer.checkLegacyDexoptDisabled();
         final boolean shouldBePublic = (dexoptFlags & DEXOPT_PUBLIC) != 0;
         final boolean isProfileGuidedFilter = (dexoptFlags & DEXOPT_PROFILE_GUIDED) != 0;
         boolean newProfile = profileAnalysisResult == PROFILE_ANALYSIS_OPTIMIZE;
@@ -962,7 +969,7 @@
         } catch (IOException ioe) {
             Slog.w(TAG, "IOException reading apk: " + path, ioe);
             return DEX_OPT_FAILED;
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             Slog.wtf(TAG, "Unexpected exception when calling dexoptNeeded on " + path, e);
             return DEX_OPT_FAILED;
         }
@@ -976,11 +983,12 @@
 
     /** Returns true if the current artifacts of the app are private to the app itself. */
     @GuardedBy("mInstallLock")
-    private boolean isOdexPrivate(String packageName, String path, String isa, String oatDir) {
+    private boolean isOdexPrivate(String packageName, String path, String isa, String oatDir)
+            throws LegacyDexoptDisabledException {
         try {
             return mInstaller.getOdexVisibility(packageName, path, isa, oatDir)
                     == Installer.ODEX_IS_PRIVATE;
-        } catch (Exception e) {
+        } catch (InstallerException e) {
             Slog.w(TAG, "Failed to get odex visibility for " + path, e);
             return false;
         }
@@ -996,7 +1004,7 @@
      * may return a different result.
      */
     private int analyseProfiles(AndroidPackage pkg, int uid, String profileName,
-            String compilerFilter) {
+            String compilerFilter) throws LegacyDexoptDisabledException {
         // Check if we are allowed to merge and if the compiler filter is profile guided.
         if (!isProfileGuidedCompilerFilter(compilerFilter)) {
             return PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index afcd9d1..0556c3b 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -48,6 +48,7 @@
 import static com.android.internal.util.XmlUtils.writeByteArrayAttribute;
 import static com.android.internal.util.XmlUtils.writeStringAttribute;
 import static com.android.internal.util.XmlUtils.writeUriAttribute;
+import static com.android.server.pm.DexOptHelper.useArtService;
 import static com.android.server.pm.PackageInstallerService.prepareStageDir;
 import static com.android.server.pm.PackageManagerService.APP_METADATA_FILE_NAME;
 
@@ -164,6 +165,7 @@
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.LocalServices;
 import com.android.server.pm.Installer.InstallerException;
+import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
@@ -2382,9 +2384,15 @@
                 }
 
                 if (isLinkPossible(fromFiles, toDir)) {
-                    if (!mResolvedInstructionSets.isEmpty()) {
-                        final File oatDir = new File(toDir, "oat");
-                        createOatDirs(tempPackageName, mResolvedInstructionSets, oatDir);
+                    if (!useArtService()) { // ART Service creates oat dirs on demand instead.
+                        if (!mResolvedInstructionSets.isEmpty()) {
+                            final File oatDir = new File(toDir, "oat");
+                            try {
+                                createOatDirs(tempPackageName, mResolvedInstructionSets, oatDir);
+                            } catch (LegacyDexoptDisabledException e) {
+                                throw new RuntimeException(e);
+                            }
+                        }
                     }
                     // pre-create lib dirs for linking if necessary
                     if (!mResolvedNativeLibPaths.isEmpty()) {
@@ -3629,7 +3637,7 @@
     }
 
     private void createOatDirs(String packageName, List<String> instructionSets, File fromDir)
-            throws PackageManagerException {
+            throws PackageManagerException, LegacyDexoptDisabledException {
         for (String instructionSet : instructionSets) {
             try {
                 mInstaller.createOatDir(packageName, fromDir.getAbsolutePath(), instructionSet);
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index fb47c8a..04f5e56 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -47,6 +47,7 @@
 import android.util.ArraySet;
 import android.util.SparseArray;
 
+import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DynamicCodeLogger;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
@@ -708,7 +709,12 @@
     @Override
     @Deprecated
     public final long deleteOatArtifactsOfPackage(String packageName) {
-        return mService.deleteOatArtifactsOfPackage(snapshot(), packageName);
+        // TODO(b/251903639): Call into ART Service.
+        try {
+            return mService.deleteOatArtifactsOfPackage(snapshot(), packageName);
+        } catch (LegacyDexoptDisabledException e) {
+            throw new RuntimeException(e);
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b825ec4..399e32a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -39,6 +39,7 @@
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
 import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_INIT_TIME;
+import static com.android.server.pm.DexOptHelper.useArtService;
 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet;
 import static com.android.server.pm.InstructionSets.getPreferredInstructionSet;
 import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
@@ -198,6 +199,7 @@
 import com.android.server.compat.CompatChange;
 import com.android.server.compat.PlatformCompat;
 import com.android.server.pm.Installer.InstallerException;
+import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.Settings.VersionInfo;
 import com.android.server.pm.dex.ArtManagerService;
 import com.android.server.pm.dex.ArtUtils;
@@ -2136,16 +2138,19 @@
                 // the rest of the commands above) because there's precious little we
                 // can do about it. A settings error is reported, though.
                 final List<String> changedAbiCodePath =
-                        ScanPackageUtils.applyAdjustedAbiToSharedUser(
-                                setting, null /*scannedPackage*/,
+                        ScanPackageUtils.applyAdjustedAbiToSharedUser(setting,
+                                null /*scannedPackage*/,
                                 mInjector.getAbiHelper().getAdjustedAbiForSharedUser(
                                         setting.getPackageStates(), null /*scannedPackage*/));
-                if (changedAbiCodePath != null && changedAbiCodePath.size() > 0) {
+                if (!useArtService() && // Skip for ART Service since it has its own dex file GC.
+                        changedAbiCodePath != null && changedAbiCodePath.size() > 0) {
                     for (int i = changedAbiCodePath.size() - 1; i >= 0; --i) {
                         final String codePathString = changedAbiCodePath.get(i);
                         try {
                             mInstaller.rmdex(codePathString,
                                     getDexCodeInstructionSet(getPreferredInstructionSet()));
+                        } catch (LegacyDexoptDisabledException e) {
+                            throw new RuntimeException(e);
                         } catch (InstallerException ignored) {
                         }
                     }
@@ -2952,7 +2957,12 @@
     }
 
     public void updatePackagesIfNeeded() {
-        mDexOptHelper.performPackageDexOptUpgradeIfNeeded();
+        // TODO(b/251903639): Call into ART Service.
+        try {
+            mDexOptHelper.performPackageDexOptUpgradeIfNeeded();
+        } catch (LegacyDexoptDisabledException e) {
+            throw new RuntimeException(e);
+        }
     }
 
     private void notifyPackageUseInternal(String packageName, int reason) {
@@ -4266,7 +4276,12 @@
                     }
                 });
 
-        mBackgroundDexOptService.systemReady();
+        // TODO(b/251903639): Call into ART Service.
+        try {
+            mBackgroundDexOptService.systemReady();
+        } catch (LegacyDexoptDisabledException e) {
+            throw new RuntimeException(e);
+        }
 
         // Prune unused static shared libraries which have been cached a period of time
         schedulePruneUnusedStaticSharedLibraries(false /* delay */);
@@ -4615,7 +4630,8 @@
 
             final Computer snapshot = snapshotComputer();
             final AndroidPackage pkg = snapshot.getPackage(packageName);
-            try (PackageFreezer ignored = freezePackage(packageName, "clearApplicationProfileData")) {
+            try (PackageFreezer ignored =
+                            freezePackage(packageName, "clearApplicationProfileData")) {
                 synchronized (mInstallLock) {
                     mAppDataHelper.clearAppProfilesLIF(pkg);
                 }
@@ -4797,9 +4813,14 @@
                 throw new IllegalArgumentException("Unknown package: " + packageName);
             }
 
+            // TODO(b/251903639): Call into ART Service.
             synchronized (mInstallLock) {
                 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dump profiles");
-                mArtManagerService.dumpProfiles(pkg, dumpClassesAndMethods);
+                try {
+                    mArtManagerService.dumpProfiles(pkg, dumpClassesAndMethods);
+                } catch (LegacyDexoptDisabledException e) {
+                    throw new RuntimeException(e);
+                }
                 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
             }
         }
@@ -5497,20 +5518,40 @@
          */
         @Override
         public void reconcileSecondaryDexFiles(String packageName) {
+            if (useArtService()) {
+                // ART Service currently relies on a GC to find stale oat files, including secondary
+                // dex files. Hence it doesn't use this call for anything.
+                return;
+            }
+
             final Computer snapshot = snapshotComputer();
             if (snapshot.getInstantAppPackageName(Binder.getCallingUid()) != null) {
                 return;
             } else if (snapshot.isInstantAppInternal(
-                    packageName, UserHandle.getCallingUserId(), Process.SYSTEM_UID)) {
+                               packageName, UserHandle.getCallingUserId(), Process.SYSTEM_UID)) {
                 return;
             }
-            mDexManager.reconcileSecondaryDexFiles(packageName);
+            try {
+                mDexManager.reconcileSecondaryDexFiles(packageName);
+            } catch (LegacyDexoptDisabledException e) {
+                throw new RuntimeException(e);
+            }
         }
 
         @Override
         public void registerDexModule(String packageName, String dexModulePath,
                 boolean isSharedModule,
                 IDexModuleRegisterCallback callback) {
+            if (useArtService()) {
+                // ART Service currently doesn't support this explicit dexopting and instead relies
+                // on background dexopt for secondary dex files. This API is problematic since it
+                // doesn't provide the correct classloader context.
+                Slog.i(TAG,
+                        "Ignored unsupported registerDexModule call for " + dexModulePath + " in "
+                                + packageName);
+                return;
+            }
+
             int userId = UserHandle.getCallingUserId();
             ApplicationInfo ai = snapshot().getApplicationInfo(packageName, /*flags*/ 0, userId);
             DexManager.RegisterDexModuleResult result;
@@ -5520,7 +5561,12 @@
                                 " calling user. package=" + packageName + ", user=" + userId);
                 result = new DexManager.RegisterDexModuleResult(false, "Package not installed");
             } else {
-                result = mDexManager.registerDexModule(ai, dexModulePath, isSharedModule, userId);
+                try {
+                    result = mDexManager.registerDexModule(
+                            ai, dexModulePath, isSharedModule, userId);
+                } catch (LegacyDexoptDisabledException e) {
+                    throw new RuntimeException(e);
+                }
             }
 
             if (callback != null) {
@@ -6994,7 +7040,8 @@
         return AndroidPackageUtils.canHaveOatDir(packageState, packageState.getPkg());
     }
 
-    long deleteOatArtifactsOfPackage(@NonNull Computer snapshot, String packageName) {
+    long deleteOatArtifactsOfPackage(@NonNull Computer snapshot, String packageName)
+            throws LegacyDexoptDisabledException {
         PackageManagerServiceUtils.enforceSystemOrRootOrShell(
                 "Only the system or shell can delete oat artifacts");
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 2138c20..6af1d0f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -113,6 +113,7 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
 import com.android.server.art.ArtManagerLocal;
+import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.PackageManagerShellCommandDataLoader.Metadata;
 import com.android.server.pm.permission.LegacyPermissionManagerInternal;
 import com.android.server.pm.permission.PermissionAllowlist;
@@ -1953,52 +1954,62 @@
     }
 
     private int runBgDexOpt() throws RemoteException {
-        String opt = getNextOption();
+        // TODO(b/251903639): Call into ART Service.
+        try {
+            String opt = getNextOption();
 
-        if (opt == null) {
-            List<String> packageNames = new ArrayList<>();
-            String arg;
-            while ((arg = getNextArg()) != null) {
-                packageNames.add(arg);
-            }
-            if (!BackgroundDexOptService.getService().runBackgroundDexoptJob(
-                    packageNames.isEmpty() ? null : packageNames)) {
-                getOutPrintWriter().println("Failure");
-                return -1;
-            }
-        } else {
-            String extraArg = getNextArg();
-            if (extraArg != null) {
-                getErrPrintWriter().println("Invalid argument: " + extraArg);
-                return -1;
-            }
-
-            switch (opt) {
-                case "--cancel":
-                    return cancelBgDexOptJob();
-
-                case "--disable":
-                    BackgroundDexOptService.getService().setDisableJobSchedulerJobs(true);
-                    break;
-
-                case "--enable":
-                    BackgroundDexOptService.getService().setDisableJobSchedulerJobs(false);
-                    break;
-
-                default:
-                    getErrPrintWriter().println("Unknown option: " + opt);
+            if (opt == null) {
+                List<String> packageNames = new ArrayList<>();
+                String arg;
+                while ((arg = getNextArg()) != null) {
+                    packageNames.add(arg);
+                }
+                if (!BackgroundDexOptService.getService().runBackgroundDexoptJob(
+                            packageNames.isEmpty() ? null : packageNames)) {
+                    getOutPrintWriter().println("Failure");
                     return -1;
-            }
-        }
+                }
+            } else {
+                String extraArg = getNextArg();
+                if (extraArg != null) {
+                    getErrPrintWriter().println("Invalid argument: " + extraArg);
+                    return -1;
+                }
 
-        getOutPrintWriter().println("Success");
-        return 0;
+                switch (opt) {
+                    case "--cancel":
+                        return cancelBgDexOptJob();
+
+                    case "--disable":
+                        BackgroundDexOptService.getService().setDisableJobSchedulerJobs(true);
+                        break;
+
+                    case "--enable":
+                        BackgroundDexOptService.getService().setDisableJobSchedulerJobs(false);
+                        break;
+
+                    default:
+                        getErrPrintWriter().println("Unknown option: " + opt);
+                        return -1;
+                }
+            }
+
+            getOutPrintWriter().println("Success");
+            return 0;
+        } catch (LegacyDexoptDisabledException e) {
+            throw new RuntimeException(e);
+        }
     }
 
     private int cancelBgDexOptJob() throws RemoteException {
-        BackgroundDexOptService.getService().cancelBackgroundDexoptJob();
-        getOutPrintWriter().println("Success");
-        return 0;
+        // TODO(b/251903639): Call into ART Service.
+        try {
+            BackgroundDexOptService.getService().cancelBackgroundDexoptJob();
+            getOutPrintWriter().println("Success");
+            return 0;
+        } catch (LegacyDexoptDisabledException e) {
+            throw new RuntimeException(e);
+        }
     }
 
     private int runDeleteDexOpt() throws RemoteException {
@@ -2008,8 +2019,8 @@
             pw.println("Error: no package name");
             return 1;
         }
-        long freedBytes = LocalServices.getService(
-                PackageManagerInternal.class).deleteOatArtifactsOfPackage(packageName);
+        long freedBytes = LocalServices.getService(PackageManagerInternal.class)
+                                  .deleteOatArtifactsOfPackage(packageName);
         if (freedBytes < 0) {
             pw.println("Error: delete failed");
             return 1;
@@ -2273,7 +2284,7 @@
             if (abandonSession) {
                 try {
                     doAbandonSession(sessionId, false /*logSuccess*/);
-                } catch (Exception ignore) {
+                } catch (RuntimeException ignore) {
                 }
             }
         }
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index eb99536..e5aaddb 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -45,6 +45,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.ArrayUtils;
+import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.parsing.PackageCacher;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.parsing.pkg.PackageImpl;
@@ -424,8 +425,11 @@
             String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
             for (String codePath : allCodePaths) {
                 for (String dexCodeInstructionSet : dexCodeInstructionSets) {
+                    // TODO(b/251903639): Call into ART Service.
                     try {
                         mPm.mInstaller.rmdex(codePath, dexCodeInstructionSet);
+                    } catch (LegacyDexoptDisabledException e) {
+                        throw new RuntimeException(e);
                     } catch (Installer.InstallerException ignored) {
                     }
                 }
diff --git a/services/core/java/com/android/server/pm/ScanResult.java b/services/core/java/com/android/server/pm/ScanResult.java
index 750e893..2af01d6 100644
--- a/services/core/java/com/android/server/pm/ScanResult.java
+++ b/services/core/java/com/android/server/pm/ScanResult.java
@@ -47,6 +47,9 @@
      */
     @Nullable
     public final PackageSetting mPkgSetting;
+
+    // TODO(b/260124949): Check if this can be dropped when the legacy PackageManager dexopt code is
+    // cleaned up.
     /** ABI code paths that have changed in the package scan */
     @Nullable public final List<String> mChangedAbiCodePath;
 
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index e19ebce..b5b6347 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -52,6 +52,7 @@
 import com.android.server.LocalServices;
 import com.android.server.pm.Installer;
 import com.android.server.pm.Installer.InstallerException;
+import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.PackageManagerService;
 import com.android.server.pm.PackageManagerServiceCompilerMapping;
 import com.android.server.pm.parsing.PackageInfoUtils;
@@ -177,7 +178,6 @@
                 == ApplicationInfo.FLAG_DEBUGGABLE;
     }
 
-
     @Override
     public void snapshotRuntimeProfile(@ProfileType int profileType, @Nullable String packageName,
             @Nullable String codePath, @NonNull ISnapshotRuntimeProfileCallback callback,
@@ -210,15 +210,20 @@
             Slog.d(TAG, "Requested snapshot for " + packageName + ":" + codePath);
         }
 
-        if (bootImageProfile) {
-            snapshotBootImageProfile(callback);
-        } else {
-            snapshotAppProfile(packageName, codePath, callback);
+        // TODO(b/251903639): Call into ART Service.
+        try {
+            if (bootImageProfile) {
+                snapshotBootImageProfile(callback);
+            } else {
+                snapshotAppProfile(packageName, codePath, callback);
+            }
+        } catch (LegacyDexoptDisabledException e) {
+            throw new RuntimeException(e);
         }
     }
 
     private void snapshotAppProfile(String packageName, String codePath,
-            ISnapshotRuntimeProfileCallback callback) {
+            ISnapshotRuntimeProfileCallback callback) throws LegacyDexoptDisabledException {
         PackageInfo info = null;
         try {
             // Note that we use the default user 0 to retrieve the package info.
@@ -269,7 +274,8 @@
     }
 
     private void createProfileSnapshot(String packageName, String profileName, String classpath,
-            int appId, ISnapshotRuntimeProfileCallback callback) {
+            int appId, ISnapshotRuntimeProfileCallback callback)
+            throws LegacyDexoptDisabledException {
         // Ask the installer to snapshot the profile.
         try {
             if (!mInstaller.createProfileSnapshot(appId, packageName, profileName, classpath)) {
@@ -299,7 +305,8 @@
         }
     }
 
-    private void destroyProfileSnapshot(String packageName, String profileName) {
+    private void destroyProfileSnapshot(String packageName, String profileName)
+            throws LegacyDexoptDisabledException {
         if (DEBUG) {
             Slog.d(TAG, "Destroying profile snapshot for" + packageName + ":" + profileName);
         }
@@ -333,7 +340,8 @@
         }
     }
 
-    private void snapshotBootImageProfile(ISnapshotRuntimeProfileCallback callback) {
+    private void snapshotBootImageProfile(ISnapshotRuntimeProfileCallback callback)
+            throws LegacyDexoptDisabledException {
         // Combine the profiles for boot classpath and system server classpath.
         // This avoids having yet another type of profiles and simplifies the processing.
         String classpath = String.join(":", Os.getenv("BOOTCLASSPATH"),
@@ -364,7 +372,7 @@
         mHandler.post(() -> {
             try {
                 callback.onError(errCode);
-            } catch (Exception e) {
+            } catch (RemoteException | RuntimeException e) {
                 Slog.w(TAG, "Failed to callback after profile snapshot for " + packageName, e);
             }
         });
@@ -387,7 +395,7 @@
                             + packageName);
                     callback.onError(ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
                 }
-            } catch (Exception e) {
+            } catch (RemoteException | RuntimeException e) {
                 Slog.w(TAG,
                         "Failed to call onSuccess after profile snapshot for " + packageName, e);
             } finally {
@@ -402,9 +410,8 @@
      *   - create the current primary profile to save time at app startup time.
      *   - copy the profiles from the associated dex metadata file to the reference profile.
      */
-    public void prepareAppProfiles(
-            AndroidPackage pkg, @UserIdInt int user,
-            boolean updateReferenceProfileContent) {
+    public void prepareAppProfiles(AndroidPackage pkg, @UserIdInt int user,
+            boolean updateReferenceProfileContent) throws LegacyDexoptDisabledException {
         final int appId = UserHandle.getAppId(pkg.getUid());
         if (user < 0) {
             Slog.wtf(TAG, "Invalid user id: " + user);
@@ -444,9 +451,8 @@
     /**
      * Prepares the app profiles for a set of users. {@see ArtManagerService#prepareAppProfiles}.
      */
-    public void prepareAppProfiles(
-            AndroidPackage pkg, int[] user,
-            boolean updateReferenceProfileContent) {
+    public void prepareAppProfiles(AndroidPackage pkg, int[] user,
+            boolean updateReferenceProfileContent) throws LegacyDexoptDisabledException {
         for (int i = 0; i < user.length; i++) {
             prepareAppProfiles(pkg, user[i], updateReferenceProfileContent);
         }
@@ -455,7 +461,7 @@
     /**
      * Clear the profiles for the given package.
      */
-    public void clearAppProfiles(AndroidPackage pkg) {
+    public void clearAppProfiles(AndroidPackage pkg) throws LegacyDexoptDisabledException {
         try {
             ArrayMap<String, String> packageProfileNames = getPackageProfileNames(pkg);
             for (int i = packageProfileNames.size() - 1; i >= 0; i--) {
@@ -470,7 +476,8 @@
     /**
      * Dumps the profiles for the given package.
      */
-    public void dumpProfiles(AndroidPackage pkg, boolean dumpClassesAndMethods) {
+    public void dumpProfiles(AndroidPackage pkg, boolean dumpClassesAndMethods)
+            throws LegacyDexoptDisabledException {
         final int sharedGid = UserHandle.getSharedAppGid(pkg.getUid());
         try {
             ArrayMap<String, String> packageProfileNames = getPackageProfileNames(pkg);
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 8c2da45..fbf7409 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -46,6 +46,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.pm.Installer;
 import com.android.server.pm.Installer.InstallerException;
+import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.PackageDexOptimizer;
 import com.android.server.pm.PackageManagerService;
 import com.android.server.pm.PackageManagerServiceUtils;
@@ -190,7 +191,7 @@
         try {
             notifyDexLoadInternal(loadingAppInfo, classLoaderContextMap, loaderIsa,
                     loaderUserId, loaderIsIsolatedProcess);
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             Slog.w(TAG, "Exception while notifying dex load for package " +
                     loadingAppInfo.packageName, e);
         }
@@ -325,7 +326,7 @@
     public void load(Map<Integer, List<PackageInfo>> existingPackages) {
         try {
             loadInternal(existingPackages);
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             mPackageDexUsage.clear();
             mDynamicCodeLogger.clear();
             Slog.w(TAG, "Exception while loading. Starting with a fresh state.", e);
@@ -457,7 +458,7 @@
             List<String> packagesToKeepDataAbout = new ArrayList<>();
             mPackageDexUsage.syncData(
                     packageToUsersMap, packageToCodePaths, packagesToKeepDataAbout);
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             mPackageDexUsage.clear();
             Slog.w(TAG, "Exception while loading package dex usage. "
                     + "Starting with a fresh state.", e);
@@ -465,7 +466,7 @@
 
         try {
             mDynamicCodeLogger.readAndSync(packageToUsersMap);
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             mDynamicCodeLogger.clear();
             Slog.w(TAG, "Exception while loading package dynamic code usage. "
                     + "Starting with a fresh state.", e);
@@ -514,7 +515,7 @@
      * @return true if all secondary dex files were processed successfully (compiled or skipped
      *         because they don't need to be compiled)..
      */
-    public boolean dexoptSecondaryDex(DexoptOptions options) {
+    public boolean dexoptSecondaryDex(DexoptOptions options) throws LegacyDexoptDisabledException {
         if (isPlatformPackage(options.getPackageName())) {
             // We could easily redirect to #dexoptSystemServer in this case. But there should be
             // no-one calling this method directly for system server.
@@ -574,7 +575,8 @@
      * <p>PackageDexOptimizer.DEX_OPT_FAILED if any dexopt operation failed.
      * <p>PackageDexOptimizer.DEX_OPT_PERFORMED if all dexopt operations succeeded.
      */
-    public int dexoptSystemServer(DexoptOptions options) {
+    public int dexoptSystemServer(DexoptOptions options) throws LegacyDexoptDisabledException {
+        // TODO(b/254043366): Clean this up.
         if (!isPlatformPackage(options.getPackageName())) {
             Slog.wtf(TAG, "Non system server package used when trying to dexopt system server:"
                     + options.getPackageName());
@@ -664,7 +666,8 @@
      * {@code packagName} and the actual dex files. For all dex files that were
      * deleted, update the internal records and delete any generated oat files.
      */
-    public void reconcileSecondaryDexFiles(String packageName) {
+    public void reconcileSecondaryDexFiles(String packageName)
+            throws LegacyDexoptDisabledException {
         PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
         if (useInfo.getDexUseInfoMap().isEmpty()) {
             if (DEBUG) {
@@ -756,7 +759,7 @@
     // TODO(calin): questionable API in the presence of class loaders context. Needs amends as the
     // compilation happening here will use a pessimistic context.
     public RegisterDexModuleResult registerDexModule(ApplicationInfo info, String dexPath,
-            boolean isSharedModule, int userId) {
+            boolean isSharedModule, int userId) throws LegacyDexoptDisabledException {
         // Find the owning package record.
         DexSearchResult searchResult = getDexPackage(info, dexPath, userId);
 
@@ -1012,7 +1015,8 @@
      * @param packageInfo the package information.
      * @return the number of freed bytes or -1 if there was an error in the process.
      */
-    public long deleteOptimizedFiles(ArtPackageInfo packageInfo) {
+    public long deleteOptimizedFiles(ArtPackageInfo packageInfo)
+            throws LegacyDexoptDisabledException {
         long freedBytes = 0;
         boolean hadErrors = false;
         final String packageName = packageInfo.getPackageName();
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 9d098c6..1be9074 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -29,7 +29,6 @@
 import android.app.timedetector.ITimeDetectorService;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.TelephonyTimeSuggestion;
-import android.app.timedetector.TimePoint;
 import android.content.Context;
 import android.os.Binder;
 import android.os.Handler;
@@ -361,6 +360,34 @@
         mHandler.post(() -> mTimeDetectorStrategy.suggestNetworkTime(timeSignal));
     }
 
+    @Override
+    public UnixEpochTime latestNetworkTime() {
+        NetworkTimeSuggestion suggestion = getLatestNetworkSuggestion();
+        if (suggestion != null) {
+            return suggestion.getUnixEpochTime();
+        } else {
+            throw new ParcelableException(new DateTimeException("Missing network time fix"));
+        }
+    }
+
+    /**
+     * Returns the latest network suggestion accepted. For use by {@link TimeDetectorShellCommand}.
+     */
+    @Nullable
+    NetworkTimeSuggestion getLatestNetworkSuggestion() {
+        // TODO(b/222295093): Return the latest network time from mTimeDetectorStrategy once we can
+        //  be sure that all uses of NtpTrustedTime results in a suggestion being made to the time
+        //  detector. mNtpTrustedTime can be removed once this happens.
+        NtpTrustedTime.TimeResult ntpResult = mNtpTrustedTime.getCachedTimeResult();
+        if (ntpResult != null) {
+            UnixEpochTime unixEpochTime = new UnixEpochTime(
+                    ntpResult.getElapsedRealtimeMillis(), ntpResult.getTimeMillis());
+            return new NetworkTimeSuggestion(unixEpochTime, ntpResult.getUncertaintyMillis());
+        } else {
+            return null;
+        }
+    }
+
     void suggestGnssTime(@NonNull GnssTimeSuggestion timeSignal) {
         enforceSuggestGnssTimePermission();
         Objects.requireNonNull(timeSignal);
@@ -377,19 +404,6 @@
     }
 
     @Override
-    public TimePoint latestNetworkTime() {
-        // TODO(b/222295093): Return the latest network time from mTimeDetectorStrategy once we can
-        //  be sure that all uses of NtpTrustedTime results in a suggestion being made to the time
-        //  detector. mNtpTrustedTime can be removed once this happens.
-        NtpTrustedTime.TimeResult ntpResult = mNtpTrustedTime.getCachedTimeResult();
-        if (ntpResult != null) {
-            return new TimePoint(ntpResult.getTimeMillis(), ntpResult.getElapsedRealtimeMillis());
-        } else {
-            throw new ParcelableException(new DateTimeException("Missing network time fix"));
-        }
-    }
-
-    @Override
     protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
             @Nullable String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
@@ -421,7 +435,7 @@
     private void enforceSuggestNetworkTimePermission() {
         mContext.enforceCallingPermission(
                 android.Manifest.permission.SET_TIME,
-                "set time");
+                "suggest network time");
     }
 
     private void enforceSuggestGnssTimePermission() {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 70e7dae..fd6d606 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -6344,7 +6344,7 @@
                 int wakefulness) {
             synchronized (mGlobalLock) {
                 if (mHomeProcess != null && (dumpPackage == null
-                        || mHomeProcess.mPkgList.contains(dumpPackage))) {
+                        || mHomeProcess.containsPackage(dumpPackage))) {
                     if (needSep) {
                         pw.println();
                         needSep = false;
@@ -6352,7 +6352,7 @@
                     pw.println("  mHomeProcess: " + mHomeProcess);
                 }
                 if (mPreviousProcess != null && (dumpPackage == null
-                        || mPreviousProcess.mPkgList.contains(dumpPackage))) {
+                        || mPreviousProcess.containsPackage(dumpPackage))) {
                     if (needSep) {
                         pw.println();
                         needSep = false;
@@ -6360,14 +6360,14 @@
                     pw.println("  mPreviousProcess: " + mPreviousProcess);
                 }
                 if (dumpAll && (mPreviousProcess == null || dumpPackage == null
-                        || mPreviousProcess.mPkgList.contains(dumpPackage))) {
+                        || mPreviousProcess.containsPackage(dumpPackage))) {
                     StringBuilder sb = new StringBuilder(128);
                     sb.append("  mPreviousProcessVisibleTime: ");
                     TimeUtils.formatDuration(mPreviousProcessVisibleTime, sb);
                     pw.println(sb);
                 }
                 if (mHeavyWeightProcess != null && (dumpPackage == null
-                        || mHeavyWeightProcess.mPkgList.contains(dumpPackage))) {
+                        || mHeavyWeightProcess.containsPackage(dumpPackage))) {
                     if (needSep) {
                         pw.println();
                         needSep = false;
@@ -6491,18 +6491,18 @@
                 }
 
                 if (mHomeProcess != null && (dumpPackage == null
-                        || mHomeProcess.mPkgList.contains(dumpPackage))) {
+                        || mHomeProcess.containsPackage(dumpPackage))) {
                     mHomeProcess.dumpDebug(proto, HOME_PROC);
                 }
 
                 if (mPreviousProcess != null && (dumpPackage == null
-                        || mPreviousProcess.mPkgList.contains(dumpPackage))) {
+                        || mPreviousProcess.containsPackage(dumpPackage))) {
                     mPreviousProcess.dumpDebug(proto, PREVIOUS_PROC);
                     proto.write(PREVIOUS_PROC_VISIBLE_TIME_MS, mPreviousProcessVisibleTime);
                 }
 
                 if (mHeavyWeightProcess != null && (dumpPackage == null
-                        || mHeavyWeightProcess.mPkgList.contains(dumpPackage))) {
+                        || mHeavyWeightProcess.containsPackage(dumpPackage))) {
                     mHeavyWeightProcess.dumpDebug(proto, HEAVY_WEIGHT_PROC);
                 }
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 103b9b2..b1bc52a 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1714,7 +1714,7 @@
                     // Don't kill the home process along with tasks from the same package.
                     continue;
                 }
-                if (!proc.mPkgList.contains(pkg)) {
+                if (!proc.containsPackage(pkg)) {
                     // Don't kill process that is not associated with this task.
                     continue;
                 }
diff --git a/services/core/java/com/android/server/wm/CompatModePackages.java b/services/core/java/com/android/server/wm/CompatModePackages.java
index a035948..d5828ef 100644
--- a/services/core/java/com/android/server/wm/CompatModePackages.java
+++ b/services/core/java/com/android/server/wm/CompatModePackages.java
@@ -566,7 +566,7 @@
             SparseArray<WindowProcessController> pidMap = mService.mProcessMap.getPidMap();
             for (int i = pidMap.size() - 1; i >= 0; i--) {
                 final WindowProcessController app = pidMap.valueAt(i);
-                if (!app.mPkgList.contains(packageName)) {
+                if (!app.containsPackage(packageName)) {
                     continue;
                 }
                 try {
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 9b361fe..3e279b2 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -76,6 +76,7 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.IRemoteAnimationRunner;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.HeavyWeightSwitcherActivity;
 import com.android.internal.protolog.common.ProtoLog;
@@ -118,7 +119,8 @@
     // communicate back to the activity manager side.
     public final Object mOwner;
     // List of packages running in the process
-    final ArraySet<String> mPkgList = new ArraySet<>();
+    @GuardedBy("itself")
+    private final ArrayList<String> mPkgList = new ArrayList<>(1);
     private final WindowProcessListener mListener;
     private final ActivityTaskManagerService mAtm;
     private final BackgroundLaunchProcessController mBgLaunchController;
@@ -645,18 +647,26 @@
 
     @HotPath(caller = HotPath.PROCESS_CHANGE)
     public void addPackage(String packageName) {
-        synchronized (mAtm.mGlobalLockWithoutBoost) {
-            mPkgList.add(packageName);
+        synchronized (mPkgList) {
+            if (!mPkgList.contains(packageName)) {
+                mPkgList.add(packageName);
+            }
         }
     }
 
     @HotPath(caller = HotPath.PROCESS_CHANGE)
     public void clearPackageList() {
-        synchronized (mAtm.mGlobalLockWithoutBoost) {
+        synchronized (mPkgList) {
             mPkgList.clear();
         }
     }
 
+    boolean containsPackage(String packageName) {
+        synchronized (mPkgList) {
+            return mPkgList.contains(packageName);
+        }
+    }
+
     void addActivityIfNeeded(ActivityRecord r) {
         // even if we already track this activity, note down that it has been launched
         setLastActivityLaunchTime(r.lastLaunchTime);
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 5ecf737..a1f4096 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -329,8 +329,8 @@
 
             mSurfaceController = new WindowSurfaceController(attrs.getTitle().toString(), format,
                     flags, this, attrs.type);
-            mSurfaceController.setColorSpaceAgnostic((attrs.privateFlags
-                    & WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC) != 0);
+            mSurfaceController.setColorSpaceAgnostic(w.getPendingTransaction(),
+                    (attrs.privateFlags & LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC) != 0);
 
             w.setHasSurface(true);
             // The surface instance is changed. Make sure the input info can be applied to the
@@ -527,7 +527,7 @@
         if (mSurfaceController == null) {
             return;
         }
-        mSurfaceController.setColorSpaceAgnostic(agnostic);
+        mSurfaceController.setColorSpaceAgnostic(mWin.getPendingTransaction(), agnostic);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index 4a5c473..607ce25 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -236,24 +236,13 @@
         }
     }
 
-    void setColorSpaceAgnostic(boolean agnostic) {
+    void setColorSpaceAgnostic(SurfaceControl.Transaction t, boolean agnostic) {
         ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isColorSpaceAgnostic=%b: %s", agnostic, title);
 
         if (mSurfaceControl == null) {
             return;
         }
-        if (SHOW_LIGHT_TRANSACTIONS) {
-            Slog.i(TAG, ">>> OPEN TRANSACTION setColorSpaceAgnosticLocked");
-        }
-        mService.openSurfaceTransaction();
-        try {
-            getGlobalTransaction().setColorSpaceAgnostic(mSurfaceControl, agnostic);
-        } finally {
-            mService.closeSurfaceTransaction("setColorSpaceAgnostic");
-            if (SHOW_LIGHT_TRANSACTIONS) {
-                Slog.i(TAG, "<<< CLOSE TRANSACTION setColorSpaceAgnosticLocked");
-            }
-        }
+        t.setColorSpaceAgnostic(mSurfaceControl, agnostic);
     }
 
     boolean showRobustly(SurfaceControl.Transaction t) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index 70a7a02..4c42791 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -34,6 +34,7 @@
 import android.app.admin.DeviceAdminInfo;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.FactoryResetProtectionPolicy;
+import android.app.admin.ManagedSubscriptionsPolicy;
 import android.app.admin.PackagePolicy;
 import android.app.admin.PasswordPolicy;
 import android.app.admin.PreferentialNetworkServiceConfig;
@@ -168,6 +169,7 @@
     private static final String TAG_PROTECTED_PACKAGES = "protected_packages";
     private static final String TAG_SUSPENDED_PACKAGES = "suspended-packages";
     private static final String TAG_MTE_POLICY = "mte-policy";
+    private static final String TAG_MANAGED_SUBSCRIPTIONS_POLICY = "managed_subscriptions_policy";
     private static final String ATTR_VALUE = "value";
     private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification";
     private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications";
@@ -272,6 +274,9 @@
     // Wi-Fi SSID restriction policy.
     WifiSsidPolicy mWifiSsidPolicy;
 
+    // Managed subscriptions policy.
+    ManagedSubscriptionsPolicy mManagedSubscriptionsPolicy;
+
     // TODO: review implementation decisions with frameworks team
     boolean specifiesGlobalProxy = false;
     String globalProxySpec = null;
@@ -642,6 +647,11 @@
                 mManagedProfileCallerIdAccess);
         writePackagePolicy(out, TAG_CROSS_PROFILE_CONTACTS_SEARCH_POLICY,
                 mManagedProfileContactsAccess);
+        if (mManagedSubscriptionsPolicy != null) {
+            out.startTag(null, TAG_MANAGED_SUBSCRIPTIONS_POLICY);
+            mManagedSubscriptionsPolicy.saveToXml(out);
+            out.endTag(null, TAG_MANAGED_SUBSCRIPTIONS_POLICY);
+        }
     }
 
     private void writePackagePolicy(TypedXmlSerializer out, String tag,
@@ -946,6 +956,8 @@
                 mManagedProfileCallerIdAccess = readPackagePolicy(parser);
             } else if (TAG_CROSS_PROFILE_CONTACTS_SEARCH_POLICY.equals(tag)) {
                 mManagedProfileContactsAccess = readPackagePolicy(parser);
+            } else if (TAG_MANAGED_SUBSCRIPTIONS_POLICY.equals(tag)) {
+                mManagedSubscriptionsPolicy = ManagedSubscriptionsPolicy.readFromXml(parser);
             } else {
                 Slogf.w(LOG_TAG, "Unknown admin tag: %s", tag);
                 XmlUtils.skipCurrentTag(parser);
@@ -1414,5 +1426,13 @@
 
         pw.print("accountTypesWithManagementDisabled=");
         pw.println(accountTypesWithManagementDisabled);
+
+        if (mManagedSubscriptionsPolicy != null) {
+            pw.println("mManagedSubscriptionsPolicy:");
+            pw.increaseIndent();
+            pw.print("mPolicyType=");
+            mManagedSubscriptionsPolicy.getPolicyType();
+            pw.decreaseIndent();
+        }
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2b92b22..cdb2e08 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -218,6 +218,7 @@
 import android.app.admin.FactoryResetProtectionPolicy;
 import android.app.admin.FullyManagedDeviceProvisioningParams;
 import android.app.admin.ManagedProfileProvisioningParams;
+import android.app.admin.ManagedSubscriptionsPolicy;
 import android.app.admin.NetworkEvent;
 import android.app.admin.PackagePolicy;
 import android.app.admin.ParcelableGranteeMap;
@@ -334,6 +335,8 @@
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.ParcelableKeyGenParameterSpec;
 import android.stats.devicepolicy.DevicePolicyEnums;
+import android.telecom.TelecomManager;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
 import android.text.TextUtils;
@@ -768,6 +771,9 @@
     private final DeviceStateCacheImpl mStateCache = new DeviceStateCacheImpl();
     private final Object mESIDInitilizationLock = new Object();
     private EnterpriseSpecificIdCalculator mEsidCalculator;
+    private final Object mSubscriptionsChangedListenerLock = new Object();
+    @GuardedBy("mSubscriptionsChangedListenerLock")
+    private SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionsChangedListener;
 
     /**
      * Contains the list of OEM Default Role Holders for Contact-related roles
@@ -973,8 +979,6 @@
     @GuardedBy("getLockObject()")
     final SparseArray<DevicePolicyData> mUserData;
 
-    @GuardedBy("getLockObject()")
-
     final Handler mHandler;
     final Handler mBackgroundHandler;
 
@@ -3092,6 +3096,7 @@
                 onLockSettingsReady();
                 loadAdminDataAsync();
                 mOwners.systemReady();
+                applyManagedSubscriptionsPolicyIfRequired();
                 break;
             case SystemService.PHASE_ACTIVITY_MANAGER_READY:
                 synchronized (getLockObject()) {
@@ -3110,6 +3115,22 @@
         }
     }
 
+    private void applyManagedSubscriptionsPolicyIfRequired() {
+        int copeProfileUserId = getOrganizationOwnedProfileUserId();
+        // This policy is relevant only for COPE devices.
+        if (copeProfileUserId != UserHandle.USER_NULL) {
+            unregisterOnSubscriptionsChangedListener();
+            int policyType = getManagedSubscriptionsPolicy().getPolicyType();
+            if (policyType == ManagedSubscriptionsPolicy.TYPE_ALL_PERSONAL_SUBSCRIPTIONS) {
+                // By default, assign all current and future subs to system user on COPE devices.
+                registerListenerToAssignSubscriptionsToUser(UserHandle.USER_SYSTEM);
+            } else if (policyType == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
+                // Add listener to assign all current and future subs to managed profile.
+                registerListenerToAssignSubscriptionsToUser(copeProfileUserId);
+            }
+        }
+    }
+
     private void updatePersonalAppsSuspensionOnUserStart(int userHandle) {
         final int profileUserHandle = getManagedUserId(userHandle);
         if (profileUserHandle >= 0) {
@@ -6993,9 +7014,23 @@
         }
         mLockSettingsInternal.refreshStrongAuthTimeout(parentId);
 
+        clearManagedSubscriptionsPolicy();
+
         Slogf.i(LOG_TAG, "Cleaning up device-wide policies done.");
     }
 
+    private void clearManagedSubscriptionsPolicy() {
+        unregisterOnSubscriptionsChangedListener();
+
+        SubscriptionManager subscriptionManager = mContext.getSystemService(
+                SubscriptionManager.class);
+        //Iterate over all the subscriptions and remove association with any user.
+        int[] subscriptionIds = subscriptionManager.getActiveSubscriptionIdList(false);
+        for (int subId : subscriptionIds) {
+            subscriptionManager.setSubscriptionUserHandle(subId, null);
+        }
+    }
+
     /**
      * @param factoryReset null: legacy behaviour, false: attempt to remove user, true: attempt to
      *                     factory reset
@@ -10088,6 +10123,10 @@
                 mStateCache.dump(pw);
                 pw.println();
             }
+
+            synchronized (mSubscriptionsChangedListenerLock) {
+                pw.println("Subscription changed listener : " + mSubscriptionsChangedListener);
+            }
             mHandler.post(() -> handleDump(pw));
             dumpResources(pw);
         }
@@ -19952,4 +19991,139 @@
                 HEADLESS_FLAG,
                 DEFAULT_HEADLESS_FLAG);
     }
+
+    @Override
+    public ManagedSubscriptionsPolicy getManagedSubscriptionsPolicy() {
+        synchronized (getLockObject()) {
+            ActiveAdmin admin = getProfileOwnerOfOrganizationOwnedDeviceLocked();
+            if (admin != null && admin.mManagedSubscriptionsPolicy != null) {
+                return admin.mManagedSubscriptionsPolicy;
+            }
+        }
+        return new ManagedSubscriptionsPolicy(
+                ManagedSubscriptionsPolicy.TYPE_ALL_PERSONAL_SUBSCRIPTIONS);
+    }
+
+    @Override
+    public void setManagedSubscriptionsPolicy(ManagedSubscriptionsPolicy policy) {
+        CallerIdentity caller = getCallerIdentity();
+        Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller),
+                "This policy can only be set by a profile owner on an organization-owned device.");
+
+        synchronized (getLockObject()) {
+            final ActiveAdmin admin = getProfileOwnerLocked(caller.getUserId());
+            if (hasUserSetupCompleted(UserHandle.USER_SYSTEM) && !isAdminTestOnlyLocked(
+                    admin.info.getComponent(), caller.getUserId())) {
+                throw new IllegalStateException("Not allowed to apply this policy after setup");
+            }
+            boolean changed = false;
+            if (!Objects.equals(policy, admin.mManagedSubscriptionsPolicy)) {
+                admin.mManagedSubscriptionsPolicy = policy;
+                changed = true;
+            }
+            if (changed) {
+                saveSettingsLocked(caller.getUserId());
+            } else {
+                return;
+            }
+        }
+
+        applyManagedSubscriptionsPolicyIfRequired();
+
+        int policyType = getManagedSubscriptionsPolicy().getPolicyType();
+        if (policyType == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
+            final long id = mInjector.binderClearCallingIdentity();
+            try {
+                int parentUserId = getProfileParentId(caller.getUserId());
+                installOemDefaultDialerAndMessagesApp(parentUserId, caller.getUserId());
+            } finally {
+                mInjector.binderRestoreCallingIdentity(id);
+            }
+        }
+    }
+
+    private void installOemDefaultDialerAndMessagesApp(int sourceUserId, int targetUserId) {
+        try {
+            UserHandle sourceUserHandle = UserHandle.of(sourceUserId);
+            TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
+            String dialerAppPackage = telecomManager.getDefaultDialerPackage(
+                    sourceUserHandle);
+            String messagesAppPackage = SmsApplication.getDefaultSmsApplicationAsUser(mContext,
+                    true, sourceUserHandle).getPackageName();
+            if (dialerAppPackage != null) {
+                mIPackageManager.installExistingPackageAsUser(dialerAppPackage, targetUserId,
+                        PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS,
+                        PackageManager.INSTALL_REASON_POLICY, null);
+            } else {
+                Slogf.w(LOG_TAG, "Couldn't install dialer app, dialer app package is null");
+            }
+
+            if (messagesAppPackage != null) {
+                mIPackageManager.installExistingPackageAsUser(messagesAppPackage, targetUserId,
+                        PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS,
+                        PackageManager.INSTALL_REASON_POLICY, null);
+            } else {
+                Slogf.w(LOG_TAG, "Couldn't install messages app, messages app package is null");
+            }
+        } catch (RemoteException re) {
+            // shouldn't happen
+            Slogf.wtf(LOG_TAG, "Failed to install dialer/messages app", re);
+        }
+    }
+
+    private void registerListenerToAssignSubscriptionsToUser(int userId) {
+        synchronized (mSubscriptionsChangedListenerLock) {
+            if (mSubscriptionsChangedListener != null) {
+                return;
+            }
+            SubscriptionManager subscriptionManager = mContext.getSystemService(
+                    SubscriptionManager.class);
+            // Listener to assign all current and future subs to managed profile.
+            mSubscriptionsChangedListener = new SubscriptionManager.OnSubscriptionsChangedListener(
+                    mHandler.getLooper()) {
+                @Override
+                public void onSubscriptionsChanged() {
+                    final long id = mInjector.binderClearCallingIdentity();
+                    try {
+                        int[] subscriptionIds = subscriptionManager.getActiveSubscriptionIdList(
+                                false);
+                        for (int subId : subscriptionIds) {
+                            UserHandle associatedUserHandle =
+                                    subscriptionManager.getSubscriptionUserHandle(subId);
+                            if (associatedUserHandle == null
+                                    || associatedUserHandle.getIdentifier() != userId) {
+                                subscriptionManager.setSubscriptionUserHandle(subId,
+                                        UserHandle.of(userId));
+                            }
+                        }
+                    } finally {
+                        mInjector.binderRestoreCallingIdentity(id);
+                    }
+                }
+            };
+
+            final long id = mInjector.binderClearCallingIdentity();
+            try {
+                // When listener is added onSubscriptionsChanged gets called immediately for once
+                // (even if subscriptions are not changed) and later on when subscriptions changes.
+                subscriptionManager.addOnSubscriptionsChangedListener(
+                        mSubscriptionsChangedListener.getHandlerExecutor(),
+                        mSubscriptionsChangedListener);
+            } finally {
+                mInjector.binderRestoreCallingIdentity(id);
+            }
+        }
+    }
+
+    private void unregisterOnSubscriptionsChangedListener() {
+        synchronized (mSubscriptionsChangedListenerLock) {
+            if (mSubscriptionsChangedListener != null) {
+                SubscriptionManager subscriptionManager = mContext.getSystemService(
+                        SubscriptionManager.class);
+                subscriptionManager.removeOnSubscriptionsChangedListener(
+                        mSubscriptionsChangedListener);
+                mSubscriptionsChangedListener = null;
+            }
+        }
+    }
 }
\ No newline at end of file
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 01674bb..8b36da5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
@@ -169,19 +169,19 @@
     }
 
     @Test
-    public void testBootCompleted() {
+    public void testBootCompleted() throws Exception {
         initUntilBootCompleted();
     }
 
     @Test
-    public void testNoExecutionForIdleJobBeforePostBootUpdate() {
+    public void testNoExecutionForIdleJobBeforePostBootUpdate() throws Exception {
         initUntilBootCompleted();
 
         assertThat(mService.onStartJob(mJobServiceForIdle, mJobParametersForIdle)).isFalse();
     }
 
     @Test
-    public void testNoExecutionForLowStorage() {
+    public void testNoExecutionForLowStorage() throws Exception {
         initUntilBootCompleted();
         when(mPackageManager.isStorageLow()).thenReturn(true);
 
@@ -191,7 +191,7 @@
     }
 
     @Test
-    public void testNoExecutionForNoOptimizablePackages() {
+    public void testNoExecutionForNoOptimizablePackages() throws Exception {
         initUntilBootCompleted();
         when(mDexOptHelper.getOptimizablePackages(any())).thenReturn(Collections.emptyList());
 
@@ -201,7 +201,7 @@
     }
 
     @Test
-    public void testPostBootUpdateFullRun() {
+    public void testPostBootUpdateFullRun() throws Exception {
         initUntilBootCompleted();
 
         runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot,
@@ -210,7 +210,7 @@
     }
 
     @Test
-    public void testPostBootUpdateFullRunWithPackageFailure() {
+    public void testPostBootUpdateFullRunWithPackageFailure() throws Exception {
         mDexOptResultForPackageAAA = PackageDexOptimizer.DEX_OPT_FAILED;
 
         initUntilBootCompleted();
@@ -224,7 +224,7 @@
     }
 
     @Test
-    public void testIdleJobFullRun() {
+    public void testIdleJobFullRun() throws Exception {
         initUntilBootCompleted();
         runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot,
                 /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK,
@@ -235,7 +235,7 @@
     }
 
     @Test
-    public void testIdleJobFullRunWithFailureOnceAndSuccessAfterUpdate() {
+    public void testIdleJobFullRunWithFailureOnceAndSuccessAfterUpdate() throws Exception {
         mDexOptResultForPackageAAA = PackageDexOptimizer.DEX_OPT_FAILED;
 
         initUntilBootCompleted();
@@ -271,7 +271,7 @@
     }
 
     @Test
-    public void testIdleJobFullRunWithFatalError() {
+    public void testIdleJobFullRunWithFatalError() throws Exception {
         initUntilBootCompleted();
         runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot,
                 /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK,
@@ -285,7 +285,7 @@
     }
 
     @Test
-    public void testSystemReadyWhenDisabled() {
+    public void testSystemReadyWhenDisabled() throws Exception {
         when(mInjector.isBackgroundDexOptDisabled()).thenReturn(true);
 
         mService.systemReady();
@@ -294,7 +294,7 @@
     }
 
     @Test
-    public void testStopByCancelFlag() {
+    public void testStopByCancelFlag() throws Exception {
         when(mInjector.createAndStartThread(any(), any())).thenReturn(Thread.currentThread());
         initUntilBootCompleted();
 
@@ -447,7 +447,7 @@
     }
 
     @Test
-    public void testStopByThermal() {
+    public void testStopByThermal() throws Exception {
         when(mInjector.createAndStartThread(any(), any())).thenReturn(Thread.currentThread());
         initUntilBootCompleted();
 
@@ -478,7 +478,7 @@
     }
 
     @Test
-    public void testDisableJobSchedulerJobs() {
+    public void testDisableJobSchedulerJobs() throws Exception {
         when(mInjector.getCallingUid()).thenReturn(Process.SHELL_UID);
         mService.setDisableJobSchedulerJobs(true);
         assertThat(mService.onStartJob(mJobServiceForIdle, mJobParametersForIdle)).isFalse();
@@ -492,7 +492,7 @@
         assertThrows(SecurityException.class, () -> mService.setDisableJobSchedulerJobs(true));
     }
 
-    private void initUntilBootCompleted() {
+    private void initUntilBootCompleted() throws Exception {
         ArgumentCaptor<BroadcastReceiver> argReceiver = ArgumentCaptor.forClass(
                 BroadcastReceiver.class);
         ArgumentCaptor<IntentFilter> argIntentFilter = ArgumentCaptor.forClass(IntentFilter.class);
@@ -515,7 +515,7 @@
         assertThat(jobIds).containsExactlyElementsIn(expectedJobIds);
     }
 
-    private void verifyLastControlDexOptBlockingCall(boolean expected) {
+    private void verifyLastControlDexOptBlockingCall(boolean expected) throws Exception {
         ArgumentCaptor<Boolean> argDexOptBlock = ArgumentCaptor.forClass(Boolean.class);
         verify(mDexOptHelper, atLeastOnce()).controlDexOptBlocking(argDexOptBlock.capture());
         assertThat(argDexOptBlock.getValue()).isEqualTo(expected);
@@ -523,7 +523,7 @@
 
     private void runFullJob(BackgroundDexOptJobService jobService, JobParameters params,
             boolean expectedReschedule, int expectedStatus, int totalJobFinishedWithParams,
-            @Nullable String expectedSkippedPackage) {
+            @Nullable String expectedSkippedPackage) throws Exception {
         when(mInjector.createAndStartThread(any(), any())).thenReturn(Thread.currentThread());
         addFullRunSequence(expectedSkippedPackage);
         assertThat(mService.onStartJob(jobService, params)).isTrue();
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index a55b196..61c3f13 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -5014,7 +5014,8 @@
                 .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
         when(getServices().userManager.getPrimaryUser())
                 .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
-
+        when(getServices().subscriptionManager.getActiveSubscriptionIdList(false)).thenReturn(
+                new int[1]);
         // Set some device-wide policies:
         // Security logging
         when(getServices().settings.securityLogGetLoggingEnabledProperty()).thenReturn(true);
@@ -5062,6 +5063,7 @@
         // Unsuspend personal apps
         verify(getServices().packageManagerInternal)
                 .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, UserHandle.USER_SYSTEM);
+        verify(getServices().subscriptionManager).setSubscriptionUserHandle(0, null);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index ac1667d..c739969 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -239,6 +239,8 @@
                 return mMockSystemServices.locationManager;
             case Context.ROLE_SERVICE:
                 return mMockSystemServices.roleManager;
+            case Context.TELEPHONY_SUBSCRIPTION_SERVICE:
+                return mMockSystemServices.subscriptionManager;
         }
         throw new UnsupportedOperationException();
     }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index cec9d50..2a6a979 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -63,6 +63,7 @@
 import android.permission.IPermissionManager;
 import android.provider.Settings;
 import android.security.KeyChain;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
@@ -135,6 +136,7 @@
     public final DevicePolicyManager devicePolicyManager;
     public final LocationManager locationManager;
     public final RoleManager roleManager;
+    public final SubscriptionManager subscriptionManager;
     /** Note this is a partial mock, not a real mock. */
     public final PackageManager packageManager;
     public final BuildMock buildMock = new BuildMock();
@@ -188,6 +190,7 @@
         devicePolicyManager = mock(DevicePolicyManager.class);
         locationManager = mock(LocationManager.class);
         roleManager = realContext.getSystemService(RoleManager.class);
+        subscriptionManager = mock(SubscriptionManager.class);
 
         // Package manager is huge, so we use a partial mock instead.
         packageManager = spy(realContext.getPackageManager());
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index 4b417ba..a857238 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -20,6 +20,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -44,7 +45,6 @@
 import android.app.time.UnixEpochTime;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.TelephonyTimeSuggestion;
-import android.app.timedetector.TimePoint;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.HandlerThread;
@@ -385,8 +385,8 @@
                 1234L, 54321L, 999, InetSocketAddress.createUnresolved("test.timeserver", 123));
         when(mMockNtpTrustedTime.getCachedTimeResult())
                 .thenReturn(latestNetworkTime);
-        TimePoint expected = new TimePoint(latestNetworkTime.getTimeMillis(),
-                latestNetworkTime.getElapsedRealtimeMillis());
+        UnixEpochTime expected = new UnixEpochTime(
+                latestNetworkTime.getElapsedRealtimeMillis(), latestNetworkTime.getTimeMillis());
         assertEquals(expected, mTimeDetectorService.latestNetworkTime());
     }
 
@@ -397,6 +397,25 @@
     }
 
     @Test
+    public void testGetLatestNetworkSuggestion() {
+        NtpTrustedTime.TimeResult latestNetworkTime = new NtpTrustedTime.TimeResult(
+                1234L, 54321L, 999, InetSocketAddress.createUnresolved("test.timeserver", 123));
+        when(mMockNtpTrustedTime.getCachedTimeResult())
+                .thenReturn(latestNetworkTime);
+        UnixEpochTime expectedUnixEpochTime = new UnixEpochTime(
+                latestNetworkTime.getElapsedRealtimeMillis(), latestNetworkTime.getTimeMillis());
+        NetworkTimeSuggestion expected = new NetworkTimeSuggestion(
+                expectedUnixEpochTime, latestNetworkTime.getUncertaintyMillis());
+        assertEquals(expected, mTimeDetectorService.getLatestNetworkSuggestion());
+    }
+
+    @Test
+    public void testGetLatestNetworkSuggestion_noTimeAvailable() {
+        when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(null);
+        assertNull(mTimeDetectorService.getLatestNetworkSuggestion());
+    }
+
+    @Test
     public void testGetTimeState() {
         doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
         TimeState fakeState = new TimeState(new UnixEpochTime(12345L, 98765L), true);