Merge "Reset mServedView when failing to start input by not target window"
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index aec60f2..b2bd8d7 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -266,6 +266,27 @@
         }
     }
 
+    /** Tests switching to an uninitialized user with wait times between iterations. */
+    @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+    public void switchUser_realistic() throws Exception {
+        while (mRunner.keepRunning()) {
+            mRunner.pauseTiming();
+            final int startUser = ActivityManager.getCurrentUser();
+            final int userId = createUserNoFlags();
+            waitCoolDownPeriod();
+            Log.d(TAG, "Starting timer");
+            mRunner.resumeTiming();
+
+            switchUser(userId);
+
+            mRunner.pauseTiming();
+            Log.d(TAG, "Stopping timer");
+            switchUserNoCheck(startUser);
+            removeUser(userId);
+            mRunner.resumeTimingForNextIteration();
+        }
+    }
+
     /** Tests switching to a previously-started, but no-longer-running, user. */
     @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
     public void switchUser_stopped() throws RemoteException {
@@ -286,6 +307,30 @@
         }
     }
 
+    /** Tests switching to a previously-started, but no-longer-running, user with wait
+     * times between iterations */
+    @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+    public void switchUser_stopped_realistic() throws RemoteException {
+        final int startUser = ActivityManager.getCurrentUser();
+        final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ true);
+        while (mRunner.keepRunning()) {
+            mRunner.pauseTiming();
+            waitCoolDownPeriod();
+            Log.d(TAG, "Starting timer");
+            mRunner.resumeTiming();
+
+            switchUser(testUser);
+
+            mRunner.pauseTiming();
+            Log.d(TAG, "Stopping timer");
+            switchUserNoCheck(startUser);
+            stopUserAfterWaitingForBroadcastIdle(testUser, true);
+            attestFalse("Failed to stop user " + testUser, mAm.isUserRunning(testUser));
+            mRunner.resumeTimingForNextIteration();
+        }
+        removeUser(testUser);
+    }
+
     /** Tests switching to an already-created already-running non-owner background user. */
     @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
     public void switchUser_running() throws RemoteException {
@@ -306,6 +351,29 @@
         }
     }
 
+    /** Tests switching to an already-created already-running non-owner background user, with wait
+     * times between iterations */
+    @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+    public void switchUser_running_realistic() throws RemoteException {
+        final int startUser = ActivityManager.getCurrentUser();
+        final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ false);
+        while (mRunner.keepRunning()) {
+            mRunner.pauseTiming();
+            waitCoolDownPeriod();
+            Log.d(TAG, "Starting timer");
+            mRunner.resumeTiming();
+
+            switchUser(testUser);
+
+            mRunner.pauseTiming();
+            Log.d(TAG, "Stopping timer");
+            waitForBroadcastIdle();
+            switchUserNoCheck(startUser);
+            mRunner.resumeTimingForNextIteration();
+        }
+        removeUser(testUser);
+    }
+
     /** Tests stopping a background user. */
     @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
     public void stopUser() throws RemoteException {
@@ -902,4 +970,18 @@
     private void waitForBroadcastIdle() {
         ShellHelper.runShellCommand("am wait-for-broadcast-idle");
     }
+
+    private void sleep(long ms) {
+        try {
+            Thread.sleep(ms);
+        } catch (InterruptedException e) {
+            // Ignore
+        }
+    }
+
+    private void waitCoolDownPeriod() {
+        final int tenSeconds = 1000 * 10;
+        waitForBroadcastIdle();
+        sleep(tenSeconds);
+    }
 }
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index 659db9f..c9981da 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -400,6 +400,8 @@
      * Returns {@code true} if the app currently holds the
      * {@link android.Manifest.permission#RUN_LONG_JOBS} permission, allowing it to run long jobs.
      * @hide
+     * TODO(255371817): consider exposing this to apps who could call
+     * {@link #scheduleAsPackage(JobInfo, String, int, String)}
      */
     public boolean hasRunLongJobsPermission(@NonNull String packageName, @UserIdInt int userId) {
         return false;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 1147e07..c032513 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -3660,11 +3660,8 @@
             return canPersist;
         }
 
-        private void validateJob(JobInfo job, int callingUid) {
-            validateJob(job, callingUid, null);
-        }
-
-        private void validateJob(JobInfo job, int callingUid, @Nullable JobWorkItem jobWorkItem) {
+        private int validateJob(@NonNull JobInfo job, int callingUid, int sourceUserId,
+                @Nullable String sourcePkgName, @Nullable JobWorkItem jobWorkItem) {
             final boolean rejectNegativeNetworkEstimates = CompatChanges.isChangeEnabled(
                             JobInfo.REJECT_NEGATIVE_NETWORK_ESTIMATES, callingUid);
             job.enforceValidity(
@@ -3684,6 +3681,37 @@
                             + " FLAG_EXEMPT_FROM_APP_STANDBY. Job=" + job);
                 }
             }
+            if (job.isUserInitiated()) {
+                int sourceUid = -1;
+                if (sourceUserId != -1 && sourcePkgName != null) {
+                    try {
+                        sourceUid = AppGlobals.getPackageManager().getPackageUid(
+                                sourcePkgName, 0, sourceUserId);
+                    } catch (RemoteException ex) {
+                        // Can't happen, PackageManager runs in the same process.
+                    }
+                }
+                // We aim to check the permission of both the source and calling app so that apps
+                // don't attempt to bypass the permission by using other apps to do the work.
+                if (sourceUid != -1) {
+                    // Check the permission of the source app.
+                    final int sourceResult =
+                            validateRunLongJobsPermission(sourceUid, sourcePkgName);
+                    if (sourceResult != JobScheduler.RESULT_SUCCESS) {
+                        return sourceResult;
+                    }
+                }
+                final String callingPkgName = job.getService().getPackageName();
+                if (callingUid != sourceUid || !callingPkgName.equals(sourcePkgName)) {
+                    // Source app is different from calling app. Make sure the calling app also has
+                    // the permission.
+                    final int callingResult =
+                            validateRunLongJobsPermission(callingUid, callingPkgName);
+                    if (callingResult != JobScheduler.RESULT_SUCCESS) {
+                        return callingResult;
+                    }
+                }
+            }
             if (jobWorkItem != null) {
                 jobWorkItem.enforceValidity(rejectNegativeNetworkEstimates);
                 if (jobWorkItem.getEstimatedNetworkDownloadBytes() != JobInfo.NETWORK_BYTES_UNKNOWN
@@ -3704,6 +3732,19 @@
                     }
                 }
             }
+            return JobScheduler.RESULT_SUCCESS;
+        }
+
+        private int validateRunLongJobsPermission(int uid, String packageName) {
+            final int state = getRunLongJobsPermissionState(uid, packageName);
+            if (state == PermissionChecker.PERMISSION_HARD_DENIED) {
+                throw new SecurityException(android.Manifest.permission.RUN_LONG_JOBS
+                        + " required to schedule user-initiated jobs.");
+            }
+            if (state == PermissionChecker.PERMISSION_SOFT_DENIED) {
+                return JobScheduler.RESULT_FAILURE;
+            }
+            return JobScheduler.RESULT_SUCCESS;
         }
 
         // IJobScheduler implementation
@@ -3724,7 +3765,10 @@
                 }
             }
 
-            validateJob(job, uid);
+            final int result = validateJob(job, uid, -1, null, null);
+            if (result != JobScheduler.RESULT_SUCCESS) {
+                return result;
+            }
 
             final long ident = Binder.clearCallingIdentity();
             try {
@@ -3752,7 +3796,10 @@
                 throw new NullPointerException("work is null");
             }
 
-            validateJob(job, uid, work);
+            final int result = validateJob(job, uid, -1, null, work);
+            if (result != JobScheduler.RESULT_SUCCESS) {
+                return result;
+            }
 
             final long ident = Binder.clearCallingIdentity();
             try {
@@ -3783,7 +3830,10 @@
                         + " not permitted to schedule jobs for other apps");
             }
 
-            validateJob(job, callerUid);
+            int result = validateJob(job, callerUid, userId, packageName, null);
+            if (result != JobScheduler.RESULT_SUCCESS) {
+                return result;
+            }
 
             final long ident = Binder.clearCallingIdentity();
             try {
@@ -4257,11 +4307,16 @@
         return 0;
     }
 
+    /** Returns true if both the appop and permission are granted. */
     private boolean checkRunLongJobsPermission(int packageUid, String packageName) {
-        // Returns true if both the appop and permission are granted.
+        return getRunLongJobsPermissionState(packageUid, packageName)
+                == PermissionChecker.PERMISSION_GRANTED;
+    }
+
+    private int getRunLongJobsPermissionState(int packageUid, String packageName) {
         return PermissionChecker.checkPermissionForPreflight(getTestableContext(),
                 android.Manifest.permission.RUN_LONG_JOBS, PermissionChecker.PID_UNKNOWN,
-                packageUid, packageName) == PermissionChecker.PERMISSION_GRANTED;
+                packageUid, packageName);
     }
 
     @VisibleForTesting
diff --git a/core/api/current.txt b/core/api/current.txt
index 0aed10c..fc4e580 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -39361,6 +39361,8 @@
     method public final boolean onUnbind(@NonNull android.content.Intent);
     method public abstract void performControlAction(@NonNull String, @NonNull android.service.controls.actions.ControlAction, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method public static void requestAddControl(@NonNull android.content.Context, @NonNull android.content.ComponentName, @NonNull android.service.controls.Control);
+    field public static final String EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS = "android.service.controls.extra.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS";
+    field public static final String META_DATA_PANEL_ACTIVITY = "android.service.controls.META_DATA_PANEL_ACTIVITY";
     field public static final String SERVICE_CONTROLS = "android.service.controls.ControlsProviderService";
     field @NonNull public static final String TAG = "ControlsProviderService";
   }
@@ -41038,6 +41040,32 @@
     field public static final int RTT_MODE_VCO = 3; // 0x3
   }
 
+  public final class CallAttributes implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.net.Uri getAddress();
+    method public int getCallCapabilities();
+    method public int getCallType();
+    method public int getDirection();
+    method @NonNull public CharSequence getDisplayName();
+    method @NonNull public android.telecom.PhoneAccountHandle getPhoneAccountHandle();
+    method public void writeToParcel(@Nullable android.os.Parcel, int);
+    field public static final int AUDIO_CALL = 1; // 0x1
+    field @NonNull public static final android.os.Parcelable.Creator<android.telecom.CallAttributes> CREATOR;
+    field public static final int DIRECTION_INCOMING = 1; // 0x1
+    field public static final int DIRECTION_OUTGOING = 2; // 0x2
+    field public static final int SUPPORTS_SET_INACTIVE = 2; // 0x2
+    field public static final int SUPPORTS_STREAM = 4; // 0x4
+    field public static final int SUPPORTS_TRANSFER = 8; // 0x8
+    field public static final int VIDEO_CALL = 2; // 0x2
+  }
+
+  public static final class CallAttributes.Builder {
+    ctor public CallAttributes.Builder(@NonNull android.telecom.PhoneAccountHandle, int, @NonNull CharSequence, @NonNull android.net.Uri);
+    method @NonNull public android.telecom.CallAttributes build();
+    method @NonNull public android.telecom.CallAttributes.Builder setCallCapabilities(int);
+    method @NonNull public android.telecom.CallAttributes.Builder setCallType(int);
+  }
+
   public final class CallAudioState implements android.os.Parcelable {
     ctor public CallAudioState(boolean, int, int);
     method public static String audioRouteToString(int);
@@ -41056,6 +41084,15 @@
     field public static final int ROUTE_WIRED_OR_EARPIECE = 5; // 0x5
   }
 
+  public final class CallControl implements java.lang.AutoCloseable {
+    method public void close();
+    method public void disconnect(@NonNull android.telecom.DisconnectCause, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
+    method @NonNull public android.os.ParcelUuid getCallId();
+    method public void rejectCall(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
+    method public void setActive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
+    method public void setInactive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
+  }
+
   public final class CallEndpoint implements android.os.Parcelable {
     ctor public CallEndpoint(@NonNull CharSequence, int, @NonNull android.os.ParcelUuid);
     method public int describeContents();
@@ -41086,6 +41123,30 @@
     field public static final int ERROR_UNSPECIFIED = 4; // 0x4
   }
 
+  public interface CallEventCallback {
+    method public void onAnswer(int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method public void onCallAudioStateChanged(@NonNull android.telecom.CallAudioState);
+    method public void onDisconnect(@NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method public void onReject(@NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method public void onSetActive(@NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method public void onSetInactive(@NonNull java.util.function.Consumer<java.lang.Boolean>);
+  }
+
+  public final class CallException extends java.lang.RuntimeException implements android.os.Parcelable {
+    ctor public CallException(@Nullable String);
+    ctor public CallException(@Nullable String, int);
+    method public int describeContents();
+    method public int getCode();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int CODE_CALL_CANNOT_BE_SET_TO_ACTIVE = 4; // 0x4
+    field public static final int CODE_CALL_IS_NOT_BEING_TRACKED = 3; // 0x3
+    field public static final int CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME = 5; // 0x5
+    field public static final int CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL = 2; // 0x2
+    field public static final int CODE_ERROR_UNKNOWN = 1; // 0x1
+    field public static final int CODE_OPERATION_TIMED_OUT = 6; // 0x6
+    field @NonNull public static final android.os.Parcelable.Creator<android.telecom.CallException> CREATOR;
+  }
+
   public abstract class CallRedirectionService extends android.app.Service {
     ctor public CallRedirectionService();
     method public final void cancelCall();
@@ -41595,6 +41656,7 @@
     field public static final int CAPABILITY_RTT = 4096; // 0x1000
     field public static final int CAPABILITY_SELF_MANAGED = 2048; // 0x800
     field public static final int CAPABILITY_SIM_SUBSCRIPTION = 4; // 0x4
+    field public static final int CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS = 262144; // 0x40000
     field public static final int CAPABILITY_SUPPORTS_VIDEO_CALLING = 1024; // 0x400
     field public static final int CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS = 65536; // 0x10000
     field public static final int CAPABILITY_VIDEO_CALLING = 8; // 0x8
@@ -41787,6 +41849,7 @@
     method public void acceptHandover(android.net.Uri, int, android.telecom.PhoneAccountHandle);
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ANSWER_PHONE_CALLS, android.Manifest.permission.MODIFY_PHONE_STATE}) public void acceptRingingCall();
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ANSWER_PHONE_CALLS, android.Manifest.permission.MODIFY_PHONE_STATE}) public void acceptRingingCall(int);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_OWN_CALLS) public void addCall(@NonNull android.telecom.CallAttributes, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telecom.CallControl,android.telecom.CallException>, @NonNull android.telecom.CallEventCallback);
     method public void addNewIncomingCall(android.telecom.PhoneAccountHandle, android.os.Bundle);
     method public void addNewIncomingConference(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.os.Bundle);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void cancelMissedCallsNotification();
@@ -54259,7 +54322,7 @@
   }
 
   public abstract class HandwritingGesture {
-    method @Nullable public String getFallbackText();
+    method @Nullable public final String getFallbackText();
     field public static final int GESTURE_TYPE_DELETE = 4; // 0x4
     field public static final int GESTURE_TYPE_DELETE_RANGE = 64; // 0x40
     field public static final int GESTURE_TYPE_INSERT = 2; // 0x2
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index fd1ee27..adbbe61 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -478,6 +478,10 @@
 
 package android.telephony {
 
+  public class CarrierConfigManager {
+    field public static final String KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT = "min_udp_port_4500_nat_timeout_sec_int";
+  }
+
   public abstract class CellSignalStrength {
     method public static int getNumSignalStrengthLevels();
   }
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 7105a4f..1eed2d4 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3432,7 +3432,7 @@
     method @NonNull public String getTargetPackageName();
     method public int getUserId();
     method public boolean isEnabled();
-    method public void writeToParcel(android.os.Parcel, int);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.content.om.OverlayInfo> CREATOR;
   }
 
@@ -5817,6 +5817,8 @@
     field public static final int DATA_STATUS_DISABLED_CONTAMINANT = 4; // 0x4
     field public static final int DATA_STATUS_DISABLED_DEBUG = 32; // 0x20
     field public static final int DATA_STATUS_DISABLED_DOCK = 8; // 0x8
+    field public static final int DATA_STATUS_DISABLED_DOCK_DEVICE_MODE = 128; // 0x80
+    field public static final int DATA_STATUS_DISABLED_DOCK_HOST_MODE = 64; // 0x40
     field public static final int DATA_STATUS_DISABLED_FORCE = 16; // 0x10
     field public static final int DATA_STATUS_DISABLED_OVERHEAT = 2; // 0x2
     field public static final int DATA_STATUS_ENABLED = 1; // 0x1
@@ -6592,6 +6594,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int[] getSupportedSystemUsages();
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
     method public boolean isAudioServerRunning();
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean isBluetoothVariableLatencyEnabled();
     method public boolean isHdmiSystemAudioSupported();
     method @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public boolean isPstnCallAudioInterceptable();
     method @RequiresPermission(android.Manifest.permission.ACCESS_ULTRASOUND) public boolean isUltrasoundSupported();
@@ -6610,6 +6613,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setActiveAssistantServiceUids(@NonNull int[]);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo, @IntRange(from=0) long);
     method public void setAudioServerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.AudioServerStateCallback);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setBluetoothVariableLatencyEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setFocusRequestResult(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDeviceForCapturePreset(int, @NonNull android.media.AudioDeviceAttributes);
@@ -6617,6 +6621,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDevicesForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull java.util.List<android.media.AudioDeviceAttributes>);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setSupportedSystemUsages(@NonNull int[]);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setVolumeIndexForAttributes(@NonNull android.media.AudioAttributes, int, int);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean supportsBluetoothVariableLatency();
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicyAsync(@NonNull android.media.audiopolicy.AudioPolicy);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterMuteAwaitConnectionCallback(@NonNull android.media.AudioManager.MuteAwaitConnectionCallback);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 82cc3fd..5be98bf 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3232,7 +3232,7 @@
 package android.view.inputmethod {
 
   public abstract class HandwritingGesture {
-    method public int getGestureType();
+    method public final int getGestureType();
   }
 
   public final class InlineSuggestion implements android.os.Parcelable {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index d1772e37..da88f4b 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1015,6 +1015,8 @@
 
     private ComponentCallbacksController mCallbacksController;
 
+    @Nullable private IVoiceInteractionManagerService mVoiceInteractionManagerService;
+
     private final WindowControllerCallback mWindowControllerCallback =
             new WindowControllerCallback() {
         /**
@@ -1624,18 +1626,17 @@
 
     private void notifyVoiceInteractionManagerServiceActivityEvent(
             @VoiceInteractionSession.VoiceInteractionActivityEventType int type) {
-
-        final IVoiceInteractionManagerService service =
-                IVoiceInteractionManagerService.Stub.asInterface(
-                        ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
-        if (service == null) {
-            Log.w(TAG, "notifyVoiceInteractionManagerServiceActivityEvent: Can not get "
-                    + "VoiceInteractionManagerService");
-            return;
+        if (mVoiceInteractionManagerService == null) {
+            mVoiceInteractionManagerService = IVoiceInteractionManagerService.Stub.asInterface(
+                    ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
+            if (mVoiceInteractionManagerService == null) {
+                Log.w(TAG, "notifyVoiceInteractionManagerServiceActivityEvent: Can not get "
+                        + "VoiceInteractionManagerService");
+                return;
+            }
         }
-
         try {
-            service.notifyActivityEventChanged(mToken, type);
+            mVoiceInteractionManagerService.notifyActivityEventChanged(mToken, type);
         } catch (RemoteException e) {
             // Empty
         }
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index e655209..1187459 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -115,6 +115,9 @@
     private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
             new RectF(0, 0, 1, 1);
 
+    /** Temporary feature flag for project b/197814683 */
+    private final boolean mLockscreenLiveWallpaper;
+
     /** {@hide} */
     private static final String PROP_WALLPAPER = "ro.config.wallpaper";
     /** {@hide} */
@@ -750,6 +753,8 @@
         mWcgEnabled = context.getResources().getConfiguration().isScreenWideColorGamut()
                 && context.getResources().getBoolean(R.bool.config_enableWcgMode);
         mCmProxy = new ColorManagementProxy(context);
+        mLockscreenLiveWallpaper = context.getResources()
+                .getBoolean(R.bool.config_independentLockscreenLiveWallpaper);
     }
 
     // no-op constructor called just by DisabledWallpaperManager
@@ -757,6 +762,7 @@
         mContext = null;
         mCmProxy = null;
         mWcgEnabled = false;
+        mLockscreenLiveWallpaper = false;
     }
 
     /**
@@ -774,6 +780,15 @@
     }
 
     /**
+     * Temporary method for project b/197814683.
+     * @return true if the lockscreen wallpaper always uses a wallpaperService, not a static image
+     * @hide
+     */
+    public boolean isLockscreenLiveWallpaperEnabled() {
+        return mLockscreenLiveWallpaper;
+    }
+
+    /**
      * Indicate whether wcg (Wide Color Gamut) should be enabled.
      * <p>
      * Some devices lack of capability of mixed color spaces composition,
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 088ac06..8561018 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -57,6 +57,7 @@
 import android.hardware.input.VirtualNavigationTouchpadConfig;
 import android.hardware.input.VirtualTouchscreen;
 import android.hardware.input.VirtualTouchscreenConfig;
+import android.media.AudioManager;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -303,6 +304,22 @@
     }
 
     /**
+     * Requests sound effect to be played on virtual device.
+     *
+     * @see android.media.AudioManager#playSoundEffect(int)
+     *
+     * @param deviceId - id of the virtual audio device
+     * @param effectType the type of sound effect
+     * @hide
+     */
+    public void playSoundEffect(int deviceId, @AudioManager.SystemSoundEffect int effectType) {
+        //TODO - handle requests to play sound effects by custom callbacks or SoundPool asociated
+        // with device session id.
+        // For now, this is intentionally left empty and effectively disables sound effects for
+        // virtual devices with custom device audio policy.
+    }
+
+    /**
      * A virtual device has its own virtual display, audio output, microphone, and camera etc. The
      * creator of a virtual device can take the output from the virtual display and stream it over
      * to another device, and inject input events that are received from the remote device.
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 597b0f5..d4a0a08 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -758,7 +758,7 @@
          */
         @NonNull
         public Builder setAudioPlaybackSessionId(int playbackSessionId) {
-            if (playbackSessionId != AUDIO_SESSION_ID_GENERATE || playbackSessionId < 0) {
+            if (playbackSessionId < 0) {
                 throw new IllegalArgumentException("Invalid playback audio session id");
             }
             mAudioPlaybackSessionId = playbackSessionId;
@@ -782,7 +782,7 @@
          */
         @NonNull
         public Builder setAudioRecordingSessionId(int recordingSessionId) {
-            if (recordingSessionId != AUDIO_SESSION_ID_GENERATE || recordingSessionId < 0) {
+            if (recordingSessionId < 0) {
                 throw new IllegalArgumentException("Invalid recording audio session id");
             }
             mAudioRecordingSessionId = recordingSessionId;
diff --git a/core/java/android/content/om/OverlayIdentifier.java b/core/java/android/content/om/OverlayIdentifier.java
index 454d0d1..a43091e 100644
--- a/core/java/android/content/om/OverlayIdentifier.java
+++ b/core/java/android/content/om/OverlayIdentifier.java
@@ -27,32 +27,44 @@
 
 /**
  * A key used to uniquely identify a Runtime Resource Overlay (RRO).
+ * <!-- For applications -->
  *
- * An overlay always belongs to a package and may optionally have a name associated with it.
- * The name helps uniquely identify a particular overlay within a package.
- * @hide
+ * <p>An overlay always belongs to a package and have a mandatory name associated with it. The name
+ * helps uniquely identify a particular overlay within a package.
+ *
+ * <!-- For OverlayManagerService, it isn't public part and hidden by HTML comment. -->
+ * <!--
+ * <p>An overlay always belongs to a package and may optionally have a name associated with it. The
+ * name helps uniquely identify a particular overlay within a package.
+ * -->
+ *
+ * @see OverlayInfo#getOverlayIdentifier()
+ * @see OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier)
  */
 /** @hide */
 @DataClass(genConstructor = false, genBuilder = false, genHiddenBuilder = false,
         genEqualsHashCode = true, genToString = false)
-public class OverlayIdentifier implements Parcelable  {
+public final class OverlayIdentifier implements Parcelable {
     /**
      * The package name containing or owning the overlay.
+     *
+     * @hide
      */
-    @Nullable
-    private final String mPackageName;
+    @Nullable private final String mPackageName;
 
     /**
      * The unique name within the package of the overlay.
+     *
+     * @hide
      */
-    @Nullable
-    private final String mOverlayName;
+    @Nullable private final String mOverlayName;
 
     /**
      * Creates an identifier from a package and unique name within the package.
      *
      * @param packageName the package containing or owning the overlay
      * @param overlayName the unique name of the overlay within the package
+     * @hide
      */
     public OverlayIdentifier(@NonNull String packageName, @Nullable String overlayName) {
         mPackageName = packageName;
@@ -63,18 +75,24 @@
      * Creates an identifier for an overlay without a name.
      *
      * @param packageName the package containing or owning the overlay
+     * @hide
      */
     public OverlayIdentifier(@NonNull String packageName) {
         mPackageName = packageName;
         mOverlayName = null;
     }
 
+    /**
+     * {@inheritDoc}
+     * @hide
+     */
     @Override
     public String toString() {
         return mOverlayName == null ? mPackageName : mPackageName + ":" + mOverlayName;
     }
 
     /** @hide */
+    @NonNull
     public static OverlayIdentifier fromString(@NonNull String text) {
         final String[] parts = text.split(":", 2);
         if (parts.length == 2) {
@@ -86,7 +104,7 @@
 
 
 
-    // Code below generated by codegen v1.0.22.
+    // Code below generated by codegen v1.0.23.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
@@ -101,6 +119,8 @@
 
     /**
      * Retrieves the package name containing or owning the overlay.
+     *
+     * @hide
      */
     @DataClass.Generated.Member
     public @Nullable String getPackageName() {
@@ -109,12 +129,18 @@
 
     /**
      * Retrieves the unique name within the package of the overlay.
+     *
+     * @hide
      */
     @DataClass.Generated.Member
     public @Nullable String getOverlayName() {
         return mOverlayName;
     }
 
+    /**
+     * {@inheritDoc}
+     * @hide
+     */
     @Override
     @DataClass.Generated.Member
     public boolean equals(@Nullable Object o) {
@@ -132,6 +158,10 @@
                 && Objects.equals(mOverlayName, that.mOverlayName);
     }
 
+    /**
+     * {@inheritDoc}
+     * @hide
+     */
     @Override
     @DataClass.Generated.Member
     public int hashCode() {
@@ -144,6 +174,10 @@
         return _hash;
     }
 
+    /**
+     * {@inheritDoc}
+     * @hide
+     */
     @Override
     @DataClass.Generated.Member
     public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -158,6 +192,10 @@
         if (mOverlayName != null) dest.writeString(mOverlayName);
     }
 
+    /**
+     * {@inheritDoc}
+     * @hide
+     */
     @Override
     @DataClass.Generated.Member
     public int describeContents() { return 0; }
@@ -165,7 +203,7 @@
     /** @hide */
     @SuppressWarnings({"unchecked", "RedundantCast"})
     @DataClass.Generated.Member
-    protected OverlayIdentifier(@NonNull Parcel in) {
+    /* package-private */ OverlayIdentifier(@NonNull Parcel in) {
         // You can override field unparcelling by defining methods like:
         // static FieldType unparcelFieldName(Parcel in) { ... }
 
@@ -194,10 +232,10 @@
     };
 
     @DataClass.Generated(
-            time = 1612482438728L,
-            codegenVersion = "1.0.22",
+            time = 1670404485646L,
+            codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/om/OverlayIdentifier.java",
-            inputSignatures = "private final @android.annotation.Nullable java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mOverlayName\npublic @java.lang.Override java.lang.String toString()\npublic static  android.content.om.OverlayIdentifier fromString(java.lang.String)\nclass OverlayIdentifier extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genHiddenBuilder=false, genEqualsHashCode=true, genToString=false)")
+            inputSignatures = "private final @android.annotation.Nullable java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mOverlayName\npublic @java.lang.Override java.lang.String toString()\npublic static @android.annotation.NonNull android.content.om.OverlayIdentifier fromString(java.lang.String)\nclass OverlayIdentifier extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genHiddenBuilder=false, genEqualsHashCode=true, genToString=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java
index a470de2..a81d16ab 100644
--- a/core/java/android/content/om/OverlayInfo.java
+++ b/core/java/android/content/om/OverlayInfo.java
@@ -33,9 +33,19 @@
 import java.util.Objects;
 
 /**
+ * An immutable information about an overlay.
+ *
+ * <p>Applications calling {@link OverlayManager#getOverlayInfosForTarget(String)} get the
+ * information list of the registered overlays. Each element in the list presents the information of
+ * the particular overlay.
+ *
+ * <!-- For OverlayManagerService, it isn't public part and hidden by HTML comment. -->
+ * <!--
  * Immutable overlay information about a package. All PackageInfos that
  * represent an overlay package will have a corresponding OverlayInfo.
+ * -->
  *
+ * @see OverlayManager#getOverlayInfosForTarget(String)
  * @hide
  */
 @SystemApi
@@ -174,14 +184,14 @@
      *
      * @hide
      */
-    public final String targetOverlayableName;
+    @Nullable public final String targetOverlayableName;
 
     /**
      * Category of the overlay package
      *
      * @hide
      */
-    public final String category;
+    @Nullable public final String category;
 
     /**
      * Full path to the base APK for this overlay package
@@ -272,7 +282,7 @@
     }
 
     /** @hide */
-    public OverlayInfo(Parcel source) {
+    public OverlayInfo(@NonNull Parcel source) {
         packageName = source.readString();
         overlayName = source.readString();
         targetPackageName = source.readString();
@@ -299,7 +309,9 @@
     }
 
     /**
-     * {@inheritDoc}
+     * Get the overlay name from the registered fabricated overlay.
+     *
+     * @return the overlay name
      * @hide
      */
     @Override
@@ -309,7 +321,9 @@
     }
 
     /**
-     * {@inheritDoc}
+     * Returns the name of the target overlaid package.
+     *
+     * @return the target package name
      * @hide
      */
     @Override
@@ -342,11 +356,13 @@
     }
 
     /**
-     * {@inheritDoc}
+     * Return the target overlayable name.
+     *
+     * @return the name of the target overlayable resources set
      * @hide
      */
-    @Override
     @SystemApi
+    @Override
     @Nullable
     public String getTargetOverlayableName() {
         return targetOverlayableName;
@@ -366,12 +382,18 @@
      *
      * @hide
      */
+    @NonNull
     public String getBaseCodePath() {
         return baseCodePath;
     }
 
     /**
-     * {@inheritDoc}
+     * Get the unique identifier from the overlay information.
+     *
+     * <p>The return value of this function can be used to unregister the related overlay.
+     *
+     * @return an identifier representing the current overlay.
+     * @see OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier)
      * @hide
      */
     @Override
@@ -415,7 +437,7 @@
     }
 
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString(packageName);
         dest.writeString(overlayName);
         dest.writeString(targetPackageName);
@@ -429,7 +451,7 @@
         dest.writeBoolean(isFabricated);
     }
 
-    public static final @android.annotation.NonNull Parcelable.Creator<OverlayInfo> CREATOR =
+    public static final @NonNull Parcelable.Creator<OverlayInfo> CREATOR =
             new Parcelable.Creator<OverlayInfo>() {
         @Override
         public OverlayInfo createFromParcel(Parcel source) {
@@ -492,6 +514,11 @@
         }
     }
 
+    /**
+     * {@inheritDoc}
+     *
+     * @hide
+     */
     @Override
     public int hashCode() {
         final int prime = 31;
@@ -508,6 +535,11 @@
         return result;
     }
 
+    /**
+     * {@inheritDoc}
+     *
+     * @hide
+     */
     @Override
     public boolean equals(@Nullable Object obj) {
         if (this == obj) {
@@ -547,6 +579,11 @@
         return true;
     }
 
+    /**
+     * {@inheritDoc}
+     *
+     * @hide
+     */
     @NonNull
     @Override
     public String toString() {
diff --git a/core/java/android/credentials/ui/RequestInfo.java b/core/java/android/credentials/ui/RequestInfo.java
index c3937b6..5eaf2a0 100644
--- a/core/java/android/credentials/ui/RequestInfo.java
+++ b/core/java/android/credentials/ui/RequestInfo.java
@@ -44,6 +44,8 @@
     public static final @NonNull String EXTRA_REQUEST_INFO =
             "android.credentials.ui.extra.REQUEST_INFO";
 
+    /** Type value for any request that does not require UI. */
+    public static final @NonNull String TYPE_UNDEFINED = "android.credentials.ui.TYPE_UNDEFINED";
     /** Type value for an executeGetCredential request. */
     public static final @NonNull String TYPE_GET = "android.credentials.ui.TYPE_GET";
     /** Type value for an executeCreateCredential request. */
diff --git a/core/java/android/hardware/usb/UsbPort.java b/core/java/android/hardware/usb/UsbPort.java
index e0f9cad..cdd67b7 100644
--- a/core/java/android/hardware/usb/UsbPort.java
+++ b/core/java/android/hardware/usb/UsbPort.java
@@ -46,6 +46,8 @@
 import static android.hardware.usb.UsbPortStatus.DATA_STATUS_DISABLED_DOCK;
 import static android.hardware.usb.UsbPortStatus.DATA_STATUS_DISABLED_FORCE;
 import static android.hardware.usb.UsbPortStatus.DATA_STATUS_DISABLED_DEBUG;
+import static android.hardware.usb.UsbPortStatus.DATA_STATUS_DISABLED_DOCK_HOST_MODE;
+import static android.hardware.usb.UsbPortStatus.DATA_STATUS_DISABLED_DOCK_DEVICE_MODE;
 import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY;
 import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_BC_1_2;
 import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_MISSING_RP;
@@ -676,6 +678,15 @@
             statusString.append("disabled-debug, ");
         }
 
+        if ((usbDataStatus & DATA_STATUS_DISABLED_DOCK_HOST_MODE) ==
+            DATA_STATUS_DISABLED_DOCK_HOST_MODE) {
+            statusString.append("disabled-host-dock, ");
+        }
+
+        if ((usbDataStatus & DATA_STATUS_DISABLED_DOCK_DEVICE_MODE) ==
+            DATA_STATUS_DISABLED_DOCK_DEVICE_MODE) {
+            statusString.append("disabled-device-dock, ");
+        }
         return statusString.toString().replaceAll(", $", "");
     }
 
diff --git a/core/java/android/hardware/usb/UsbPortStatus.java b/core/java/android/hardware/usb/UsbPortStatus.java
index ed3e40d..e61703d 100644
--- a/core/java/android/hardware/usb/UsbPortStatus.java
+++ b/core/java/android/hardware/usb/UsbPortStatus.java
@@ -219,7 +219,11 @@
     public static final int DATA_STATUS_DISABLED_CONTAMINANT = 1 << 2;
 
     /**
-     * USB data is disabled due to docking event.
+     * This flag indicates that some or all data modes are disabled
+     * due to docking event, and the specific sub-statuses viz.,
+     * {@link #DATA_STATUS_DISABLED_DOCK_HOST_MODE},
+     * {@link #DATA_STATUS_DISABLED_DOCK_DEVICE_MODE}
+     * can be checked for individual modes.
      */
     public static final int DATA_STATUS_DISABLED_DOCK = 1 << 3;
 
@@ -235,6 +239,18 @@
     public static final int DATA_STATUS_DISABLED_DEBUG = 1 << 5;
 
     /**
+     * USB host mode is disabled due to docking event.
+     * {@link #DATA_STATUS_DISABLED_DOCK} will be set as well.
+     */
+    public static final int DATA_STATUS_DISABLED_DOCK_HOST_MODE = 1 << 6;
+
+    /**
+     * USB device mode is disabled due to docking event.
+     * {@link #DATA_STATUS_DISABLED_DOCK} will be set as well.
+     */
+    public static final int DATA_STATUS_DISABLED_DOCK_DEVICE_MODE = 1 << 7;
+
+    /**
      * Unknown whether a power brick is connected.
      */
     public static final int POWER_BRICK_STATUS_UNKNOWN = 0;
@@ -329,6 +345,8 @@
             DATA_STATUS_DISABLED_OVERHEAT,
             DATA_STATUS_DISABLED_CONTAMINANT,
             DATA_STATUS_DISABLED_DOCK,
+            DATA_STATUS_DISABLED_DOCK_HOST_MODE,
+            DATA_STATUS_DISABLED_DOCK_DEVICE_MODE,
             DATA_STATUS_DISABLED_FORCE,
             DATA_STATUS_DISABLED_DEBUG
     })
@@ -357,6 +375,20 @@
         mSupportedRoleCombinations = supportedRoleCombinations;
         mContaminantProtectionStatus = contaminantProtectionStatus;
         mContaminantDetectionStatus = contaminantDetectionStatus;
+
+        // Older implementations that only set the DISABLED_DOCK_MODE will have the other two
+        // set at the HAL interface level, so the "dock mode only" state shouldn't be visible here.
+        // But the semantics are ensured here.
+        int disabledDockModes = (usbDataStatus &
+            (DATA_STATUS_DISABLED_DOCK_HOST_MODE | DATA_STATUS_DISABLED_DOCK_DEVICE_MODE));
+        if (disabledDockModes != 0) {
+            // Set DATA_STATUS_DISABLED_DOCK when one of DATA_STATUS_DISABLED_DOCK_*_MODE is set
+            usbDataStatus |= DATA_STATUS_DISABLED_DOCK;
+        } else {
+            // Clear DATA_STATUS_DISABLED_DOCK when none of DATA_STATUS_DISABLED_DOCK_*_MODE is set
+            usbDataStatus &= ~DATA_STATUS_DISABLED_DOCK;
+        }
+
         mUsbDataStatus = usbDataStatus;
         mPowerTransferLimited = powerTransferLimited;
         mPowerBrickConnectionStatus = powerBrickConnectionStatus;
@@ -472,7 +504,8 @@
      *         {@link #DATA_STATUS_UNKNOWN}, {@link #DATA_STATUS_ENABLED},
      *         {@link #DATA_STATUS_DISABLED_OVERHEAT}, {@link #DATA_STATUS_DISABLED_CONTAMINANT},
      *         {@link #DATA_STATUS_DISABLED_DOCK}, {@link #DATA_STATUS_DISABLED_FORCE},
-     *         {@link #DATA_STATUS_DISABLED_DEBUG}
+     *         {@link #DATA_STATUS_DISABLED_DEBUG}, {@link #DATA_STATUS_DISABLED_DOCK_HOST_MODE},
+     *         {@link #DATA_STATUS_DISABLED_DOCK_DEVICE_MODE}
      */
     public @UsbDataStatus int getUsbDataStatus() {
         return mUsbDataStatus;
diff --git a/core/java/android/service/controls/ControlsProviderService.java b/core/java/android/service/controls/ControlsProviderService.java
index 950c8ac..65e2390 100644
--- a/core/java/android/service/controls/ControlsProviderService.java
+++ b/core/java/android/service/controls/ControlsProviderService.java
@@ -58,27 +58,28 @@
      * Manifest metadata to show a custom embedded activity as part of device controls.
      *
      * The value of this metadata must be the {@link ComponentName} as a string of an activity in
-     * the same package that will be launched as part of a TaskView.
+     * the same package that will be launched embedded in the device controls space.
      *
      * The activity must be exported, enabled and protected by
      * {@link Manifest.permission.BIND_CONTROLS}.
      *
-     * @hide
+     * It is recommended that the activity is declared {@code android:resizeableActivity="true"}.
      */
     public static final String META_DATA_PANEL_ACTIVITY =
             "android.service.controls.META_DATA_PANEL_ACTIVITY";
 
     /**
-     * Boolean extra containing the value of
-     * {@link android.provider.Settings.Secure#LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS}.
+     * Boolean extra containing the value of the setting allowing actions on a locked device.
+     *
+     * This corresponds to the setting that indicates whether the user has
+     * consented to allow actions on devices that declare {@link Control#isAuthRequired()} as
+     * {@code false} when the device is locked.
      *
      * This is passed with the intent when the panel specified by {@link #META_DATA_PANEL_ACTIVITY}
      * is launched.
-     *
-     * @hide
      */
     public static final String EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS =
-            "android.service.controls.extra.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS";
+            "android.service.controls.extra.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS";
 
     /**
      * @hide
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index c074e84..709bc2b 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -47,7 +47,6 @@
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
-import android.util.SparseIntArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.InsetsSourceConsumer.ShowResult;
 import android.view.InsetsState.InternalInsetsType;
@@ -1179,6 +1178,8 @@
             // nothing to animate.
             listener.onCancelled(null);
             if (DEBUG) Log.d(TAG, "no types to animate in controlAnimationUnchecked");
+            Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApi", 0);
+            Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApiToImeReady", 0);
             return;
         }
         cancelExistingControllers(types);
@@ -1218,12 +1219,20 @@
             setRequestedVisibleTypes(mReportedRequestedVisibleTypes, types);
 
             Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApi", 0);
+            if (!fromIme) {
+                Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApiToImeReady", 0);
+            }
             return;
         }
 
         if (typesReady == 0) {
             if (DEBUG) Log.d(TAG, "No types ready. onCancelled()");
             listener.onCancelled(null);
+            reportRequestedVisibleTypes();
+            Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApi", 0);
+            if (!fromIme) {
+                Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApiToImeReady", 0);
+            }
             return;
         }
 
@@ -1571,6 +1580,10 @@
         if (types == 0) {
             // nothing to animate.
             if (DEBUG) Log.d(TAG, "applyAnimation, nothing to animate");
+            Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApi", 0);
+            if (!fromIme) {
+                Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApiToImeReady", 0);
+            }
             return;
         }
 
diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java
index 43d427d..9472d86 100644
--- a/core/java/android/view/WindowManagerPolicyConstants.java
+++ b/core/java/android/view/WindowManagerPolicyConstants.java
@@ -55,13 +55,6 @@
     int PRESENCE_INTERNAL = 1 << 0;
     int PRESENCE_EXTERNAL = 1 << 1;
 
-    // Alternate bars position values
-    int ALT_BAR_UNKNOWN = -1;
-    int ALT_BAR_LEFT = 1 << 0;
-    int ALT_BAR_RIGHT = 1 << 1;
-    int ALT_BAR_BOTTOM = 1 << 2;
-    int ALT_BAR_TOP = 1 << 3;
-
     // Navigation bar position values
     int NAV_BAR_INVALID = -1;
     int NAV_BAR_LEFT = 1 << 0;
diff --git a/core/java/android/view/inputmethod/HandwritingGesture.java b/core/java/android/view/inputmethod/HandwritingGesture.java
index 07b1e1f..2516269 100644
--- a/core/java/android/view/inputmethod/HandwritingGesture.java
+++ b/core/java/android/view/inputmethod/HandwritingGesture.java
@@ -159,7 +159,7 @@
      * @hide
      */
     @TestApi
-    public @GestureType int getGestureType() {
+    public final @GestureType int getGestureType() {
         return mType;
     }
 
@@ -173,7 +173,7 @@
      * example 2: join can fail if the gesture is drawn over text but there is no whitespace.
      */
     @Nullable
-    public String getFallbackText() {
+    public final String getFallbackText() {
         return mFallbackText;
     }
 }
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 28ac464..19cb30e 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -3201,6 +3201,30 @@
     return nativeToJavaStatus(status);
 }
 
+static jboolean android_media_AudioSystem_supportsBluetoothVariableLatency(JNIEnv *env,
+                                                                           jobject thiz) {
+    bool supports;
+    if (AudioSystem::supportsBluetoothVariableLatency(&supports) != NO_ERROR) {
+        supports = false;
+    }
+    return supports;
+}
+
+static int android_media_AudioSystem_setBluetoothVariableLatencyEnabled(JNIEnv *env, jobject thiz,
+                                                                        jboolean enabled) {
+    return (jint)check_AudioSystem_Command(
+            AudioSystem::setBluetoothVariableLatencyEnabled(enabled));
+}
+
+static jboolean android_media_AudioSystem_isBluetoothVariableLatencyEnabled(JNIEnv *env,
+                                                                            jobject thiz) {
+    bool enabled;
+    if (AudioSystem::isBluetoothVariableLatencyEnabled(&enabled) != NO_ERROR) {
+        enabled = false;
+    }
+    return enabled;
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gMethods[] =
@@ -3364,7 +3388,13 @@
          {"getPreferredMixerAttributes", "(Landroid/media/AudioAttributes;ILjava/util/List;)I",
           (void *)android_media_AudioSystem_getPreferredMixerAttributes},
          {"clearPreferredMixerAttributes", "(Landroid/media/AudioAttributes;II)I",
-          (void *)android_media_AudioSystem_clearPreferredMixerAttributes}};
+          (void *)android_media_AudioSystem_clearPreferredMixerAttributes},
+         {"supportsBluetoothVariableLatency", "()Z",
+          (void *)android_media_AudioSystem_supportsBluetoothVariableLatency},
+         {"setBluetoothVariableLatencyEnabled", "(Z)I",
+          (void *)android_media_AudioSystem_setBluetoothVariableLatencyEnabled},
+         {"isBluetoothVariableLatencyEnabled", "()Z",
+          (void *)android_media_AudioSystem_isBluetoothVariableLatencyEnabled}};
 
 static const JNINativeMethod gEventHandlerMethods[] = {
     {"native_setup",
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index 0160f18..73671db 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -85,7 +85,18 @@
     @Test
     fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
 
-    @Presubmit @Test fun primaryAppLayerKeepVisible() = flicker.layerKeepVisible(primaryApp)
+    @Presubmit
+    @Test
+    fun primaryAppLayerKeepVisible() {
+        Assume.assumeFalse(isShellTransitionsEnabled)
+        flicker.layerKeepVisible(primaryApp)
+    }
+
+    @FlakyTest(bugId = 263213649)
+    @Test fun primaryAppLayerKeepVisible_ShellTransit() {
+        Assume.assumeTrue(isShellTransitionsEnabled)
+        flicker.layerKeepVisible(primaryApp)
+    }
 
     @Presubmit
     @Test
@@ -99,17 +110,7 @@
         }
     }
 
-    @Presubmit
-    @Test fun primaryAppWindowKeepVisible() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-        flicker.appWindowKeepVisible(primaryApp)
-    }
-
-    @FlakyTest(bugId = 263213649)
-    @Test fun primaryAppWindowKeepVisible_ShellTransit() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
-        flicker.appWindowKeepVisible(primaryApp)
-    }
+    @Presubmit @Test fun primaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(primaryApp)
 
     @Presubmit
     @Test
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index b0609ec..fdd6233 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -16,6 +16,10 @@
 
 package android.media;
 
+import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
@@ -34,6 +38,7 @@
 import android.bluetooth.BluetoothCodecConfig;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothLeAudioCodecConfig;
+import android.companion.virtual.VirtualDeviceManager;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -100,6 +105,7 @@
 
     private Context mOriginalContext;
     private Context mApplicationContext;
+    private @Nullable VirtualDeviceManager mVirtualDeviceManager; // Lazy initialized.
     private long mVolumeKeyUpTime;
     private static final String TAG = "AudioManager";
     private static final boolean DEBUG = false;
@@ -858,6 +864,14 @@
         return sService;
     }
 
+    private VirtualDeviceManager getVirtualDeviceManager() {
+        if (mVirtualDeviceManager != null) {
+            return mVirtualDeviceManager;
+        }
+        mVirtualDeviceManager = getContext().getSystemService(VirtualDeviceManager.class);
+        return mVirtualDeviceManager;
+    }
+
     /**
      * Sends a simulated key event for a media button.
      * To simulate a key press, you must first send a KeyEvent built with a
@@ -3635,11 +3649,15 @@
      * whether sounds are heard or not.
      * @hide
      */
-    public void  playSoundEffect(@SystemSoundEffect int effectType, int userId) {
+    public void playSoundEffect(@SystemSoundEffect int effectType, int userId) {
         if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {
             return;
         }
 
+        if (delegateSoundEffectToVdm(effectType)) {
+            return;
+        }
+
         final IAudioService service = getService();
         try {
             service.playSoundEffect(effectType, userId);
@@ -3657,11 +3675,15 @@
      * NOTE: This version is for applications that have their own
      * settings panel for enabling and controlling volume.
      */
-    public void  playSoundEffect(@SystemSoundEffect int effectType, float volume) {
+    public void playSoundEffect(@SystemSoundEffect int effectType, float volume) {
         if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {
             return;
         }
 
+        if (delegateSoundEffectToVdm(effectType)) {
+            return;
+        }
+
         final IAudioService service = getService();
         try {
             service.playSoundEffectVolume(effectType, volume);
@@ -3671,6 +3693,28 @@
     }
 
     /**
+     * Checks whether this {@link AudioManager} instance is asociated with {@link VirtualDevice}
+     * configured with custom device policy for audio. If there is such device, request to play
+     * sound effect is forwarded to {@link VirtualDeviceManager}.
+     *
+     * @param effectType - The type of sound effect.
+     * @return true if the request was forwarded to {@link VirtualDeviceManager} instance,
+     * false otherwise.
+     */
+    private boolean delegateSoundEffectToVdm(@SystemSoundEffect int effectType) {
+        int deviceId = getContext().getDeviceId();
+        if (deviceId != DEVICE_ID_DEFAULT) {
+            VirtualDeviceManager vdm = getVirtualDeviceManager();
+            if (vdm != null && vdm.getDevicePolicy(deviceId, POLICY_TYPE_AUDIO)
+                    != DEVICE_POLICY_DEFAULT) {
+                vdm.playSoundEffect(deviceId, effectType);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      *  Load Sound effects.
      *  This method must be called when sound effects are enabled.
      */
@@ -8773,6 +8817,55 @@
         }
     }
 
+    /**
+     * Requests if the implementation supports controlling the latency modes
+     * over the Bluetooth A2DP or LE Audio links.
+     *
+     * @return true if supported, false otherwise
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public boolean supportsBluetoothVariableLatency() {
+        try {
+            return getService().supportsBluetoothVariableLatency();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Enables or disables the variable Bluetooth latency control mechanism in the
+     * audio framework and the audio HAL. This does not apply to the latency mode control
+     * on the spatializer output as this is a built-in feature.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public void setBluetoothVariableLatencyEnabled(boolean enabled) {
+        try {
+            getService().setBluetoothVariableLatencyEnabled(enabled);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Indicates if the variable Bluetooth latency control mechanism is enabled or disabled.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public boolean isBluetoothVariableLatencyEnabled() {
+        try {
+            return getService().isBluetoothVariableLatencyEnabled();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     //====================================================================
     // Mute await connection
 
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 3e0d657..a743586 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -2476,4 +2476,30 @@
      */
     public static native int clearPreferredMixerAttributes(
             @NonNull AudioAttributes attributes, int portId, int uid);
+
+
+    /**
+     * Requests if the implementation supports controlling the latency modes
+     * over the Bluetooth A2DP or LE Audio links.
+     *
+     * @return true if supported, false otherwise
+     *
+     * @hide
+     */
+    public static native boolean supportsBluetoothVariableLatency();
+
+    /**
+     * Enables or disables the variable Bluetooth latency control mechanism in the
+     * audio framework and the audio HAL. This does not apply to the latency mode control
+     * on the spatializer output as this is a built-in feature.
+     *
+     * @hide
+     */
+    public static native int setBluetoothVariableLatencyEnabled(boolean enabled);
+
+    /**
+     * Indicates if the variable Bluetooth latency control mechanism is enabled or disabled.
+     * @hide
+     */
+    public static native boolean isBluetoothVariableLatencyEnabled();
 }
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 05a0b86..0f63cc4 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -587,4 +587,16 @@
             IPreferredMixerAttributesDispatcher dispatcher);
     oneway void unregisterPreferredMixerAttributesDispatcher(
             IPreferredMixerAttributesDispatcher dispatcher);
+
+    @EnforcePermission("MODIFY_AUDIO_ROUTING")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)")
+    boolean supportsBluetoothVariableLatency();
+
+    @EnforcePermission("MODIFY_AUDIO_ROUTING")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)")
+    void setBluetoothVariableLatencyEnabled(boolean enabled);
+
+    @EnforcePermission("MODIFY_AUDIO_ROUTING")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)")
+    boolean isBluetoothVariableLatencyEnabled();
 }
diff --git a/media/java/android/media/RouteListingPreference.java b/media/java/android/media/RouteListingPreference.java
index 6557277..d74df7a 100644
--- a/media/java/android/media/RouteListingPreference.java
+++ b/media/java/android/media/RouteListingPreference.java
@@ -87,6 +87,9 @@
      * Returns true if the application would like media route listing to use the system's ordering
      * strategy, or false if the application would like route listing to respect the ordering
      * obtained from {@link #getItems()}.
+     *
+     * <p>The system's ordering strategy is implementation-dependent, but may take into account each
+     * route's recency or frequency of use in order to rank them.
      */
     public boolean getUseSystemOrdering() {
         return mUseSystemOrdering;
diff --git a/media/java/android/media/tv/AdBuffer.aidl b/media/java/android/media/tv/AdBuffer.aidl
new file mode 100644
index 0000000..b1e2d3e
--- /dev/null
+++ b/media/java/android/media/tv/AdBuffer.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+parcelable AdBuffer;
diff --git a/media/java/android/media/tv/AdBuffer.java b/media/java/android/media/tv/AdBuffer.java
new file mode 100644
index 0000000..ed44508
--- /dev/null
+++ b/media/java/android/media/tv/AdBuffer.java
@@ -0,0 +1,163 @@
+/*
+ * 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.media.tv;
+
+import android.annotation.NonNull;
+import android.media.MediaCodec.BufferFlag;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SharedMemory;
+
+/**
+ * Buffer for advertisement data.
+ * @hide
+ */
+public class AdBuffer implements Parcelable {
+    private final int mId;
+    @NonNull
+    private final String mMimeType;
+    @NonNull
+    private final SharedMemory mBuffer;
+    private final int mOffset;
+    private final int mLength;
+    private final long mPresentationTimeUs;
+    private final int mFlags;
+
+    public AdBuffer(
+            int id,
+            @NonNull String mimeType,
+            @NonNull SharedMemory buffer,
+            int offset,
+            int length,
+            long presentationTimeUs,
+            @BufferFlag int flags) {
+        this.mId = id;
+        this.mMimeType = mimeType;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mimeType);
+        this.mBuffer = buffer;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, buffer);
+        this.mOffset = offset;
+        this.mLength = length;
+        this.mPresentationTimeUs = presentationTimeUs;
+        this.mFlags = flags;
+    }
+
+    /**
+     * Gets corresponding AD request ID.
+     */
+    public int getId() {
+        return mId;
+    }
+
+    /**
+     * Gets the mime type of the data.
+     */
+    @NonNull
+    public String getMimeType() {
+        return mMimeType;
+    }
+
+    /**
+     * Gets the shared memory which stores the data.
+     */
+    @NonNull
+    public SharedMemory getSharedMemory() {
+        return mBuffer;
+    }
+
+    /**
+     * Gets the offset of the buffer.
+     */
+    public int getOffset() {
+        return mOffset;
+    }
+
+    /**
+     * Gets the data length.
+     */
+    public int getLength() {
+        return mLength;
+    }
+
+    /**
+     * Gets the presentation time in microseconds.
+     */
+    public long getPresentationTimeUs() {
+        return mPresentationTimeUs;
+    }
+
+    /**
+     * Gets the flags.
+     */
+    @BufferFlag
+    public int getFlags() {
+        return mFlags;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        dest.writeInt(mId);
+        dest.writeString(mMimeType);
+        dest.writeTypedObject(mBuffer, flags);
+        dest.writeInt(mOffset);
+        dest.writeInt(mLength);
+        dest.writeLong(mPresentationTimeUs);
+        dest.writeInt(mFlags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    private AdBuffer(@NonNull Parcel in) {
+        int id = in.readInt();
+        String mimeType = in.readString();
+        SharedMemory buffer = (SharedMemory) in.readTypedObject(SharedMemory.CREATOR);
+        int offset = in.readInt();
+        int length = in.readInt();
+        long presentationTimeUs = in.readLong();
+        int flags = in.readInt();
+
+        this.mId = id;
+        this.mMimeType = mimeType;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mMimeType);
+        this.mBuffer = buffer;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mBuffer);
+        this.mOffset = offset;
+        this.mLength = length;
+        this.mPresentationTimeUs = presentationTimeUs;
+        this.mFlags = flags;
+    }
+
+    public static final @NonNull Parcelable.Creator<AdBuffer> CREATOR =
+            new Parcelable.Creator<AdBuffer>() {
+                @Override
+                public AdBuffer[] newArray(int size) {
+                    return new AdBuffer[size];
+                }
+
+                @Override
+                public AdBuffer createFromParcel(Parcel in) {
+                    return new AdBuffer(in);
+            }
+    };
+}
diff --git a/media/java/android/media/tv/AdRequest.java b/media/java/android/media/tv/AdRequest.java
index f2fb93d..60dfc5e 100644
--- a/media/java/android/media/tv/AdRequest.java
+++ b/media/java/android/media/tv/AdRequest.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
@@ -69,10 +70,25 @@
     private final long mEchoInterval;
     private final String mMediaFileType;
     private final Bundle mMetadata;
+    private final Uri mUri;
 
     public AdRequest(int id, @RequestType int requestType,
             @Nullable ParcelFileDescriptor fileDescriptor, long startTime, long stopTime,
             long echoInterval, @Nullable String mediaFileType, @NonNull Bundle metadata) {
+        this(id, requestType, fileDescriptor, null, startTime, stopTime, echoInterval,
+                mediaFileType, metadata);
+    }
+
+    /** @hide */
+    public AdRequest(int id, @RequestType int requestType, @Nullable Uri uri, long startTime,
+            long stopTime, long echoInterval, @NonNull Bundle metadata) {
+        this(id, requestType, null, uri, startTime, stopTime, echoInterval, null, metadata);
+    }
+
+    private AdRequest(int id, @RequestType int requestType,
+            @Nullable ParcelFileDescriptor fileDescriptor, @Nullable Uri uri, long startTime,
+            long stopTime, long echoInterval, @Nullable String mediaFileType,
+            @NonNull Bundle metadata) {
         mId = id;
         mRequestType = requestType;
         mFileDescriptor = fileDescriptor;
@@ -81,15 +97,23 @@
         mEchoInterval = echoInterval;
         mMediaFileType = mediaFileType;
         mMetadata = metadata;
+        mUri = uri;
     }
 
     private AdRequest(Parcel source) {
         mId = source.readInt();
         mRequestType = source.readInt();
-        if (source.readInt() != 0) {
+        int readInt = source.readInt();
+        if (readInt == 1) {
             mFileDescriptor = ParcelFileDescriptor.CREATOR.createFromParcel(source);
+            mUri = null;
+        } else if (readInt == 2) {
+            String stringUri = source.readString();
+            mUri = stringUri == null ? null : Uri.parse(stringUri);
+            mFileDescriptor = null;
         } else {
             mFileDescriptor = null;
+            mUri = null;
         }
         mStartTime = source.readLong();
         mStopTime = source.readLong();
@@ -117,7 +141,7 @@
      * Gets the file descriptor of the AD media.
      *
      * @return The file descriptor of the AD media. Can be {@code null} for
-     *         {@link #REQUEST_TYPE_STOP}
+     *         {@link #REQUEST_TYPE_STOP} or a URI is used.
      */
     @Nullable
     public ParcelFileDescriptor getFileDescriptor() {
@@ -125,6 +149,18 @@
     }
 
     /**
+     * Gets the URI of the AD media.
+     *
+     * @return The URI of the AD media. Can be {@code null} for {@link #REQUEST_TYPE_STOP} or a file
+     *         descriptor is used.
+     * @hide
+     */
+    @Nullable
+    public Uri getUri() {
+        return mUri;
+    }
+
+    /**
      * Gets the start time of the AD media in milliseconds.
      * <p>0 means start immediately
      */
@@ -189,6 +225,10 @@
         if (mFileDescriptor != null) {
             dest.writeInt(1);
             mFileDescriptor.writeToParcel(dest, flags);
+        } else if (mUri != null) {
+            dest.writeInt(2);
+            String stringUri = mUri.toString();
+            dest.writeString(stringUri);
         } else {
             dest.writeInt(0);
         }
diff --git a/media/java/android/media/tv/AdResponse.java b/media/java/android/media/tv/AdResponse.java
index 08c66ab..a15e8c1 100644
--- a/media/java/android/media/tv/AdResponse.java
+++ b/media/java/android/media/tv/AdResponse.java
@@ -34,7 +34,8 @@
             RESPONSE_TYPE_PLAYING,
             RESPONSE_TYPE_FINISHED,
             RESPONSE_TYPE_STOPPED,
-            RESPONSE_TYPE_ERROR
+            RESPONSE_TYPE_ERROR,
+            RESPONSE_TYPE_BUFFERING
     })
     public @interface ResponseType {}
 
@@ -42,6 +43,8 @@
     public static final int RESPONSE_TYPE_FINISHED = 2;
     public static final int RESPONSE_TYPE_STOPPED = 3;
     public static final int RESPONSE_TYPE_ERROR = 4;
+    /** @hide */
+    public static final int RESPONSE_TYPE_BUFFERING = 5;
 
     public static final @NonNull Parcelable.Creator<AdResponse> CREATOR =
             new Parcelable.Creator<AdResponse>() {
diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl
index 49148ce..ed2fd20 100644
--- a/media/java/android/media/tv/ITvInputClient.aidl
+++ b/media/java/android/media/tv/ITvInputClient.aidl
@@ -17,6 +17,7 @@
 package android.media.tv;
 
 import android.content.ComponentName;
+import android.media.tv.AdBuffer;
 import android.media.tv.AdResponse;
 import android.media.tv.AitInfo;
 import android.media.tv.BroadcastInfoResponse;
@@ -59,4 +60,5 @@
 
     // For ad response
     void onAdResponse(in AdResponse response, int seq);
+    void onAdBufferConsumed(in AdBuffer buffer, int seq);
 }
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index 2a33ee6..f7c1e3c 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -20,6 +20,7 @@
 import android.content.Intent;
 import android.graphics.Rect;
 import android.media.PlaybackParams;
+import android.media.tv.AdBuffer;
 import android.media.tv.AdRequest;
 import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.DvbDeviceInfo;
@@ -110,6 +111,7 @@
 
     // For ad request
     void requestAd(in IBinder sessionToken, in AdRequest request, int userId);
+    void notifyAdBuffer(in IBinder sessionToken, in AdBuffer buffer, int userId);
 
     // For TV input hardware binding
     List<TvInputHardwareInfo> getHardwareList();
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index 9820034..326b98d 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -18,6 +18,7 @@
 
 import android.graphics.Rect;
 import android.media.PlaybackParams;
+import android.media.tv.AdBuffer;
 import android.media.tv.AdRequest;
 import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.TvTrackInfo;
@@ -71,4 +72,5 @@
 
     // For ad request
     void requestAd(in AdRequest request);
+    void notifyAdBuffer(in AdBuffer buffer);
 }
diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl
index 9dfdb78..b2a8d1c 100644
--- a/media/java/android/media/tv/ITvInputSessionCallback.aidl
+++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl
@@ -16,6 +16,7 @@
 
 package android.media.tv;
 
+import android.media.tv.AdBuffer;
 import android.media.tv.AdResponse;
 import android.media.tv.AitInfo;
 import android.media.tv.BroadcastInfoResponse;
@@ -56,4 +57,5 @@
 
     // For ad response
     void onAdResponse(in AdResponse response);
+    void onAdBufferConsumed(in AdBuffer buffer);
 }
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 8911f6c..634f102 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -74,6 +74,7 @@
     private static final int DO_REMOVE_BROADCAST_INFO = 25;
     private static final int DO_SET_IAPP_NOTIFICATION_ENABLED = 26;
     private static final int DO_REQUEST_AD = 27;
+    private static final int DO_NOTIFY_AD_BUFFER = 28;
 
     private final boolean mIsRecordingSession;
     private final HandlerCaller mCaller;
@@ -224,6 +225,7 @@
             case DO_START_RECORDING: {
                 SomeArgs args = (SomeArgs) msg.obj;
                 mTvInputRecordingSessionImpl.startRecording((Uri) args.arg1, (Bundle) args.arg2);
+                args.recycle();
                 break;
             }
             case DO_STOP_RECORDING: {
@@ -254,6 +256,10 @@
                 mTvInputSessionImpl.requestAd((AdRequest) msg.obj);
                 break;
             }
+            case DO_NOTIFY_AD_BUFFER: {
+                mTvInputSessionImpl.notifyAdBuffer((AdBuffer) msg.obj);
+                break;
+            }
             default: {
                 Log.w(TAG, "Unhandled message code: " + msg.what);
                 break;
@@ -424,6 +430,11 @@
         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_REQUEST_AD, request));
     }
 
+    @Override
+    public void notifyAdBuffer(AdBuffer buffer) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_NOTIFY_AD_BUFFER, buffer));
+    }
+
     private final class TvInputEventReceiver extends InputEventReceiver {
         public TvInputEventReceiver(InputChannel inputChannel, Looper looper) {
             super(inputChannel, looper);
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 04d28e7..690fcb1 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -965,6 +965,19 @@
                 });
             }
         }
+
+        void postAdBufferConsumed(AdBuffer buffer) {
+            if (mSession.mIAppNotificationEnabled) {
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (mSession.getInteractiveAppSession() != null) {
+                            mSession.getInteractiveAppSession().notifyAdBufferConsumed(buffer);
+                        }
+                    }
+                });
+            }
+        }
     }
 
     /**
@@ -1412,6 +1425,18 @@
                     record.postAdResponse(response);
                 }
             }
+
+            @Override
+            public void onAdBufferConsumed(AdBuffer buffer, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postAdBufferConsumed(buffer);
+                }
+            }
         };
         ITvInputManagerCallback managerCallback = new ITvInputManagerCallback.Stub() {
             @Override
@@ -3204,6 +3229,21 @@
             }
         }
 
+        /**
+         * Notifies when the advertisement buffer is filled and ready to be read.
+         */
+        public void notifyAdBuffer(AdBuffer buffer) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyAdBuffer(mToken, buffer, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         private final class InputEventHandler extends Handler {
             public static final int MSG_SEND_INPUT_EVENT = 1;
             public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 70acf25..c46cdbc 100755
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -913,6 +913,27 @@
             });
         }
 
+        /**
+         * Notifies the advertisement buffer is consumed.
+         * @hide
+         */
+        public void notifyAdBufferConsumed(AdBuffer buffer) {
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) Log.d(TAG, "notifyAdBufferConsumed");
+                        if (mSessionCallback != null) {
+                            mSessionCallback.onAdBufferConsumed(buffer);
+                        }
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "error in notifyAdBufferConsumed", e);
+                    }
+                }
+            });
+        }
+
         private void notifyTimeShiftStartPositionChanged(final long timeMs) {
             executeOrPostRunnableOnMainThread(new Runnable() {
                 @MainThread
@@ -1129,6 +1150,13 @@
         }
 
         /**
+         * Called when advertisement buffer is ready.
+         * @hide
+         */
+        public void onAdBuffer(AdBuffer buffer) {
+        }
+
+        /**
          * Tunes to a given channel.
          *
          * <p>No video will be displayed until {@link #notifyVideoAvailable()} is called.
@@ -1753,6 +1781,10 @@
             onRequestAd(request);
         }
 
+        void notifyAdBuffer(AdBuffer buffer) {
+            onAdBuffer(buffer);
+        }
+
         /**
          * Takes care of dispatching incoming input events and tells whether the event was handled.
          */
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
index 9b8ec5e..98357fc 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
@@ -17,6 +17,7 @@
 package android.media.tv.interactive;
 
 import android.graphics.Rect;
+import android.media.tv.AdBuffer;
 import android.media.tv.AdRequest;
 import android.media.tv.BroadcastInfoRequest;
 import android.net.Uri;
@@ -37,6 +38,7 @@
     void onSessionStateChanged(int state, int err, int seq);
     void onBiInteractiveAppCreated(in Uri biIAppUri, in String biIAppId, int seq);
     void onTeletextAppStateChanged(int state, int seq);
+    void onAdBuffer(in AdBuffer buffer, int seq);
     void onCommandRequest(in String cmdType, in Bundle parameters, int seq);
     void onSetVideoBounds(in Rect rect, int seq);
     void onRequestCurrentChannelUri(int seq);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
index 38fc717..8bfceee 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
@@ -17,6 +17,7 @@
 package android.media.tv.interactive;
 
 import android.graphics.Rect;
+import android.media.tv.AdBuffer;
 import android.media.tv.AdResponse;
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.TvTrackInfo;
@@ -71,6 +72,7 @@
     void notifyBroadcastInfoResponse(in IBinder sessionToken, in BroadcastInfoResponse response,
             int UserId);
     void notifyAdResponse(in IBinder sessionToken, in AdResponse response, int UserId);
+    void notifyAdBufferConsumed(in IBinder sessionToken, in AdBuffer buffer, int userId);
 
     void createMediaView(in IBinder sessionToken, in IBinder windowToken, in Rect frame,
             int userId);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
index 9e33536..1953117 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
@@ -19,6 +19,7 @@
 import android.graphics.Rect;
 import android.media.tv.BroadcastInfoResponse;
 import android.net.Uri;
+import android.media.tv.AdBuffer;
 import android.media.tv.AdResponse;
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.TvTrackInfo;
@@ -59,6 +60,7 @@
     void dispatchSurfaceChanged(int format, int width, int height);
     void notifyBroadcastInfoResponse(in BroadcastInfoResponse response);
     void notifyAdResponse(in AdResponse response);
+    void notifyAdBufferConsumed(in AdBuffer buffer);
 
     void createMediaView(in IBinder windowToken, in Rect frame);
     void relayoutMediaView(in Rect frame);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
index 4ce5871..cd4f410 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
@@ -17,6 +17,7 @@
 package android.media.tv.interactive;
 
 import android.graphics.Rect;
+import android.media.tv.AdBuffer;
 import android.media.tv.AdRequest;
 import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.interactive.ITvInteractiveAppSession;
@@ -36,6 +37,7 @@
     void onSessionStateChanged(int state, int err);
     void onBiInteractiveAppCreated(in Uri biIAppUri, in String biIAppId);
     void onTeletextAppStateChanged(int state);
+    void onAdBuffer(in AdBuffer buffer);
     void onCommandRequest(in String cmdType, in Bundle parameters);
     void onSetVideoBounds(in Rect rect);
     void onRequestCurrentChannelUri();
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
index a2fdfe0..b646326 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.Rect;
+import android.media.tv.AdBuffer;
 import android.media.tv.AdResponse;
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.TvContentRating;
@@ -83,6 +84,7 @@
     private static final int DO_REMOVE_MEDIA_VIEW = 29;
     private static final int DO_NOTIFY_RECORDING_STARTED = 30;
     private static final int DO_NOTIFY_RECORDING_STOPPED = 31;
+    private static final int DO_NOTIFY_AD_BUFFER_CONSUMED = 32;
 
     private final HandlerCaller mCaller;
     private Session mSessionImpl;
@@ -253,6 +255,10 @@
                 mSessionImpl.removeMediaView(true);
                 break;
             }
+            case DO_NOTIFY_AD_BUFFER_CONSUMED: {
+                mSessionImpl.notifyAdBufferConsumed((AdBuffer) msg.obj);
+                break;
+            }
             default: {
                 Log.w(TAG, "Unhandled message code: " + msg.what);
                 break;
@@ -425,6 +431,11 @@
     }
 
     @Override
+    public void notifyAdBufferConsumed(AdBuffer buffer) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_NOTIFY_AD_BUFFER_CONSUMED, buffer));
+    }
+
+    @Override
     public void createMediaView(IBinder windowToken, Rect frame) {
         mCaller.executeOrSendMessage(
                 mCaller.obtainMessageOO(DO_CREATE_MEDIA_VIEW, windowToken, frame));
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index 287df40..c57efc8 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -23,6 +23,7 @@
 import android.annotation.SystemService;
 import android.content.Context;
 import android.graphics.Rect;
+import android.media.tv.AdBuffer;
 import android.media.tv.AdRequest;
 import android.media.tv.AdResponse;
 import android.media.tv.BroadcastInfoRequest;
@@ -558,6 +559,18 @@
                     record.postTeletextAppStateChanged(state);
                 }
             }
+
+            @Override
+            public void onAdBuffer(AdBuffer buffer, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postAdBuffer(buffer);
+                }
+            }
         };
         ITvInteractiveAppManagerCallback managerCallback =
                 new ITvInteractiveAppManagerCallback.Stub() {
@@ -1278,6 +1291,21 @@
         }
 
         /**
+         * Notifies the advertisement buffer is consumed.
+         */
+        public void notifyAdBufferConsumed(AdBuffer buffer) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyAdBufferConsumed(mToken, buffer, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
          * Releases this session.
          */
         public void release() {
@@ -1808,6 +1836,17 @@
                 }
             });
         }
+
+        void postAdBuffer(AdBuffer buffer) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (mSession.getInputSession() != null) {
+                        mSession.getInputSession().notifyAdBuffer(buffer);
+                    }
+                }
+            });
+        }
     }
 
     /**
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 00150d5..4ed7ca5 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -30,6 +30,7 @@
 import android.content.Intent;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
+import android.media.tv.AdBuffer;
 import android.media.tv.AdRequest;
 import android.media.tv.AdResponse;
 import android.media.tv.BroadcastInfoRequest;
@@ -616,6 +617,13 @@
         public void onAdResponse(@NonNull AdResponse response) {
         }
 
+        /**
+         * Called when an advertisement buffer is consumed.
+         * @hide
+         */
+        public void onAdBufferConsumed(AdBuffer buffer) {
+        }
+
         @Override
         public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
             return false;
@@ -1190,6 +1198,17 @@
         }
 
         /**
+         * Calls {@link #onAdBufferConsumed}.
+         */
+        void notifyAdBufferConsumed(AdBuffer buffer) {
+            if (DEBUG) {
+                Log.d(TAG,
+                        "notifyAdBufferConsumed (buffer=" + buffer + ")");
+            }
+            onAdBufferConsumed(buffer);
+        }
+
+        /**
          * Calls {@link #onRecordingStarted(String)}.
          */
         void notifyRecordingStarted(String recordingId) {
@@ -1290,6 +1309,33 @@
             });
         }
 
+
+        /**
+         * Notifies when the advertisement buffer is filled and ready to be read.
+         * @hide
+         */
+        @CallSuper
+        public void notifyAdBuffer(AdBuffer buffer) {
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) {
+                            Log.d(TAG,
+                                    "notifyAdBuffer(buffer=" + buffer + ")");
+                        }
+                        if (mSessionCallback != null) {
+                            mSessionCallback.onAdBuffer(buffer);
+                        }
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "error in notifyAdBuffer", e);
+                    }
+                }
+            });
+        }
+
+
         /**
          * Takes care of dispatching incoming input events and tells whether the event was handled.
          */
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java
new file mode 100644
index 0000000..76543f4
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mediaframeworktest.unit;
+
+
+import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.media.AudioManager.FX_KEY_CLICK;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.companion.virtual.VirtualDeviceManager;
+import android.content.Context;
+import android.media.AudioManager;
+import android.test.mock.MockContext;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class AudioManagerUnitTest {
+    private static final int TEST_VIRTUAL_DEVICE_ID = 42;
+
+    @Test
+    public void testAudioManager_playSoundWithDefaultDeviceContext() {
+        VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
+                DEVICE_POLICY_CUSTOM);
+        Context defaultDeviceContext = getVirtualDeviceMockContext(DEVICE_ID_DEFAULT, /*vdm=*/
+                mockVdm);
+        AudioManager audioManager = new AudioManager(defaultDeviceContext);
+
+        audioManager.playSoundEffect(FX_KEY_CLICK);
+
+        // We expect no interactions with VDM when running on default device.
+        verifyZeroInteractions(mockVdm);
+    }
+
+    @Test
+    public void testAudioManager_playSoundWithVirtualDeviceContextDefaultPolicy() {
+        VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
+                DEVICE_POLICY_DEFAULT);
+        Context defaultDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, /*vdm=*/
+                mockVdm);
+        AudioManager audioManager = new AudioManager(defaultDeviceContext);
+
+        audioManager.playSoundEffect(FX_KEY_CLICK);
+
+        // We expect playback not to be delegated to VDM because of default device policy for audio.
+        verify(mockVdm, never()).playSoundEffect(anyInt(), anyInt());
+    }
+
+    @Test
+    public void testAudioManager_playSoundWithVirtualDeviceContextCustomPolicy() {
+        VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
+                DEVICE_POLICY_CUSTOM);
+        Context defaultDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, /*vdm=*/
+                mockVdm);
+        AudioManager audioManager = new AudioManager(defaultDeviceContext);
+
+        audioManager.playSoundEffect(FX_KEY_CLICK);
+
+        // We expect playback to be delegated to VDM because of custom device policy for audio.
+        verify(mockVdm, times(1)).playSoundEffect(TEST_VIRTUAL_DEVICE_ID, FX_KEY_CLICK);
+    }
+
+    private static Context getVirtualDeviceMockContext(int deviceId, VirtualDeviceManager vdm) {
+        MockContext mockContext = mock(MockContext.class);
+        when(mockContext.getDeviceId()).thenReturn(deviceId);
+        when(mockContext.getSystemService(VirtualDeviceManager.class)).thenReturn(vdm);
+        return mockContext;
+    }
+
+    private static VirtualDeviceManager getMockVirtualDeviceManager(
+            int deviceId, int audioDevicePolicy) {
+        VirtualDeviceManager vdmMock = mock(VirtualDeviceManager.class);
+        when(vdmMock.getDevicePolicy(anyInt(), anyInt())).thenReturn(DEVICE_POLICY_DEFAULT);
+        when(vdmMock.getDevicePolicy(deviceId, POLICY_TYPE_AUDIO)).thenReturn(audioDevicePolicy);
+        return vdmMock;
+    }
+}
diff --git a/packages/CredentialManager/res/drawable/ic_profile.xml b/packages/CredentialManager/res/drawable/ic_profile.xml
deleted file mode 100644
index ae65940..0000000
--- a/packages/CredentialManager/res/drawable/ic_profile.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
-        android:viewportWidth="46"
-        android:viewportHeight="46"
-        android:width="46dp"
-        android:height="46dp">
-    <path
-        android:pathData="M45.4247 22.9953C45.4247 35.0229 35.4133 44.7953 23.0359 44.7953C10.6585 44.7953 0.646973 35.0229 0.646973 22.9953C0.646973 10.9677 10.6585 1.19531 23.0359 1.19531C35.4133 1.19531 45.4247 10.9677 45.4247 22.9953Z"
-        android:strokeColor="#202124"
-        android:strokeAlpha="0.13"
-        android:strokeWidth="1" />
-</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index a3ebf1e..91ffc44 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -9,6 +9,8 @@
   <string name="string_cancel">Cancel</string>
   <!-- Button label to confirm choosing the default dialog information and continue. [CHAR LIMIT=40] -->
   <string name="string_continue">Continue</string>
+  <!-- Button label to create this credential in other available places. [CHAR LIMIT=40] -->
+  <string name="string_more_options">More options</string>
   <!-- This appears as a text button where users can click to create this passkey in other available places. [CHAR LIMIT=80] -->
   <string name="string_create_in_another_place">Create in another place</string>
   <!-- This appears as a text button where users can click to create this password or other credential types in other available places. [CHAR LIMIT=80] -->
@@ -20,11 +22,11 @@
   <!-- This appears as the title of the modal bottom sheet introducing what is passkey to users. [CHAR LIMIT=200] -->
   <string name="passkey_creation_intro_title">Safer with passkeys</string>
   <!-- This appears as the description body of the modal bottom sheet introducing why passkey beneficial on the passwords side. [CHAR LIMIT=200] -->
-  <string name="passkey_creation_intro_body_password">No need to create or remember complex passwords</string>
+  <string name="passkey_creation_intro_body_password">With passkeys, you don’t need to create or remember complex passwords</string>
   <!-- This appears as the description body of the modal bottom sheet introducing why passkey beneficial on the safety side. [CHAR LIMIT=200] -->
-  <string name="passkey_creation_intro_body_fingerprint">Use your fingerprint, face, or screen lock to create a unique passkey</string>
+  <string name="passkey_creation_intro_body_fingerprint">Passkeys are encrypted digital keys you create using your fingerprint, face, or screen lock</string>
   <!-- This appears as the description body of the modal bottom sheet introducing why passkey beneficial on the using other devices side. [CHAR LIMIT=200] -->
-  <string name="passkey_creation_intro_body_device">Passkeys are saved to a password manager, so you can sign in on other devices</string>
+  <string name="passkey_creation_intro_body_device">They are saved to a password manager, so you can sign in on other devices</string>
   <!-- This appears as the title of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] -->
   <string name="choose_provider_title">Choose where to <xliff:g id="createTypes" example="create your passkeys">%1$s</xliff:g></string>
   <!-- Create types which are inserted as a placeholder for string choose_provider_title. [CHAR LIMIT=200] -->
@@ -33,26 +35,23 @@
   <string name="save_your_sign_in_info">save your sign-in info</string>
 
   <!-- This appears as the description body of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] -->
-  <string name="choose_provider_body">Set a default password manager to save your passwords and passkeys and sign in faster next time.</string>
+  <string name="choose_provider_body">Select a password manager to save your info and sign in faster next time.</string>
   <!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is passkey. [CHAR LIMIT=200] -->
-  <string name="choose_create_option_passkey_title">Create a passkey in <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g>?</string>
+  <string name="choose_create_option_passkey_title">Create passkey for <xliff:g id="appName" example="Tribank">%1$s</xliff:g>?</string>
   <!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is password. [CHAR LIMIT=200] -->
-  <string name="choose_create_option_password_title">Save your password to <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g>?</string>
+  <string name="choose_create_option_password_title">Save password for <xliff:g id="appName" example="Tribank">%1$s</xliff:g>?</string>
   <!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is others. [CHAR LIMIT=200] -->
-  <string name="choose_create_option_sign_in_title">Save your sign-in info to <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g>?</string>
+  <string name="choose_create_option_sign_in_title">Save sign-in info for <xliff:g id="appName" example="Tribank">%1$s</xliff:g>?</string>
   <!-- This appears as the description body of the modal bottom sheet for users to choose the create option inside a provider. [CHAR LIMIT=200] -->
-  <string name="choose_create_option_description">You can use your <xliff:g id="appDomainName" example="Tribank">%1$s</xliff:g> <xliff:g id="type" example="passkey">%2$s</xliff:g> on any device. It is saved to <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%3$s</xliff:g> for <xliff:g id="createInfoDisplayName" example="elisa.beckett@gmail.com">%4$s</xliff:g></string>
-  <!-- Types which are inserted as a placeholder for string choose_create_option_description. [CHAR LIMIT=200] -->
+  <string name="choose_create_option_description">You can use your <xliff:g id="appDomainName" example="Tribank">%1$s</xliff:g> <xliff:g id="credentialTypes" example="passkey">%2$s</xliff:g> on any device. It is saved to <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%3$s</xliff:g> for <xliff:g id="createInfoDisplayName" example="elisa.beckett@gmail.com">%4$s</xliff:g>.</string>
+  <!-- Types which are inserted as a placeholder as credentialTypes for other strings. [CHAR LIMIT=200] -->
   <string name="passkey">passkey</string>
   <string name="password">password</string>
   <string name="sign_ins">sign-ins</string>
+  <string name="sign_in_info">sign-in info</string>
 
-  <!-- This appears as the title of the modal bottom sheet for users to choose other available places the created passkey can be created to. [CHAR LIMIT=200] -->
-  <string name="create_passkey_in_title">Create passkey in</string>
   <!-- This appears as the title of the modal bottom sheet for users to choose other available places the created password can be saved to. [CHAR LIMIT=200] -->
-  <string name="save_password_to_title">Save password to</string>
-  <!-- This appears as the title of the modal bottom sheet for users to choose other available places the created other credential types can be saved to. [CHAR LIMIT=200] -->
-  <string name="save_sign_in_to_title">Save sign-in to</string>
+  <string name="save_credential_to_title">Save <xliff:g id="credentialTypes" example="passkey">%1$s</xliff:g> to</string>
   <!-- This appears as the title of the modal bottom sheet for users to choose to create a passkey on another device. [CHAR LIMIT=200] -->
   <string name="create_passkey_in_other_device_title">Create a passkey in another device?</string>
   <!-- This appears as the title of the modal bottom sheet for users to confirm whether they should use the selected provider as default or not. [CHAR LIMIT=200] -->
@@ -65,11 +64,13 @@
   <!-- Button label to set the selected provider on the modal bottom sheet not as default but just use once. [CHAR LIMIT=40] -->
   <string name="use_once">Use once</string>
   <!-- Appears as an option row subtitle to show how many passwords and passkeys are saved in this option when there are passwords and passkeys. [CHAR LIMIT=80] -->
-  <string name="more_options_usage_passwords_passkeys"><xliff:g id="passwordsNumber" example="1">%1$s</xliff:g> passwords, <xliff:g id="passkeysNumber" example="2">%2$s</xliff:g> passkeys</string>
+  <string name="more_options_usage_passwords_passkeys"><xliff:g id="passwordsNumber" example="1">%1$s</xliff:g> passwords • <xliff:g id="passkeysNumber" example="2">%2$s</xliff:g> passkeys</string>
   <!-- Appears as an option row subtitle to show how many passwords and passkeys are saved in this option when there are only passwords. [CHAR LIMIT=80] -->
   <string name="more_options_usage_passwords"><xliff:g id="passwordsNumber" example="3">%1$s</xliff:g> passwords</string>
   <!-- Appears as an option row subtitle to show how many passwords and passkeys are saved in this option when there are only passkeys. [CHAR LIMIT=80] -->
   <string name="more_options_usage_passkeys"><xliff:g id="passkeysNumber" example="4">%1$s</xliff:g> passkeys</string>
+  <!-- Appears as an option row subtitle to show how many total credentials are saved in this option when the request type is other sign-ins. [CHAR LIMIT=80] -->
+  <string name="more_options_usage_credentials"><xliff:g id="totalCredentialsNumber" example="5">%1$s</xliff:g> credentials</string>
   <!-- Appears before a request display name when the credential type is passkey . [CHAR LIMIT=80] -->
   <string name="passkey_before_subtitle">Passkey</string>
   <!-- Appears as an option row title that users can choose to use another device for this creation. [CHAR LIMIT=80] -->
@@ -92,8 +93,8 @@
   <string name="get_dialog_title_choose_sign_in_for">Choose a saved sign-in for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
   <!-- Appears as an option row for viewing all the available sign-in options. [CHAR LIMIT=80] -->
   <string name="get_dialog_use_saved_passkey_for">Sign in another way</string>
-  <!-- Button label to close the dialog when the user does not want to use any sign-in. [CHAR LIMIT=40] -->
-  <string name="get_dialog_button_label_no_thanks">No thanks</string>
+  <!-- Appears as a text button in the snackbar for users to click to view all options. [CHAR LIMIT=80] -->
+  <string name="snackbar_action">View options</string>
   <!-- Button label to continue with the selected sign-in. [CHAR LIMIT=40] -->
   <string name="get_dialog_button_label_continue">Continue</string>
   <!-- Separator for sign-in type and username in a sign-in entry. -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index d7ce532..6a6a3c5 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -36,13 +36,14 @@
 import android.credentials.ui.BaseDialogResult
 import android.credentials.ui.ProviderPendingIntentResponse
 import android.credentials.ui.UserSelectionDialogResult
-import android.graphics.drawable.Icon
 import android.os.Binder
 import android.os.Bundle
 import android.os.ResultReceiver
 import android.service.credentials.CredentialProviderService
 import android.util.ArraySet
-import com.android.credentialmanager.createflow.CreateCredentialUiState
+import com.android.credentialmanager.createflow.RequestDisplayInfo
+import com.android.credentialmanager.createflow.EnabledProviderInfo
+import com.android.credentialmanager.createflow.DisabledProviderInfo
 import com.android.credentialmanager.getflow.GetCredentialUiState
 import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest.Companion.toBundle
 import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest
@@ -67,7 +68,7 @@
     requestInfo = intent.extras?.getParcelable(
       RequestInfo.EXTRA_REQUEST_INFO,
       RequestInfo::class.java
-    ) ?: testGetRequestInfo()
+    ) ?: testCreatePasskeyRequestInfo()
 
     providerEnabledList = when (requestInfo.type) {
       RequestInfo.TYPE_CREATE ->
@@ -134,19 +135,24 @@
     )
   }
 
-  fun createCredentialInitialUiState(): CreateCredentialUiState {
-    val requestDisplayInfo = CreateFlowUtils.toRequestDisplayInfo(requestInfo, context)
+  fun getCreateProviderEnableListInitialUiState(): List<EnabledProviderInfo> {
     val providerEnabledList = CreateFlowUtils.toEnabledProviderList(
       // Handle runtime cast error
-      providerEnabledList as List<CreateCredentialProviderData>, requestDisplayInfo, context)
-    val providerDisabledList = CreateFlowUtils.toDisabledProviderList(
-      // Handle runtime cast error
-      providerDisabledList, context)
+      providerEnabledList as List<CreateCredentialProviderData>, context)
     providerEnabledList.forEach{providerInfo -> providerInfo.createOptions =
       providerInfo.createOptions.sortedWith(compareBy { it.lastUsedTimeMillis }).reversed()
     }
-    return CreateFlowUtils.toCreateCredentialUiState(
-      providerEnabledList, providerDisabledList, requestDisplayInfo, false)
+    return providerEnabledList
+  }
+
+  fun getCreateProviderDisableListInitialUiState(): List<DisabledProviderInfo>? {
+    return CreateFlowUtils.toDisabledProviderList(
+      // Handle runtime cast error
+      providerDisabledList, context)
+  }
+
+  fun getCreateRequestDisplayInfoInitialUiState(): RequestDisplayInfo {
+    return CreateFlowUtils.toRequestDisplayInfo(requestInfo, context)
   }
 
   companion object {
@@ -167,33 +173,33 @@
 
   // TODO: below are prototype functionalities. To be removed for productionization.
   private fun testCreateCredentialEnabledProviderList(): List<CreateCredentialProviderData> {
-    return listOf(
-      CreateCredentialProviderData
-        .Builder("io.enpass.app")
-        .setSaveEntries(
-          listOf<Entry>(
-            newCreateEntry("key1", "subkey-1", "elisa.beckett@gmail.com",
-              20, 7, 27, 10000),
-            newCreateEntry("key1", "subkey-2", "elisa.work@google.com",
-              20, 7, 27, 11000),
-          )
-        )
-        .setRemoteEntry(
-          newRemoteEntry("key2", "subkey-1")
-        )
-        .build(),
-      CreateCredentialProviderData
-        .Builder("com.dashlane")
-        .setSaveEntries(
-          listOf<Entry>(
-            newCreateEntry("key1", "subkey-3", "elisa.beckett@dashlane.com",
-              20, 7, 27, 30000),
-            newCreateEntry("key1", "subkey-4", "elisa.work@dashlane.com",
-              20, 7, 27, 31000),
-          )
-        )
-        .build(),
-    )
+      return listOf(
+          CreateCredentialProviderData
+              .Builder("io.enpass.app")
+              .setSaveEntries(
+                  listOf<Entry>(
+                      newCreateEntry("key1", "subkey-1", "elisa.beckett@gmail.com",
+                          20, 7, 27, 10000),
+                      newCreateEntry("key1", "subkey-2", "elisa.work@google.com",
+                          20, 7, 27, 11000),
+                  )
+              )
+              .setRemoteEntry(
+                  newRemoteEntry("key2", "subkey-1")
+              )
+              .build(),
+          CreateCredentialProviderData
+              .Builder("com.dashlane")
+              .setSaveEntries(
+                  listOf<Entry>(
+                      newCreateEntry("key1", "subkey-3", "elisa.beckett@dashlane.com",
+                          20, 7, 27, 30000),
+                      newCreateEntry("key1", "subkey-4", "elisa.work@dashlane.com",
+                          20, 7, 27, 31000),
+                  )
+              )
+              .build(),
+      )
   }
 
   private fun testDisabledProviderList(): List<DisabledProviderData>? {
@@ -312,8 +318,7 @@
 
         val credentialEntry = CredentialEntry(credentialType, credentialTypeDisplayName, userName,
                 userDisplayName, pendingIntent, lastUsedTimeMillis
-                ?: 0L, Icon.createWithResource(context, R.drawable.ic_passkey),
-                false)
+                ?: 0L, null, false)
 
         return Entry(
                 key,
@@ -322,7 +327,7 @@
                 pendingIntent,
                 null
         )
-    }
+  }
 
     private fun newCreateEntry(
             key: String,
@@ -351,7 +356,7 @@
 
         val createEntry = CreateEntry(
                 providerDisplayName, pendingIntent,
-                Icon.createWithResource(context, R.drawable.ic_profile), lastUsedTimeMillis,
+                null, lastUsedTimeMillis,
                 listOf(
                         CredentialCountInformation.createPasswordCountInformation(passwordCount),
                         CredentialCountInformation.createPublicKeyCountInformation(passkeyCount),
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 22b2be9..48aebec 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -123,8 +123,7 @@
           userName = credentialEntry.username.toString(),
           displayName = credentialEntry.displayName?.toString(),
           // TODO: proper fallback
-          icon = credentialEntry.icon?.loadDrawable(context)
-                  ?: context.getDrawable(R.drawable.ic_other_sign_in)!!,
+          icon = credentialEntry.icon?.loadDrawable(context),
           lastUsedTimeMillis = credentialEntry.lastUsedTimeMillis,
         )
       }
@@ -196,9 +195,8 @@
 
     fun toEnabledProviderList(
       providerDataList: List<CreateCredentialProviderData>,
-      requestDisplayInfo: RequestDisplayInfo,
       context: Context,
-    ): List<com.android.credentialmanager.createflow.EnabledProviderInfo> {
+    ): List<EnabledProviderInfo> {
       // TODO: get from the actual service info
       val packageManager = context.packageManager
 
@@ -219,7 +217,7 @@
           name = it.providerFlattenedComponentName,
           displayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString(),
           createOptions = toCreationOptionInfoList(
-            it.providerFlattenedComponentName, it.saveEntries, requestDisplayInfo, context),
+            it.providerFlattenedComponentName, it.saveEntries, context),
           remoteEntry = toRemoteInfo(it.providerFlattenedComponentName, it.remoteEntry),
         )
       }
@@ -228,14 +226,14 @@
     fun toDisabledProviderList(
       providerDataList: List<DisabledProviderData>?,
       context: Context,
-    ): List<com.android.credentialmanager.createflow.DisabledProviderInfo>? {
+    ): List<DisabledProviderInfo>? {
       // TODO: get from the actual service info
       val packageManager = context.packageManager
       return providerDataList?.map {
         val pkgInfo = packageManager
           .getPackageInfo(it.providerFlattenedComponentName,
             PackageManager.PackageInfoFlags.of(0))
-        com.android.credentialmanager.createflow.DisabledProviderInfo(
+        DisabledProviderInfo(
           icon = pkgInfo.applicationInfo.loadIcon(packageManager)!!,
           name = it.providerFlattenedComponentName,
           displayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString(),
@@ -295,14 +293,15 @@
     fun toCreateCredentialUiState(
       enabledProviders: List<EnabledProviderInfo>,
       disabledProviders: List<DisabledProviderInfo>?,
+      defaultProviderId: String?,
       requestDisplayInfo: RequestDisplayInfo,
       isOnPasskeyIntroStateAlready: Boolean,
+      isPasskeyFirstUse: Boolean,
     ): CreateCredentialUiState {
       var createOptionSize = 0
       var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
       var remoteEntry: RemoteInfo? = null
       var defaultProvider: EnabledProviderInfo? = null
-      val defaultProviderId = UserConfigRepo.getInstance().getDefaultProviderId()
       enabledProviders.forEach {
           enabledProvider ->
         if (defaultProviderId != null) {
@@ -322,13 +321,18 @@
         enabledProviders = enabledProviders,
         disabledProviders = disabledProviders,
         toCreateScreenState(
-          createOptionSize, isOnPasskeyIntroStateAlready,
-          requestDisplayInfo, defaultProvider, remoteEntry),
+          /*createOptionSize=*/createOptionSize,
+          /*isOnPasskeyIntroStateAlready=*/isOnPasskeyIntroStateAlready,
+          /*requestDisplayInfo=*/requestDisplayInfo,
+          /*defaultProvider=*/defaultProvider, /*remoteEntry=*/remoteEntry,
+          /*isPasskeyFirstUse=*/isPasskeyFirstUse),
         requestDisplayInfo,
-        isOnPasskeyIntroStateAlready,
+        defaultProvider != null,
         toActiveEntry(
-          /*defaultProvider=*/defaultProvider, createOptionSize,
-          lastSeenProviderWithNonEmptyCreateOptions, remoteEntry),
+          /*defaultProvider=*/defaultProvider,
+          /*createOptionSize=*/createOptionSize,
+          /*lastSeenProviderWithNonEmptyCreateOptions=*/lastSeenProviderWithNonEmptyCreateOptions,
+          /*remoteEntry=*/remoteEntry),
       )
     }
 
@@ -338,9 +342,10 @@
       requestDisplayInfo: RequestDisplayInfo,
       defaultProvider: EnabledProviderInfo?,
       remoteEntry: RemoteInfo?,
+      isPasskeyFirstUse: Boolean,
     ): CreateScreenState {
       return if (
-        UserConfigRepo.getInstance().getIsFirstUse() && requestDisplayInfo
+        isPasskeyFirstUse && requestDisplayInfo
           .type == TYPE_PUBLIC_KEY_CREDENTIAL && !isOnPasskeyIntroStateAlready) {
         CreateScreenState.PASSKEY_INTRO
       } else if (
@@ -382,7 +387,6 @@
     private fun toCreationOptionInfoList(
       providerId: String,
       creationEntries: List<Entry>,
-      requestDisplayInfo: RequestDisplayInfo,
       context: Context,
     ): List<CreateOptionInfo> {
       return creationEntries.map {
@@ -397,8 +401,7 @@
           pendingIntent = it.pendingIntent,
           fillInIntent = it.frameworkExtrasIntent,
           userProviderDisplayName = createEntry.accountName.toString(),
-          profileIcon = createEntry.icon?.loadDrawable(context)
-                  ?: requestDisplayInfo.typeIcon,
+          profileIcon = createEntry.icon?.loadDrawable(context),
           passwordCount = CredentialCountInformation.getPasswordCount(
                   createEntry.credentialCountInformationList) ?: 0,
           passkeyCount = CredentialCountInformation.getPasskeyCount(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt
index 5e77663..021dcab 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt
@@ -32,7 +32,7 @@
         }
     }
 
-    fun setIsFirstUse(
+    fun setIsPasskeyFirstUse(
         isFirstUse: Boolean
     ) {
         sharedPreferences.edit().apply {
@@ -45,7 +45,7 @@
         return sharedPreferences.getString(DEFAULT_PROVIDER, null)
     }
 
-    fun getIsFirstUse(): Boolean {
+    fun getIsPasskeyFirstUse(): Boolean {
         return sharedPreferences.getBoolean(IS_PASSKEY_FIRST_USE, true)
     }
 
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/CancelButton.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt
similarity index 95%
rename from packages/CredentialManager/src/com/android/credentialmanager/common/ui/CancelButton.kt
rename to packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt
index 80764b5..d0271ab 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/CancelButton.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt
@@ -23,7 +23,7 @@
 import androidx.compose.runtime.Composable
 
 @Composable
-fun CancelButton(text: String, onClick: () -> Unit) {
+fun ActionButton(text: String, onClick: () -> Unit) {
     TextButton(
         onClick = onClick,
         colors = ButtonDefaults.textButtonColors(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 38e2caa..3d23613 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -14,14 +14,11 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.material3.ButtonDefaults
 import androidx.compose.material3.Divider
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-import androidx.compose.material3.TextButton
 import androidx.compose.material3.TopAppBar
 import androidx.compose.material3.TopAppBarDefaults
 import androidx.compose.material.icons.Icons
@@ -43,7 +40,7 @@
 import com.android.credentialmanager.common.material.ModalBottomSheetLayout
 import com.android.credentialmanager.common.material.ModalBottomSheetValue
 import com.android.credentialmanager.common.material.rememberModalBottomSheetState
-import com.android.credentialmanager.common.ui.CancelButton
+import com.android.credentialmanager.common.ui.ActionButton
 import com.android.credentialmanager.common.ui.ConfirmButton
 import com.android.credentialmanager.common.ui.Entry
 import com.android.credentialmanager.common.ui.TextOnSurface
@@ -79,28 +76,30 @@
                         requestDisplayInfo = uiState.requestDisplayInfo,
                         enabledProviderList = uiState.enabledProviders,
                         disabledProviderList = uiState.disabledProviders,
-                        onCancel = viewModel::onCancel,
                         onOptionSelected = viewModel::onEntrySelectedFromFirstUseScreen,
                         onDisabledPasswordManagerSelected =
                         viewModel::onDisabledPasswordManagerSelected,
-                        onRemoteEntrySelected = viewModel::onEntrySelected,
+                        onMoreOptionsSelected = viewModel::onMoreOptionsSelectedOnProviderSelection,
                     )
                     CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
                         requestDisplayInfo = uiState.requestDisplayInfo,
                         enabledProviderList = uiState.enabledProviders,
                         providerInfo = uiState.activeEntry?.activeProvider!!,
                         createOptionInfo = uiState.activeEntry.activeEntryInfo as CreateOptionInfo,
-                        showActiveEntryOnly = uiState.showActiveEntryOnly,
                         onOptionSelected = viewModel::onEntrySelected,
                         onConfirm = viewModel::onConfirmEntrySelected,
-                        onCancel = viewModel::onCancel,
-                        onMoreOptionsSelected = viewModel::onMoreOptionsSelected,
+                        onMoreOptionsSelected = viewModel::onMoreOptionsSelectedOnCreationSelection,
                     )
                     CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
                         requestDisplayInfo = uiState.requestDisplayInfo,
                         enabledProviderList = uiState.enabledProviders,
                         disabledProviderList = uiState.disabledProviders,
-                        onBackButtonSelected = viewModel::onBackButtonSelected,
+                        hasDefaultProvider = uiState.hasDefaultProvider,
+                        isFromProviderSelection = uiState.isFromProviderSelection!!,
+                        onBackProviderSelectionButtonSelected =
+                        viewModel::onBackProviderSelectionButtonSelected,
+                        onBackCreationSelectionButtonSelected =
+                        viewModel::onBackCreationSelectionButtonSelected,
                         onOptionSelected = viewModel::onEntrySelectedFromMoreOptionScreen,
                         onDisabledPasswordManagerSelected =
                         viewModel::onDisabledPasswordManagerSelected,
@@ -172,7 +171,7 @@
                 TextSecondary(
                     text = stringResource(R.string.passkey_creation_intro_body_password),
                     style = MaterialTheme.typography.bodyMedium,
-                    modifier = Modifier.padding(start = 16.dp),
+                    modifier = Modifier.padding(start = 16.dp, end = 4.dp),
                 )
             }
             Divider(
@@ -192,7 +191,7 @@
                 TextSecondary(
                     text = stringResource(R.string.passkey_creation_intro_body_fingerprint),
                     style = MaterialTheme.typography.bodyMedium,
-                    modifier = Modifier.padding(start = 16.dp),
+                    modifier = Modifier.padding(start = 16.dp, end = 4.dp),
                 )
             }
             Divider(
@@ -212,7 +211,7 @@
                 TextSecondary(
                     text = stringResource(R.string.passkey_creation_intro_body_device),
                     style = MaterialTheme.typography.bodyMedium,
-                    modifier = Modifier.padding(start = 16.dp),
+                    modifier = Modifier.padding(start = 16.dp, end = 4.dp),
                 )
             }
             Divider(
@@ -223,7 +222,7 @@
                 horizontalArrangement = Arrangement.SpaceBetween,
                 modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
             ) {
-                CancelButton(
+                ActionButton(
                     stringResource(R.string.string_cancel),
                     onClick = onCancel
                 )
@@ -249,8 +248,7 @@
     disabledProviderList: List<DisabledProviderInfo>?,
     onOptionSelected: (ActiveEntry) -> Unit,
     onDisabledPasswordManagerSelected: () -> Unit,
-    onCancel: () -> Unit,
-    onRemoteEntrySelected: (EntryInfo) -> Unit,
+    onMoreOptionsSelected: () -> Unit,
 ) {
     ContainerCard() {
         Column() {
@@ -301,124 +299,7 @@
                         enabledProviderInfo.createOptions.forEach { createOptionInfo ->
                             item {
                                 MoreOptionsInfoRow(
-                                    providerInfo = enabledProviderInfo,
-                                    createOptionInfo = createOptionInfo,
-                                    onOptionSelected = {
-                                        onOptionSelected(
-                                            ActiveEntry(
-                                                enabledProviderInfo,
-                                                createOptionInfo
-                                            )
-                                        )
-                                    })
-                            }
-                        }
-                    }
-                    if (disabledProviderList != null && disabledProviderList.isNotEmpty()) {
-                        item {
-                            MoreOptionsDisabledProvidersRow(
-                                disabledProviders = disabledProviderList,
-                                onDisabledPasswordManagerSelected =
-                                onDisabledPasswordManagerSelected,
-                            )
-                        }
-                    }
-                }
-            }
-            // TODO: handle the error situation that if multiple remoteInfos exists
-            enabledProviderList.forEach { enabledProvider ->
-                if (enabledProvider.remoteEntry != null) {
-                    TextButton(
-                        onClick = {
-                            onRemoteEntrySelected(enabledProvider.remoteEntry!!)
-                        },
-                        modifier = Modifier
-                            .padding(horizontal = 24.dp)
-                            .align(alignment = Alignment.CenterHorizontally),
-                        colors = ButtonDefaults.textButtonColors(
-                            contentColor = MaterialTheme.colorScheme.primary,
-                        )
-                    ) {
-                        Text(
-                            text = stringResource(R.string.string_save_to_another_device),
-                            textAlign = TextAlign.Center,
-                        )
-                    }
-                }
-            }
-            Divider(
-                thickness = 24.dp,
-                color = Color.Transparent
-            )
-            Row(
-                horizontalArrangement = Arrangement.Start,
-                modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
-            ) {
-                CancelButton(stringResource(R.string.string_cancel), onCancel)
-            }
-            Divider(
-                thickness = 18.dp,
-                color = Color.Transparent,
-                modifier = Modifier.padding(bottom = 16.dp)
-            )
-        }
-    }
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun MoreOptionsSelectionCard(
-    requestDisplayInfo: RequestDisplayInfo,
-    enabledProviderList: List<EnabledProviderInfo>,
-    disabledProviderList: List<DisabledProviderInfo>?,
-    onBackButtonSelected: () -> Unit,
-    onOptionSelected: (ActiveEntry) -> Unit,
-    onDisabledPasswordManagerSelected: () -> Unit,
-    onRemoteEntrySelected: (EntryInfo) -> Unit,
-) {
-    ContainerCard() {
-        Column() {
-            TopAppBar(
-                title = {
-                    TextOnSurface(
-                        text = when (requestDisplayInfo.type) {
-                            TYPE_PUBLIC_KEY_CREDENTIAL ->
-                                stringResource(R.string.create_passkey_in_title)
-                            TYPE_PASSWORD_CREDENTIAL ->
-                                stringResource(R.string.save_password_to_title)
-                            else -> stringResource(R.string.save_sign_in_to_title)
-                        },
-                        style = MaterialTheme.typography.titleMedium,
-                    )
-                },
-                navigationIcon = {
-                    IconButton(onClick = onBackButtonSelected) {
-                        Icon(
-                            Icons.Filled.ArrowBack,
-                            stringResource(R.string.accessibility_back_arrow_button)
-                        )
-                    }
-                },
-                colors = TopAppBarDefaults.smallTopAppBarColors
-                    (containerColor = Color.Transparent),
-            )
-            Divider(
-                thickness = 8.dp,
-                color = Color.Transparent
-            )
-            ContainerCard(
-                shape = MaterialTheme.shapes.medium,
-                modifier = Modifier
-                    .padding(horizontal = 24.dp)
-                    .align(alignment = Alignment.CenterHorizontally)
-            ) {
-                LazyColumn(
-                    verticalArrangement = Arrangement.spacedBy(2.dp)
-                ) {
-                    enabledProviderList.forEach { enabledProviderInfo ->
-                        enabledProviderInfo.createOptions.forEach { createOptionInfo ->
-                            item {
-                                MoreOptionsInfoRow(
+                                    requestDisplayInfo = requestDisplayInfo,
                                     providerInfo = enabledProviderInfo,
                                     createOptionInfo = createOptionInfo,
                                     onOptionSelected = {
@@ -439,6 +320,124 @@
                             onDisabledPasswordManagerSelected,
                         )
                     }
+                }
+            }
+            Divider(
+                thickness = 24.dp,
+                color = Color.Transparent
+            )
+            // TODO: handle the error situation that if multiple remoteInfos exists
+            enabledProviderList.forEach { enabledProvider ->
+                if (enabledProvider.remoteEntry != null) {
+                    Row(
+                        horizontalArrangement = Arrangement.Start,
+                        modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+                    ) {
+                        ActionButton(
+                            stringResource(R.string.string_more_options),
+                            onMoreOptionsSelected
+                        )
+                    }
+                }
+            }
+            Divider(
+                thickness = 18.dp,
+                color = Color.Transparent,
+                modifier = Modifier.padding(bottom = 16.dp)
+            )
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun MoreOptionsSelectionCard(
+    requestDisplayInfo: RequestDisplayInfo,
+    enabledProviderList: List<EnabledProviderInfo>,
+    disabledProviderList: List<DisabledProviderInfo>?,
+    hasDefaultProvider: Boolean,
+    isFromProviderSelection: Boolean,
+    onBackProviderSelectionButtonSelected: () -> Unit,
+    onBackCreationSelectionButtonSelected: () -> Unit,
+    onOptionSelected: (ActiveEntry) -> Unit,
+    onDisabledPasswordManagerSelected: () -> Unit,
+    onRemoteEntrySelected: (EntryInfo) -> Unit,
+) {
+    ContainerCard() {
+        Column() {
+            TopAppBar(
+                title = {
+                    TextOnSurface(
+                        text =
+                        stringResource(
+                            R.string.save_credential_to_title,
+                            when (requestDisplayInfo.type) {
+                                TYPE_PUBLIC_KEY_CREDENTIAL ->
+                                    stringResource(R.string.passkey)
+                                TYPE_PASSWORD_CREDENTIAL ->
+                                    stringResource(R.string.password)
+                                else -> stringResource(R.string.sign_in_info)
+                            }),
+                        style = MaterialTheme.typography.titleMedium,
+                    )
+                },
+                navigationIcon = {
+                    IconButton(
+                        onClick =
+                        if (isFromProviderSelection)
+                            onBackProviderSelectionButtonSelected
+                        else onBackCreationSelectionButtonSelected
+                    ) {
+                        Icon(
+                            Icons.Filled.ArrowBack,
+                            stringResource(R.string.accessibility_back_arrow_button)
+                        )
+                    }
+                },
+                colors = TopAppBarDefaults.smallTopAppBarColors
+                    (containerColor = Color.Transparent),
+                modifier = Modifier.padding(top = 12.dp)
+            )
+            Divider(
+                thickness = 8.dp,
+                color = Color.Transparent
+            )
+            ContainerCard(
+                shape = MaterialTheme.shapes.medium,
+                modifier = Modifier
+                    .padding(horizontal = 24.dp)
+                    .align(alignment = Alignment.CenterHorizontally)
+            ) {
+                LazyColumn(
+                    verticalArrangement = Arrangement.spacedBy(2.dp)
+                ) {
+                    if (hasDefaultProvider) {
+                        enabledProviderList.forEach { enabledProviderInfo ->
+                            enabledProviderInfo.createOptions.forEach { createOptionInfo ->
+                                item {
+                                    MoreOptionsInfoRow(
+                                        requestDisplayInfo = requestDisplayInfo,
+                                        providerInfo = enabledProviderInfo,
+                                        createOptionInfo = createOptionInfo,
+                                        onOptionSelected = {
+                                            onOptionSelected(
+                                                ActiveEntry(
+                                                    enabledProviderInfo,
+                                                    createOptionInfo
+                                                )
+                                            )
+                                        })
+                                }
+                            }
+                        }
+                        item {
+                            MoreOptionsDisabledProvidersRow(
+                                disabledProviders = disabledProviderList,
+                                onDisabledPasswordManagerSelected =
+                                onDisabledPasswordManagerSelected,
+                            )
+                        }
+                    }
                     // TODO: handle the error situation that if multiple remoteInfos exists
                     enabledProviderList.forEach {
                         if (it.remoteEntry != null) {
@@ -453,7 +452,7 @@
                 }
             }
             Divider(
-                thickness = 18.dp,
+                thickness = 8.dp,
                 color = Color.Transparent,
                 modifier = Modifier.padding(bottom = 40.dp)
             )
@@ -496,7 +495,7 @@
                 horizontalArrangement = Arrangement.SpaceBetween,
                 modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
             ) {
-                CancelButton(
+                ActionButton(
                     stringResource(R.string.use_once),
                     onClick = onUseOnceSelected
                 )
@@ -521,34 +520,42 @@
     enabledProviderList: List<EnabledProviderInfo>,
     providerInfo: EnabledProviderInfo,
     createOptionInfo: CreateOptionInfo,
-    showActiveEntryOnly: Boolean,
     onOptionSelected: (EntryInfo) -> Unit,
     onConfirm: () -> Unit,
-    onCancel: () -> Unit,
     onMoreOptionsSelected: () -> Unit,
 ) {
     ContainerCard() {
         Column() {
+            Divider(
+                thickness = 24.dp,
+                color = Color.Transparent
+            )
             Icon(
                 bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
                 contentDescription = null,
                 tint = Color.Unspecified,
-                modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
-                    .padding(all = 24.dp).size(32.dp)
+                modifier = Modifier.align(alignment = Alignment.CenterHorizontally).size(32.dp)
+            )
+            TextSecondary(
+                text = providerInfo.displayName,
+                style = MaterialTheme.typography.titleLarge,
+                modifier = Modifier.padding(vertical = 10.dp)
+                    .align(alignment = Alignment.CenterHorizontally),
+                textAlign = TextAlign.Center,
             )
             TextOnSurface(
                 text = when (requestDisplayInfo.type) {
                     TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(
                         R.string.choose_create_option_passkey_title,
-                        providerInfo.displayName
+                        requestDisplayInfo.appDomainName
                     )
                     TYPE_PASSWORD_CREDENTIAL -> stringResource(
                         R.string.choose_create_option_password_title,
-                        providerInfo.displayName
+                        requestDisplayInfo.appDomainName
                     )
                     else -> stringResource(
                         R.string.choose_create_option_sign_in_title,
-                        providerInfo.displayName
+                        requestDisplayInfo.appDomainName
                     )
                 },
                 style = MaterialTheme.typography.titleMedium,
@@ -556,6 +563,51 @@
                     .align(alignment = Alignment.CenterHorizontally),
                 textAlign = TextAlign.Center,
             )
+            ContainerCard(
+                shape = MaterialTheme.shapes.medium,
+                modifier = Modifier
+                    .padding(all = 24.dp)
+                    .align(alignment = Alignment.CenterHorizontally),
+            ) {
+                PrimaryCreateOptionRow(
+                    requestDisplayInfo = requestDisplayInfo,
+                    entryInfo = createOptionInfo,
+                    onOptionSelected = onOptionSelected
+                )
+            }
+            var shouldShowMoreOptionsButton = false
+            var createOptionsSize = 0
+            var remoteEntry: RemoteInfo? = null
+            enabledProviderList.forEach { enabledProvider ->
+                if (enabledProvider.remoteEntry != null) {
+                    remoteEntry = enabledProvider.remoteEntry
+                }
+                createOptionsSize += enabledProvider.createOptions.size
+            }
+            if (createOptionsSize > 1 || remoteEntry != null) {
+                shouldShowMoreOptionsButton = true
+            }
+            Row(
+                horizontalArrangement =
+                if (shouldShowMoreOptionsButton) Arrangement.SpaceBetween else Arrangement.End,
+                modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+            ) {
+                if (shouldShowMoreOptionsButton) {
+                    ActionButton(
+                        stringResource(R.string.string_more_options),
+                        onClick = onMoreOptionsSelected
+                    )
+                }
+                ConfirmButton(
+                    stringResource(R.string.string_continue),
+                    onClick = onConfirm
+                )
+            }
+            Divider(
+                thickness = 1.dp,
+                color = Color.LightGray,
+                modifier = Modifier.padding(start = 24.dp, end = 24.dp, top = 18.dp)
+            )
             if (createOptionInfo.userProviderDisplayName != null) {
                 TextSecondary(
                     text = stringResource(
@@ -570,88 +622,8 @@
                         createOptionInfo.userProviderDisplayName
                     ),
                     style = MaterialTheme.typography.bodyLarge,
-                    modifier = Modifier.padding(all = 24.dp)
-                        .align(alignment = Alignment.CenterHorizontally),
-                )
-            }
-            ContainerCard(
-                shape = MaterialTheme.shapes.medium,
-                modifier = Modifier
-                    .padding(horizontal = 24.dp)
-                    .align(alignment = Alignment.CenterHorizontally),
-            ) {
-                PrimaryCreateOptionRow(
-                    requestDisplayInfo = requestDisplayInfo,
-                    entryInfo = createOptionInfo,
-                    onOptionSelected = onOptionSelected
-                )
-            }
-            if (!showActiveEntryOnly) {
-                var createOptionsSize = 0
-                enabledProviderList.forEach { enabledProvider ->
-                    createOptionsSize += enabledProvider.createOptions.size
-                }
-                if (createOptionsSize > 1) {
-                    TextButton(
-                        onClick = onMoreOptionsSelected,
-                        modifier = Modifier
-                            .padding(horizontal = 24.dp)
-                            .align(alignment = Alignment.CenterHorizontally),
-                        colors = ButtonDefaults.textButtonColors(
-                            contentColor = MaterialTheme.colorScheme.primary,
-                        ),
-                    ) {
-                        Text(
-                            text =
-                            when (requestDisplayInfo.type) {
-                                TYPE_PUBLIC_KEY_CREDENTIAL ->
-                                    stringResource(R.string.string_create_in_another_place)
-                                else -> stringResource(R.string.string_save_to_another_place)
-                            },
-                            textAlign = TextAlign.Center,
-                        )
-                    }
-                } else if (
-                    requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL
-                ) {
-                    // TODO: handle the error situation that if multiple remoteInfos exists
-                    enabledProviderList.forEach { enabledProvider ->
-                        if (enabledProvider.remoteEntry != null) {
-                            TextButton(
-                                onClick = {
-                                    onOptionSelected(enabledProvider.remoteEntry!!)
-                                },
-                                modifier = Modifier
-                                    .padding(horizontal = 24.dp)
-                                    .align(alignment = Alignment.CenterHorizontally),
-                                colors = ButtonDefaults.textButtonColors(
-                                    contentColor = MaterialTheme.colorScheme.primary,
-                                ),
-                            ) {
-                                Text(
-                                    text = stringResource(R.string.string_use_another_device),
-                                    textAlign = TextAlign.Center,
-                                )
-                            }
-                        }
-                    }
-                }
-            }
-            Divider(
-                thickness = 24.dp,
-                color = Color.Transparent
-            )
-            Row(
-                horizontalArrangement = Arrangement.SpaceBetween,
-                modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
-            ) {
-                CancelButton(
-                    stringResource(R.string.string_cancel),
-                    onClick = onCancel
-                )
-                ConfirmButton(
-                    stringResource(R.string.string_continue),
-                    onClick = onConfirm
+                    modifier = Modifier.padding(
+                        start = 24.dp, top = 8.dp, bottom = 18.dp, end = 24.dp)
                 )
             }
             Divider(
@@ -712,7 +684,7 @@
                 horizontalArrangement = Arrangement.SpaceBetween,
                 modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
             ) {
-                CancelButton(
+                ActionButton(
                     stringResource(R.string.string_cancel),
                     onClick = onCancel
                 )
@@ -740,16 +712,20 @@
     Entry(
         onClick = { onOptionSelected(entryInfo) },
         icon = {
-            Icon(
-                bitmap = if (entryInfo is CreateOptionInfo) {
-                    entryInfo.profileIcon.toBitmap().asImageBitmap()
-                } else {
-                    requestDisplayInfo.typeIcon.toBitmap().asImageBitmap()
-                },
-                contentDescription = null,
-                tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
-                modifier = Modifier.padding(start = 10.dp).size(32.dp)
-            )
+            if (entryInfo is CreateOptionInfo && entryInfo.profileIcon != null) {
+                Image(
+                    bitmap = entryInfo.profileIcon.toBitmap().asImageBitmap(),
+                    contentDescription = null,
+                    modifier = Modifier.padding(start = 10.dp).size(32.dp),
+                )
+            } else {
+                Icon(
+                    bitmap = requestDisplayInfo.typeIcon.toBitmap().asImageBitmap(),
+                    contentDescription = null,
+                    tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
+                    modifier = Modifier.padding(start = 10.dp).size(32.dp),
+                )
+            }
         },
         label = {
             Column() {
@@ -763,9 +739,9 @@
                         )
                         TextSecondary(
                             text = if (requestDisplayInfo.subtitle != null) {
-                                stringResource(
+                                requestDisplayInfo.subtitle + " • " + stringResource(
                                     R.string.passkey_before_subtitle
-                                ) + " - " + requestDisplayInfo.subtitle
+                                )
                             } else {
                                 stringResource(R.string.passkey_before_subtitle)
                             },
@@ -787,11 +763,25 @@
                         )
                     }
                     else -> {
-                        TextOnSurfaceVariant(
-                            text = requestDisplayInfo.title,
-                            style = MaterialTheme.typography.titleLarge,
-                            modifier = Modifier.padding(top = 16.dp, bottom = 16.dp, start = 5.dp),
-                        )
+                        if (requestDisplayInfo.subtitle != null) {
+                            TextOnSurfaceVariant(
+                                text = requestDisplayInfo.title,
+                                style = MaterialTheme.typography.titleLarge,
+                                modifier = Modifier.padding(top = 16.dp, start = 5.dp),
+                            )
+                            TextOnSurfaceVariant(
+                                text = requestDisplayInfo.subtitle,
+                                style = MaterialTheme.typography.bodyMedium,
+                                modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
+                            )
+                        } else {
+                            TextOnSurfaceVariant(
+                                text = requestDisplayInfo.title,
+                                style = MaterialTheme.typography.titleLarge,
+                                modifier = Modifier.padding(
+                                    top = 16.dp, bottom = 16.dp, start = 5.dp),
+                            )
+                        }
                     }
                 }
             }
@@ -802,6 +792,7 @@
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun MoreOptionsInfoRow(
+    requestDisplayInfo: RequestDisplayInfo,
     providerInfo: EnabledProviderInfo,
     createOptionInfo: CreateOptionInfo,
     onOptionSelected: () -> Unit
@@ -826,50 +817,67 @@
                     TextSecondary(
                         text = createOptionInfo.userProviderDisplayName,
                         style = MaterialTheme.typography.bodyMedium,
-                        // TODO: update the logic here for the case there is only total count
-                        modifier = if (
-                            createOptionInfo.passwordCount != null ||
-                            createOptionInfo.passkeyCount != null
-                        ) Modifier.padding(start = 5.dp) else Modifier
-                            .padding(bottom = 16.dp, start = 5.dp),
+                        modifier = Modifier.padding(start = 5.dp),
                     )
                 }
-                if (createOptionInfo.passwordCount != null &&
-                    createOptionInfo.passkeyCount != null
-                ) {
-                    TextSecondary(
-                        text =
-                        stringResource(
-                            R.string.more_options_usage_passwords_passkeys,
-                            createOptionInfo.passwordCount,
-                            createOptionInfo.passkeyCount
-                        ),
-                        style = MaterialTheme.typography.bodyMedium,
-                        modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
-                    )
-                } else if (createOptionInfo.passwordCount != null) {
-                    TextSecondary(
-                        text =
-                        stringResource(
-                            R.string.more_options_usage_passwords,
-                            createOptionInfo.passwordCount
-                        ),
-                        style = MaterialTheme.typography.bodyMedium,
-                        modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
-                    )
-                } else if (createOptionInfo.passkeyCount != null) {
-                    TextSecondary(
-                        text =
-                        stringResource(
-                            R.string.more_options_usage_passkeys,
-                            createOptionInfo.passkeyCount
-                        ),
-                        style = MaterialTheme.typography.bodyMedium,
-                        modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
-                    )
-                } else if (createOptionInfo.totalCredentialCount != null) {
-                    // TODO: Handle the case when there is total count
-                    // but no passwords and passkeys after design is set
+                if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL ||
+                    requestDisplayInfo.type == TYPE_PASSWORD_CREDENTIAL) {
+                    if (createOptionInfo.passwordCount != null &&
+                        createOptionInfo.passkeyCount != null
+                    ) {
+                        TextSecondary(
+                            text =
+                            stringResource(
+                                R.string.more_options_usage_passwords_passkeys,
+                                createOptionInfo.passwordCount,
+                                createOptionInfo.passkeyCount
+                            ),
+                            style = MaterialTheme.typography.bodyMedium,
+                            modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
+                        )
+                    } else if (createOptionInfo.passwordCount != null) {
+                        TextSecondary(
+                            text =
+                            stringResource(
+                                R.string.more_options_usage_passwords,
+                                createOptionInfo.passwordCount
+                            ),
+                            style = MaterialTheme.typography.bodyMedium,
+                            modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
+                        )
+                    } else if (createOptionInfo.passkeyCount != null) {
+                        TextSecondary(
+                            text =
+                            stringResource(
+                                R.string.more_options_usage_passkeys,
+                                createOptionInfo.passkeyCount
+                            ),
+                            style = MaterialTheme.typography.bodyMedium,
+                            modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
+                        )
+                    } else {
+                        Divider(
+                            thickness = 16.dp,
+                            color = Color.Transparent,
+                        )
+                    }
+                } else {
+                    if (createOptionInfo.totalCredentialCount != null) {
+                        TextSecondary(
+                            text =
+                            stringResource(
+                                R.string.more_options_usage_credentials,
+                                createOptionInfo.totalCredentialCount
+                            ),
+                            style = MaterialTheme.typography.bodyMedium,
+                            modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
+                        )
+                    } else {
+                        Divider(
+                            thickness = 16.dp,
+                            color = Color.Transparent,
+                        )
+                    }
                 }
             }
         }
@@ -901,7 +909,7 @@
                     )
                     // TODO: Update the subtitle once design is confirmed
                     TextSecondary(
-                        text = disabledProviders.joinToString(separator = ", ") { it.displayName },
+                        text = disabledProviders.joinToString(separator = " • ") { it.displayName },
                         style = MaterialTheme.typography.bodyMedium,
                         modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
                     )
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
index 9d029dff..7b9e113 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
@@ -39,18 +39,39 @@
   val disabledProviders: List<DisabledProviderInfo>? = null,
   val currentScreenState: CreateScreenState,
   val requestDisplayInfo: RequestDisplayInfo,
-  val showActiveEntryOnly: Boolean,
+  // Should not change with the real time update of default provider, only determine whether we're
+  // showing provider selection page at the beginning
+  val hasDefaultProvider: Boolean,
   val activeEntry: ActiveEntry? = null,
   val selectedEntry: EntryInfo? = null,
   val hidden: Boolean = false,
   val providerActivityPending: Boolean = false,
+  val isFromProviderSelection: Boolean? = null,
 )
 
 class CreateCredentialViewModel(
-  credManRepo: CredentialManagerRepo = CredentialManagerRepo.getInstance()
+  credManRepo: CredentialManagerRepo = CredentialManagerRepo.getInstance(),
+  userConfigRepo: UserConfigRepo = UserConfigRepo.getInstance()
 ) : ViewModel() {
 
-  var uiState by mutableStateOf(credManRepo.createCredentialInitialUiState())
+  var providerEnableListUiState = credManRepo.getCreateProviderEnableListInitialUiState()
+
+  var providerDisableListUiState = credManRepo.getCreateProviderDisableListInitialUiState()
+
+  var requestDisplayInfoUiState = credManRepo.getCreateRequestDisplayInfoInitialUiState()
+
+  var defaultProviderId = userConfigRepo.getDefaultProviderId()
+
+  var isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse()
+
+  var uiState by mutableStateOf(
+    CreateFlowUtils.toCreateCredentialUiState(
+      providerEnableListUiState,
+      providerDisableListUiState,
+      defaultProviderId,
+      requestDisplayInfoUiState,
+      false,
+      isPasskeyFirstUse))
     private set
 
   val dialogResult: MutableLiveData<DialogResult> by lazy {
@@ -63,9 +84,9 @@
 
   fun onConfirmIntro() {
     uiState = CreateFlowUtils.toCreateCredentialUiState(
-      uiState.enabledProviders, uiState.disabledProviders,
-      uiState.requestDisplayInfo, true)
-    UserConfigRepo.getInstance().setIsFirstUse(false)
+      providerEnableListUiState, providerDisableListUiState, defaultProviderId,
+      requestDisplayInfoUiState, true, isPasskeyFirstUse)
+    UserConfigRepo.getInstance().setIsPasskeyFirstUse(false)
   }
 
   fun getProviderInfoByName(providerName: String): EnabledProviderInfo {
@@ -74,22 +95,35 @@
     }
   }
 
-  fun onMoreOptionsSelected() {
+  fun onMoreOptionsSelectedOnProviderSelection() {
     uiState = uiState.copy(
       currentScreenState = CreateScreenState.MORE_OPTIONS_SELECTION,
+      isFromProviderSelection = true
     )
   }
 
-  fun onBackButtonSelected() {
+  fun onMoreOptionsSelectedOnCreationSelection() {
     uiState = uiState.copy(
-        currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
+      currentScreenState = CreateScreenState.MORE_OPTIONS_SELECTION,
+      isFromProviderSelection = false
+    )
+  }
+
+  fun onBackProviderSelectionButtonSelected() {
+    uiState = uiState.copy(
+        currentScreenState = CreateScreenState.PROVIDER_SELECTION,
+    )
+  }
+
+  fun onBackCreationSelectionButtonSelected() {
+    uiState = uiState.copy(
+      currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
     )
   }
 
   fun onEntrySelectedFromMoreOptionScreen(activeEntry: ActiveEntry) {
     uiState = uiState.copy(
       currentScreenState = CreateScreenState.MORE_OPTIONS_ROW_INTRO,
-      showActiveEntryOnly = false,
       activeEntry = activeEntry
     )
   }
@@ -97,7 +131,6 @@
   fun onEntrySelectedFromFirstUseScreen(activeEntry: ActiveEntry) {
     uiState = uiState.copy(
       currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
-      showActiveEntryOnly = true,
       activeEntry = activeEntry
     )
     val providerId = uiState.activeEntry?.activeProvider?.name
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index fda0b97..58db36c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -55,7 +55,7 @@
   pendingIntent: PendingIntent?,
   fillInIntent: Intent?,
   val userProviderDisplayName: String?,
-  val profileIcon: Drawable,
+  val profileIcon: Drawable?,
   val passwordCount: Int?,
   val passkeyCount: Int?,
   val totalCredentialCount: Int?,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 619f5a3..5e7f1e0 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -62,7 +62,7 @@
 import com.android.credentialmanager.common.material.ModalBottomSheetLayout
 import com.android.credentialmanager.common.material.ModalBottomSheetValue
 import com.android.credentialmanager.common.material.rememberModalBottomSheetState
-import com.android.credentialmanager.common.ui.CancelButton
+import com.android.credentialmanager.common.ui.ActionButton
 import com.android.credentialmanager.common.ui.Entry
 import com.android.credentialmanager.common.ui.TextOnSurface
 import com.android.credentialmanager.common.ui.TextSecondary
@@ -96,7 +96,6 @@
                             requestDisplayInfo = uiState.requestDisplayInfo,
                             providerDisplayInfo = uiState.providerDisplayInfo,
                             onEntrySelected = viewModel::onEntrySelected,
-                            onCancel = viewModel::onCancel,
                             onMoreOptionSelected = viewModel::onMoreOptionSelected,
                         )
                     } else {
@@ -135,7 +134,6 @@
     requestDisplayInfo: RequestDisplayInfo,
     providerDisplayInfo: ProviderDisplayInfo,
     onEntrySelected: (EntryInfo) -> Unit,
-    onCancel: () -> Unit,
     onMoreOptionSelected: () -> Unit,
 ) {
     val sortedUserNameToCredentialEntryList =
@@ -181,9 +179,6 @@
                             onEntrySelected = onEntrySelected,
                         )
                     }
-                    item {
-                        SignInAnotherWayRow(onSelect = onMoreOptionSelected)
-                    }
                 }
             }
             Divider(
@@ -194,7 +189,9 @@
                 horizontalArrangement = Arrangement.SpaceBetween,
                 modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
             ) {
-                CancelButton(stringResource(R.string.get_dialog_button_label_no_thanks), onCancel)
+                ActionButton(
+                    stringResource(R.string.get_dialog_use_saved_passkey_for),
+                    onMoreOptionSelected)
             }
             Divider(
                 thickness = 18.dp,
@@ -425,13 +422,22 @@
     Entry(
         onClick = { onEntrySelected(credentialEntryInfo) },
         icon = {
-            Icon(
-                modifier = Modifier.padding(start = 10.dp).size(32.dp),
-                bitmap = credentialEntryInfo.icon.toBitmap().asImageBitmap(),
-                // TODO: add description.
-                contentDescription = "",
-                tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
-            )
+            if (credentialEntryInfo.icon != null) {
+                Image(
+                    modifier = Modifier.padding(start = 10.dp).size(32.dp),
+                    bitmap = credentialEntryInfo.icon.toBitmap().asImageBitmap(),
+                    // TODO: add description.
+                    contentDescription = "",
+                )
+            } else {
+                Icon(
+                    modifier = Modifier.padding(start = 10.dp).size(32.dp),
+                    painter = painterResource(R.drawable.ic_other_sign_in),
+                    // TODO: add description.
+                    contentDescription = "",
+                    tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant
+                )
+            }
         },
         label = {
             Column() {
@@ -544,49 +550,35 @@
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
-fun SignInAnotherWayRow(onSelect: () -> Unit) {
-    Entry(
-        onClick = onSelect,
-        label = {
-            TextOnSurfaceVariant(
-                text = stringResource(R.string.get_dialog_use_saved_passkey_for),
-                style = MaterialTheme.typography.titleLarge,
-                modifier = Modifier.padding(vertical = 16.dp)
-            )
-        }
-    )
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
 fun SnackBarScreen(
     onClick: (Boolean) -> Unit,
     onCancel: () -> Unit,
 ) {
     // TODO: Change the height, width and position according to the design
-    Snackbar (
-        modifier = Modifier.padding(horizontal = 80.dp).padding(top = 700.dp),
+    Snackbar(
+        modifier = Modifier.padding(horizontal = 40.dp).padding(top = 700.dp),
         shape = EntryShape.FullMediumRoundedCorner,
         containerColor = LocalAndroidColorScheme.current.colorBackground,
         contentColor = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
-    ) {
-        Row(
-            horizontalArrangement = Arrangement.SpaceBetween,
-            verticalAlignment = Alignment.CenterVertically,
-        ) {
+        action = {
             TextButton(
-                onClick = {onClick(true)},
+                onClick = { onClick(true) },
             ) {
-                Text(text = stringResource(R.string.get_dialog_use_saved_passkey_for))
+                Text(text = stringResource(R.string.snackbar_action))
             }
+        },
+        dismissAction = {
             IconButton(onClick = onCancel) {
                 Icon(
                     Icons.Filled.Close,
                     contentDescription = stringResource(
                         R.string.accessibility_close_button
-                    )
+                    ),
+                    tint = LocalAndroidColorScheme.current.colorAccentTertiary
                 )
             }
-        }
+        },
+    ) {
+        Text(text = stringResource(R.string.get_dialog_use_saved_passkey_for))
     }
 }
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index 3a2a738..60939b5 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -67,7 +67,7 @@
   val credentialTypeDisplayName: String,
   val userName: String,
   val displayName: String?,
-  val icon: Drawable,
+  val icon: Drawable?,
   val lastUsedTimeMillis: Long?,
 ) : EntryInfo(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent)
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreference.kt
new file mode 100644
index 0000000..b8db63c
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreference.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.preference
+
+import com.android.settingslib.spa.framework.util.EntryHighlight
+import androidx.compose.material3.IconButton
+import androidx.compose.runtime.State
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.material3.Icon
+
+@Composable
+fun TwoTargetButtonPreference(
+        title: String,
+        summary: State<String>,
+        icon: @Composable (() -> Unit)? = null,
+        onClick: () -> Unit,
+        buttonIcon: ImageVector,
+        buttonIconDescription: String,
+        onButtonClick: () -> Unit
+) {
+    EntryHighlight {
+        TwoTargetPreference(
+                title = title,
+                summary = summary,
+                onClick = onClick,
+                icon = icon) {
+            IconButton(onClick = onButtonClick) {
+                Icon(imageVector = buttonIcon, contentDescription = buttonIconDescription)
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreferenceTest.kt
new file mode 100644
index 0000000..3a2b445
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreferenceTest.kt
@@ -0,0 +1,73 @@
+package com.android.settingslib.spa.widget.preference
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Delete
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.toState
+import com.google.common.truth.Truth
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val TEST_MODEL_TITLE = "TwoTargetButtonPreference"
+private const val TEST_MODEL_SUMMARY = "TestSummary"
+private const val TEST_BUTTON_ICON_DESCRIPTION = "TestButtonIconDescription"
+private val TEST_BUTTON_ICON = Icons.Outlined.Delete
+
+@RunWith(AndroidJUnit4::class)
+class TwoTargetButtonPreferenceTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun title_displayed() {
+        composeTestRule.setContent {
+            testTwoTargetButtonPreference()
+        }
+
+        composeTestRule.onNodeWithText(TEST_MODEL_TITLE).assertIsDisplayed()
+    }
+
+    @Test
+    fun clickable_label_canBeClicked() {
+        var clicked = false
+        composeTestRule.setContent {
+            testTwoTargetButtonPreference(onClick = { clicked = true })
+        }
+
+        composeTestRule.onNodeWithText(TEST_MODEL_TITLE).performClick()
+        Truth.assertThat(clicked).isTrue()
+    }
+
+    @Test
+    fun clickable_button_label_canBeClicked() {
+        var clicked = false
+        composeTestRule.setContent {
+            testTwoTargetButtonPreference(onButtonClick = { clicked = true })
+        }
+
+        composeTestRule.onNodeWithContentDescription(TEST_BUTTON_ICON_DESCRIPTION).performClick()
+        Truth.assertThat(clicked).isTrue()
+    }
+}
+
+@Composable
+private fun testTwoTargetButtonPreference(
+    onClick: () -> Unit = {},
+    onButtonClick: () -> Unit = {},
+) {
+    TwoTargetButtonPreference(
+        title = TEST_MODEL_TITLE,
+        summary = TEST_MODEL_SUMMARY.toState(),
+        onClick = onClick,
+        buttonIcon = TEST_BUTTON_ICON,
+        buttonIconDescription = TEST_BUTTON_ICON_DESCRIPTION,
+        onButtonClick = onButtonClick
+    )
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index deec267..3ff1d89 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -60,6 +60,7 @@
     val listModel: AppListModel<T>,
     val state: AppListState,
     val header: @Composable () -> Unit,
+    val noItemMessage: String? = null,
     val bottomPadding: Dp,
 )
 
@@ -79,7 +80,7 @@
 ) {
     LogCompositions(TAG, config.userId.toString())
     val appListData = appListDataSupplier()
-    listModel.AppListWidget(appListData, header, bottomPadding)
+    listModel.AppListWidget(appListData, header, bottomPadding, noItemMessage)
 }
 
 @Composable
@@ -87,12 +88,14 @@
     appListData: State<AppListData<T>?>,
     header: @Composable () -> Unit,
     bottomPadding: Dp,
+    noItemMessage: String?
 ) {
     val timeMeasurer = rememberTimeMeasurer(TAG)
     appListData.value?.let { (list, option) ->
         timeMeasurer.logFirst("app list first loaded")
         if (list.isEmpty()) {
-            PlaceholderTitle(stringResource(R.string.no_applications))
+            header()
+            PlaceholderTitle(noItemMessage ?: stringResource(R.string.no_applications))
             return
         }
         LazyColumn(
@@ -151,4 +154,4 @@
     ) { viewModel.reloadApps() }
 
     return viewModel.appListDataFlow.collectAsState(null, Dispatchers.IO)
-}
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListButtonItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListButtonItem.kt
new file mode 100644
index 0000000..919793a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListButtonItem.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.app
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.vector.ImageVector
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import com.android.settingslib.spa.widget.preference.TwoTargetButtonPreference
+
+@Composable
+fun <T : AppRecord> AppListItemModel<T>.AppListButtonItem (
+    onClick: () -> Unit,
+    onButtonClick: () -> Unit,
+    buttonIcon: ImageVector,
+    buttonIconDescription: String,
+) {
+        TwoTargetButtonPreference(
+                title = label,
+                summary = this@AppListButtonItem.summary,
+                icon = { AppIcon(record.app, SettingsDimension.appIconItemSize) },
+                onClick = onClick,
+                buttonIcon = buttonIcon,
+                buttonIconDescription = buttonIconDescription,
+                onButtonClick = onButtonClick
+        )
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index 318bcd9..7d21d98 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -45,6 +45,7 @@
     listModel: AppListModel<T>,
     showInstantApps: Boolean = false,
     primaryUserOnly: Boolean = false,
+    noItemMessage: String? = null,
     moreOptions: @Composable MoreOptionsScope.() -> Unit = {},
     header: @Composable () -> Unit = {},
     appList: @Composable AppListInput<T>.() -> Unit = { AppList() },
@@ -77,6 +78,7 @@
                     ),
                     header = header,
                     bottomPadding = bottomPadding,
+                    noItemMessage = noItemMessage,
                 )
                 appList(appListInput)
             }
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 8a30a3b..888b09f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -653,6 +653,9 @@
         }
         for (UsbPort usbPort : usbPortList) {
             Log.d(tag, "usbPort: " + usbPort);
+            if (!usbPort.supportsComplianceWarnings()) {
+                continue;
+            }
             final UsbPortStatus usbStatus = usbPort.getStatus();
             if (usbStatus == null || !usbStatus.isConnected()) {
                 continue;
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
index 4da47fd..db224be 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
@@ -66,6 +66,8 @@
             "com.android.settings.category.ia.battery_saver_settings";
     public static final String CATEGORY_SMART_BATTERY_SETTINGS =
             "com.android.settings.category.ia.smart_battery_settings";
+    public static final String CATEGORY_COMMUNAL_SETTINGS =
+            "com.android.settings.category.ia.communal";
 
     public static final Map<String, String> KEY_COMPAT_MAP;
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index 68a1e19..dce1e20 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -448,25 +448,41 @@
 
     @Test
     public void containsIncompatibleChargers_returnTrue() {
-        final List<UsbPort> usbPorts = new ArrayList<>();
-        usbPorts.add(mUsbPort);
-        when(mUsbManager.getPorts()).thenReturn(usbPorts);
-        when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus);
-        when(mUsbPortStatus.isConnected()).thenReturn(true);
-        when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[]{1});
-
+        setupIncompatibleCharging();
         assertThat(Utils.containsIncompatibleChargers(mContext, "tag")).isTrue();
     }
 
     @Test
     public void containsIncompatibleChargers_emptyComplianceWarnings_returnFalse() {
+        setupIncompatibleCharging();
+        when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[1]);
+
+        assertThat(Utils.containsIncompatibleChargers(mContext, "tag")).isFalse();
+    }
+
+    @Test
+    public void containsIncompatibleChargers_notSupportComplianceWarnings_returnFalse() {
+        setupIncompatibleCharging();
+        when(mUsbPort.supportsComplianceWarnings()).thenReturn(false);
+
+        assertThat(Utils.containsIncompatibleChargers(mContext, "tag")).isFalse();
+    }
+
+    @Test
+    public void containsIncompatibleChargers_usbNotConnected_returnFalse() {
+        setupIncompatibleCharging();
+        when(mUsbPortStatus.isConnected()).thenReturn(false);
+
+        assertThat(Utils.containsIncompatibleChargers(mContext, "tag")).isFalse();
+    }
+
+    private void setupIncompatibleCharging() {
         final List<UsbPort> usbPorts = new ArrayList<>();
         usbPorts.add(mUsbPort);
         when(mUsbManager.getPorts()).thenReturn(usbPorts);
         when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus);
+        when(mUsbPort.supportsComplianceWarnings()).thenReturn(true);
         when(mUsbPortStatus.isConnected()).thenReturn(true);
-        when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[1]);
-
-        assertThat(Utils.containsIncompatibleChargers(mContext, "tag")).isFalse();
+        when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[]{1});
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
index 340a6c7..c9dc1ba 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
@@ -60,8 +60,9 @@
         allKeys.add(CategoryKey.CATEGORY_GESTURES);
         allKeys.add(CategoryKey.CATEGORY_NIGHT_DISPLAY);
         allKeys.add(CategoryKey.CATEGORY_SMART_BATTERY_SETTINGS);
+        allKeys.add(CategoryKey.CATEGORY_COMMUNAL_SETTINGS);
         // DO NOT REMOVE ANYTHING ABOVE
 
-        assertThat(allKeys.size()).isEqualTo(19);
+        assertThat(allKeys.size()).isEqualTo(20);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 25fa915..58dfe30 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -447,11 +447,11 @@
     @JvmField val NOTE_TASKS = unreleasedFlag(1900, "keycode_flag")
 
     // 2000 - device controls
-    @Keep @JvmField val USE_APP_PANELS = unreleasedFlag(2000, "use_app_panels", teamfood = true)
+    @Keep @JvmField val USE_APP_PANELS = releasedFlag(2000, "use_app_panels", teamfood = true)
 
     @JvmField
     val APP_PANELS_ALL_APPS_ALLOWED =
-        unreleasedFlag(2001, "app_panels_all_apps_allowed", teamfood = true)
+        releasedFlag(2001, "app_panels_all_apps_allowed", teamfood = true)
 
     // 2100 - Falsing Manager
     @JvmField val FALSING_FOR_LONG_TAPS = releasedFlag(2100, "falsing_for_long_taps")
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
index ab93b29..d6f941d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
@@ -58,10 +58,10 @@
 
     // Keep track of the key used for the session tokens. This information is used to know when to
     // dispatch a removed event so that a media object for a local session will be removed.
-    private val keyedTokens: MutableMap<String, MutableSet<MediaSession.Token>> = mutableMapOf()
+    private val keyedTokens: MutableMap<String, MutableSet<TokenId>> = mutableMapOf()
 
     // Keep track of which media session tokens have associated notifications.
-    private val tokensWithNotifications: MutableSet<MediaSession.Token> = mutableSetOf()
+    private val tokensWithNotifications: MutableSet<TokenId> = mutableSetOf()
 
     private val sessionListener =
         object : MediaSessionManager.OnActiveSessionsChangedListener {
@@ -101,15 +101,15 @@
         isSsReactivated: Boolean
     ) {
         backgroundExecutor.execute {
-            data.token?.let { tokensWithNotifications.add(it) }
+            data.token?.let { tokensWithNotifications.add(TokenId(it)) }
             val isMigration = oldKey != null && key != oldKey
             if (isMigration) {
                 keyedTokens.remove(oldKey)?.let { removed -> keyedTokens.put(key, removed) }
             }
             if (data.token != null) {
-                keyedTokens.get(key)?.let { tokens -> tokens.add(data.token) }
+                keyedTokens.get(key)?.let { tokens -> tokens.add(TokenId(data.token)) }
                     ?: run {
-                        val tokens = mutableSetOf(data.token)
+                        val tokens = mutableSetOf(TokenId(data.token))
                         keyedTokens.put(key, tokens)
                     }
             }
@@ -125,7 +125,7 @@
                 isMigration ||
                     remote == null ||
                     remote.sessionToken == data.token ||
-                    !tokensWithNotifications.contains(remote.sessionToken)
+                    !tokensWithNotifications.contains(TokenId(remote.sessionToken))
             ) {
                 // Not filtering in this case. Passing the event along to listeners.
                 dispatchMediaDataLoaded(key, oldKey, data, immediately)
@@ -136,7 +136,7 @@
                 // If the local session uses a different notification key, then lets go a step
                 // farther and dismiss the media data so that media controls for the local session
                 // don't hang around while casting.
-                if (!keyedTokens.get(key)!!.contains(remote.sessionToken)) {
+                if (!keyedTokens.get(key)!!.contains(TokenId(remote.sessionToken))) {
                     dispatchMediaDataRemoved(key)
                 }
             }
@@ -199,6 +199,15 @@
                     packageControllers.put(controller.packageName, tokens)
                 }
         }
-        tokensWithNotifications.retainAll(controllers.map { it.sessionToken })
+        tokensWithNotifications.retainAll(controllers.map { TokenId(it.sessionToken) })
+    }
+
+    /**
+     * Represents a unique identifier for a [MediaSession.Token].
+     *
+     * It's used to avoid storing strong binders for media session tokens.
+     */
+    private data class TokenId(val id: Int) {
+        constructor(token: MediaSession.Token) : this(token.hashCode())
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 2334a4c..9421524 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -90,8 +90,13 @@
 class LinearLightRevealEffect(private val isVertical: Boolean) : LightRevealEffect {
 
     // Interpolator that reveals >80% of the content at 0.5 progress, makes revealing faster
-    private val interpolator = PathInterpolator(/* controlX1= */ 0.4f, /* controlY1= */ 0f,
-            /* controlX2= */ 0.2f, /* controlY2= */ 1f)
+    private val interpolator =
+        PathInterpolator(
+            /* controlX1= */ 0.4f,
+            /* controlY1= */ 0f,
+            /* controlX2= */ 0.2f,
+            /* controlY2= */ 1f
+        )
 
     override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
         val interpolatedAmount = interpolator.getInterpolation(amount)
@@ -116,17 +121,17 @@
 
         if (isVertical) {
             scrim.setRevealGradientBounds(
-                left = scrim.width / 2 - (scrim.width / 2) * gradientBoundsAmount,
+                left = scrim.viewWidth / 2 - (scrim.viewWidth / 2) * gradientBoundsAmount,
                 top = 0f,
-                right = scrim.width / 2 + (scrim.width / 2) * gradientBoundsAmount,
-                bottom = scrim.height.toFloat()
+                right = scrim.viewWidth / 2 + (scrim.viewWidth / 2) * gradientBoundsAmount,
+                bottom = scrim.viewHeight.toFloat()
             )
         } else {
             scrim.setRevealGradientBounds(
                 left = 0f,
-                top = scrim.height / 2 - (scrim.height / 2) * gradientBoundsAmount,
-                right = scrim.width.toFloat(),
-                bottom = scrim.height / 2 + (scrim.height / 2) * gradientBoundsAmount
+                top = scrim.viewHeight / 2 - (scrim.viewHeight / 2) * gradientBoundsAmount,
+                right = scrim.viewWidth.toFloat(),
+                bottom = scrim.viewHeight / 2 + (scrim.viewHeight / 2) * gradientBoundsAmount
             )
         }
     }
@@ -234,7 +239,14 @@
  * transparent center. The center position, size, and stops of the gradient can be manipulated to
  * reveal views below the scrim as if they are being 'lit up'.
  */
-class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
+class LightRevealScrim
+@JvmOverloads
+constructor(
+    context: Context?,
+    attrs: AttributeSet?,
+    initialWidth: Int? = null,
+    initialHeight: Int? = null
+) : View(context, attrs) {
 
     /** Listener that is called if the scrim's opaqueness changes */
     lateinit var isScrimOpaqueChangedListener: Consumer<Boolean>
@@ -278,6 +290,17 @@
     var revealGradientHeight: Float = 0f
 
     /**
+     * Keeps the initial value until the view is measured. See [LightRevealScrim.onMeasure].
+     *
+     * Needed as the view dimensions are used before the onMeasure pass happens, and without preset
+     * width and height some flicker during fold/unfold happens.
+     */
+    internal var viewWidth: Int = initialWidth ?: 0
+        private set
+    internal var viewHeight: Int = initialHeight ?: 0
+        private set
+
+    /**
      * Alpha of the fill that can be used in the beginning of the animation to hide the content.
      * Normally the gradient bounds are animated from small size so the content is not visible, but
      * if the start gradient bounds allow to see some content this could be used to make the reveal
@@ -375,6 +398,11 @@
         invalidate()
     }
 
+    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+        viewWidth = measuredWidth
+        viewHeight = measuredHeight
+    }
     /**
      * Sets bounds for the transparent oval gradient that reveals the views below the scrim. This is
      * simply a helper method that sets [revealGradientCenter], [revealGradientWidth], and
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index 9cca950..523cf68 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -159,18 +159,24 @@
         ensureOverlayRemoved()
 
         val newRoot = SurfaceControlViewHost(context, context.display!!, wwm)
-        val newView =
-            LightRevealScrim(context, null).apply {
-                revealEffect = createLightRevealEffect()
-                isScrimOpaqueChangedListener = Consumer {}
-                revealAmount =
-                    when (reason) {
-                        FOLD -> TRANSPARENT
-                        UNFOLD -> BLACK
-                    }
-            }
-
         val params = getLayoutParams()
+        val newView =
+            LightRevealScrim(
+                    context,
+                    attrs = null,
+                    initialWidth = params.width,
+                    initialHeight = params.height
+                )
+                .apply {
+                    revealEffect = createLightRevealEffect()
+                    isScrimOpaqueChangedListener = Consumer {}
+                    revealAmount =
+                        when (reason) {
+                            FOLD -> TRANSPARENT
+                            UNFOLD -> BLACK
+                        }
+                }
+
         newRoot.setView(newView, params)
 
         if (onOverlayReady != null) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt
index 97fe25d..d3befb4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt
@@ -20,6 +20,7 @@
 import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Before
@@ -36,7 +37,7 @@
 
   @Before
   fun setUp() {
-    scrim = LightRevealScrim(context, null)
+    scrim = LightRevealScrim(context, null, DEFAULT_WIDTH, DEFAULT_HEIGHT)
     scrim.isScrimOpaqueChangedListener = Consumer { opaque ->
       isOpaque = opaque
     }
@@ -63,4 +64,25 @@
     scrim.revealAmount = 0.5f
     assertFalse("Scrim is opaque even though it's revealed", scrim.isScrimOpaque)
   }
+
+  @Test
+  fun testBeforeOnMeasure_defaultDimensions() {
+    assertThat(scrim.viewWidth).isEqualTo(DEFAULT_WIDTH)
+    assertThat(scrim.viewHeight).isEqualTo(DEFAULT_HEIGHT)
+  }
+
+  @Test
+  fun testAfterOnMeasure_measuredDimensions() {
+    scrim.measure(/* widthMeasureSpec= */ exact(1), /* heightMeasureSpec= */ exact(2))
+
+    assertThat(scrim.viewWidth).isEqualTo(1)
+    assertThat(scrim.viewHeight).isEqualTo(2)
+  }
+
+  private fun exact(value: Int) = View.MeasureSpec.makeMeasureSpec(value, View.MeasureSpec.EXACTLY)
+
+  private companion object {
+    private const val DEFAULT_WIDTH = 42
+    private const val DEFAULT_HEIGHT = 24
+  }
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 8c3c0c9..b505396 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -131,6 +131,8 @@
 import android.media.audiopolicy.AudioProductStrategy;
 import android.media.audiopolicy.AudioVolumeGroup;
 import android.media.audiopolicy.IAudioPolicyCallback;
+import android.media.permission.ClearCallingIdentityContext;
+import android.media.permission.SafeCloseable;
 import android.media.projection.IMediaProjection;
 import android.media.projection.IMediaProjectionCallback;
 import android.media.projection.IMediaProjectionManager;
@@ -11219,6 +11221,34 @@
         mPrefMixerAttrDispatcher.finishBroadcast();
     }
 
+
+    /** @see AudioManager#supportsBluetoothVariableLatency() */
+    @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public boolean supportsBluetoothVariableLatency() {
+        super.supportsBluetoothVariableLatency_enforcePermission();
+        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+            return AudioSystem.supportsBluetoothVariableLatency();
+        }
+    }
+
+    /** @see AudioManager#setBluetoothVariableLatencyEnabled(boolean) */
+    @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public void setBluetoothVariableLatencyEnabled(boolean enabled) {
+        super.setBluetoothVariableLatencyEnabled_enforcePermission();
+        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+            AudioSystem.setBluetoothVariableLatencyEnabled(enabled);
+        }
+    }
+
+    /** @see AudioManager#isBluetoothVariableLatencyEnabled(boolean) */
+    @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public boolean isBluetoothVariableLatencyEnabled() {
+        super.isBluetoothVariableLatencyEnabled_enforcePermission();
+        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+            return AudioSystem.isBluetoothVariableLatencyEnabled();
+        }
+    }
+
     private final Object mExtVolumeControllerLock = new Object();
     private IAudioPolicyCallback mExtVolumeController;
     private void setExtVolumeController(IAudioPolicyCallback apc) {
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 19dbee7..c15e419 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -140,6 +140,7 @@
 import com.android.internal.net.VpnConfig;
 import com.android.internal.net.VpnProfile;
 import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.BinderUtils;
 import com.android.net.module.util.NetdUtils;
 import com.android.net.module.util.NetworkStackConstants;
 import com.android.server.DeviceIdleInternal;
@@ -2783,6 +2784,16 @@
         return hasIPV6 && !hasIPV4;
     }
 
+    private void setVpnNetworkPreference(String session, Set<Range<Integer>> ranges) {
+        BinderUtils.withCleanCallingIdentity(
+                () -> mConnectivityManager.setVpnDefaultForUids(session, ranges));
+    }
+
+    private void clearVpnNetworkPreference(String session) {
+        BinderUtils.withCleanCallingIdentity(
+                () -> mConnectivityManager.setVpnDefaultForUids(session, Collections.EMPTY_LIST));
+    }
+
     /**
      * Internal class managing IKEv2/IPsec VPN connectivity
      *
@@ -2894,6 +2905,9 @@
                     (r, exe) -> {
                         Log.d(TAG, "Runnable " + r + " rejected by the mExecutor");
                     });
+            setVpnNetworkPreference(mSessionKey,
+                    createUserAndRestrictedProfilesRanges(mUserId,
+                            mConfig.allowedApplications, mConfig.disallowedApplications));
         }
 
         @Override
@@ -3047,7 +3061,6 @@
                     mConfig.dnsServers.addAll(dnsAddrStrings);
 
                     mConfig.underlyingNetworks = new Network[] {network};
-                    mConfig.disallowedApplications = getAppExclusionList(mPackage);
 
                     networkAgent = mNetworkAgent;
 
@@ -3750,6 +3763,7 @@
             mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
             mConnectivityDiagnosticsManager.unregisterConnectivityDiagnosticsCallback(
                     mDiagnosticsCallback);
+            clearVpnNetworkPreference(mSessionKey);
 
             mExecutor.shutdown();
         }
@@ -4310,6 +4324,7 @@
             mConfig.requiresInternetValidation = profile.requiresInternetValidation;
             mConfig.excludeLocalRoutes = profile.excludeLocalRoutes;
             mConfig.allowBypass = profile.isBypassable;
+            mConfig.disallowedApplications = getAppExclusionList(mPackage);
 
             switch (profile.type) {
                 case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
@@ -4462,6 +4477,9 @@
                         .setUids(createUserAndRestrictedProfilesRanges(
                                 mUserId, null /* allowedApplications */, excludedApps))
                         .build();
+                setVpnNetworkPreference(getSessionKeyLocked(),
+                        createUserAndRestrictedProfilesRanges(mUserId,
+                                mConfig.allowedApplications, mConfig.disallowedApplications));
                 doSendNetworkCapabilities(mNetworkAgent, mNetworkCapabilities);
             }
         }
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index f5ce461..88ec691 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -136,10 +136,6 @@
     @IntDef({NAV_BAR_LEFT, NAV_BAR_RIGHT, NAV_BAR_BOTTOM})
     @interface NavigationBarPosition {}
 
-    @Retention(SOURCE)
-    @IntDef({ALT_BAR_UNKNOWN, ALT_BAR_LEFT, ALT_BAR_RIGHT, ALT_BAR_BOTTOM, ALT_BAR_TOP})
-    @interface AltBarPosition {}
-
     /**
      * Pass this event to the user / app.  To be returned from
      * {@link #interceptKeyBeforeQueueing}.
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 09a7b29..326d709 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -638,7 +638,7 @@
         } catch (RemoteException ex) {
             // Ignore
         }
-        FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_WAKE_REPORTED, reason);
+        FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_WAKE_REPORTED, reason, reasonUid);
     }
 
     /**
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index 6b2c6e3..514caf2 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -746,6 +746,8 @@
                         }
                         ret.add(new Temperature(t.value, t.type, t.name, t.throttlingStatus));
                     }
+                } catch (IllegalArgumentException | IllegalStateException e) {
+                    Slog.e(TAG, "Couldn't getCurrentCoolingDevices due to invalid status", e);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Couldn't getCurrentTemperatures, reconnecting", e);
                     connectToHal();
@@ -776,6 +778,8 @@
                         }
                         ret.add(new CoolingDevice(t.value, t.type, t.name));
                     }
+                } catch (IllegalArgumentException | IllegalStateException e) {
+                    Slog.e(TAG, "Couldn't getCurrentCoolingDevices due to invalid status", e);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Couldn't getCurrentCoolingDevices, reconnecting", e);
                     connectToHal();
@@ -799,6 +803,8 @@
 
                     return Arrays.stream(halRet).filter(t -> t.type == type).collect(
                             Collectors.toList());
+                } catch (IllegalArgumentException | IllegalStateException e) {
+                    Slog.e(TAG, "Couldn't getTemperatureThresholds due to invalid status", e);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Couldn't getTemperatureThresholds, reconnecting...", e);
                     connectToHal();
@@ -824,15 +830,30 @@
                     mInstance = IThermal.Stub.asInterface(binder);
                     try {
                         binder.linkToDeath(this, 0);
-                        mInstance.registerThermalChangedCallback(mThermalChangedCallback);
                     } catch (RemoteException e) {
                         Slog.e(TAG, "Unable to connect IThermal AIDL instance", e);
                         mInstance = null;
                     }
+                    if (mInstance != null) {
+                        registerThermalChangedCallback();
+                    }
                 }
             }
         }
 
+        @VisibleForTesting
+        void registerThermalChangedCallback() {
+            try {
+                mInstance.registerThermalChangedCallback(mThermalChangedCallback);
+            } catch (IllegalArgumentException | IllegalStateException e) {
+                Slog.e(TAG, "Couldn't registerThermalChangedCallback due to invalid status",
+                        e);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Unable to connect IThermal AIDL instance", e);
+                mInstance = null;
+            }
+        }
+
         @Override
         protected void dump(PrintWriter pw, String prefix) {
             synchronized (mHalLock) {
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index b95d372..863ee75 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -44,6 +44,7 @@
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.media.PlaybackParams;
+import android.media.tv.AdBuffer;
 import android.media.tv.AdRequest;
 import android.media.tv.AdResponse;
 import android.media.tv.AitInfo;
@@ -2520,7 +2521,30 @@
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
-            };
+            }
+        }
+
+        @Override
+        public void notifyAdBuffer(
+                IBinder sessionToken, AdBuffer buffer, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+            final int resolvedUserId = resolveCallingUserId(callingPid, callingUid,
+                    userId, "notifyAdBuffer");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).notifyAdBuffer(buffer);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slog.e(TAG, "error in notifyAdBuffer", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
 
         @Override
@@ -3624,7 +3648,7 @@
         }
 
         @Override
-        public void onBroadcastInfoResponse (BroadcastInfoResponse response) {
+        public void onBroadcastInfoResponse(BroadcastInfoResponse response) {
             synchronized (mLock) {
                 if (DEBUG) {
                     Slog.d(TAG, "onBroadcastInfoResponse()");
@@ -3641,7 +3665,7 @@
         }
 
         @Override
-        public void onAdResponse (AdResponse response) {
+        public void onAdResponse(AdResponse response) {
             synchronized (mLock) {
                 if (DEBUG) {
                     Slog.d(TAG, "onAdResponse()");
@@ -3656,6 +3680,24 @@
                 }
             }
         }
+
+        @Override
+        public void onAdBufferConsumed(AdBuffer buffer) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slog.d(TAG, "onAdBufferConsumed()");
+                }
+                if (mSessionState.session == null || mSessionState.client == null) {
+                    return;
+                }
+                try {
+                    mSessionState.client.onAdBufferConsumed(
+                            buffer, mSessionState.seq);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "error in onAdBufferConsumed", e);
+                }
+            }
+        }
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index 2c8fd96..f173f7a 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -29,6 +29,7 @@
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
 import android.graphics.Rect;
+import android.media.tv.AdBuffer;
 import android.media.tv.AdRequest;
 import android.media.tv.AdResponse;
 import android.media.tv.BroadcastInfoRequest;
@@ -1513,6 +1514,29 @@
         }
 
         @Override
+        public void notifyAdBufferConsumed(
+                IBinder sessionToken, AdBuffer buffer, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+            final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+                    "notifyAdBufferConsumed");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).notifyAdBufferConsumed(buffer);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in notifyAdBufferConsumed", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void registerCallback(final ITvInteractiveAppManagerCallback callback, int userId) {
             int callingPid = Binder.getCallingPid();
             int callingUid = Binder.getCallingUid();
@@ -2378,6 +2402,23 @@
             }
         }
 
+        @Override
+        public void onAdBuffer(AdBuffer buffer) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slogf.d(TAG, "onAdBuffer(buffer=" + buffer + ")");
+                }
+                if (mSessionState.mSession == null || mSessionState.mClient == null) {
+                    return;
+                }
+                try {
+                    mSessionState.mClient.onAdBuffer(buffer, mSessionState.mSeq);
+                } catch (RemoteException e) {
+                    Slogf.e(TAG, "error in onAdBuffer", e);
+                }
+            }
+        }
+
         @GuardedBy("mLock")
         private boolean addSessionTokenToClientStateLocked(ITvInteractiveAppSession session) {
             try {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 300deca..a68d7af 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -59,11 +59,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static android.view.WindowManagerGlobal.ADD_OKAY;
 import static android.view.WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED;
-import static android.view.WindowManagerPolicyConstants.ALT_BAR_BOTTOM;
-import static android.view.WindowManagerPolicyConstants.ALT_BAR_LEFT;
-import static android.view.WindowManagerPolicyConstants.ALT_BAR_RIGHT;
-import static android.view.WindowManagerPolicyConstants.ALT_BAR_TOP;
-import static android.view.WindowManagerPolicyConstants.ALT_BAR_UNKNOWN;
 import static android.view.WindowManagerPolicyConstants.EXTRA_HDMI_PLUGGED_STATE;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID;
@@ -139,7 +134,6 @@
 import com.android.internal.widget.PointerLocationView;
 import com.android.server.LocalServices;
 import com.android.server.UiThread;
-import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.policy.WindowManagerPolicy.NavigationBarPosition;
 import com.android.server.policy.WindowManagerPolicy.ScreenOnListener;
 import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs;
@@ -246,27 +240,6 @@
     @NavigationBarPosition
     private int mNavigationBarPosition = NAV_BAR_BOTTOM;
 
-    // Alternative status bar for when flexible insets mapping is used to place the status bar on
-    // another side of the screen.
-    private WindowState mStatusBarAlt = null;
-    @WindowManagerPolicy.AltBarPosition
-    private int mStatusBarAltPosition = ALT_BAR_UNKNOWN;
-    // Alternative navigation bar for when flexible insets mapping is used to place the navigation
-    // bar elsewhere on the screen.
-    private WindowState mNavigationBarAlt = null;
-    @WindowManagerPolicy.AltBarPosition
-    private int mNavigationBarAltPosition = ALT_BAR_UNKNOWN;
-    // Alternative climate bar for when flexible insets mapping is used to place a climate bar on
-    // the screen.
-    private WindowState mClimateBarAlt = null;
-    @WindowManagerPolicy.AltBarPosition
-    private int mClimateBarAltPosition = ALT_BAR_UNKNOWN;
-    // Alternative extra nav bar for when flexible insets mapping is used to place an extra nav bar
-    // on the screen.
-    private WindowState mExtraNavBarAlt = null;
-    @WindowManagerPolicy.AltBarPosition
-    private int mExtraNavBarAltPosition = ALT_BAR_UNKNOWN;
-
     private final ArraySet<WindowState> mInsetsSourceWindowsExceptIme = new ArraySet<>();
 
     /** Apps which are controlling the appearance of system bars */
@@ -345,6 +318,15 @@
     private boolean mForceConsumeSystemBars;
     private boolean mForceShowSystemBars;
 
+    /**
+     * Windows that provides gesture insets. If multiple windows provide gesture insets at the same
+     * side, the window with the highest z-order wins.
+     */
+    private WindowState mLeftGestureHost;
+    private WindowState mTopGestureHost;
+    private WindowState mRightGestureHost;
+    private WindowState mBottomGestureHost;
+
     private boolean mShowingDream;
     private boolean mLastShowingDream;
     private boolean mDreamingLockscreen;
@@ -362,13 +344,9 @@
     private boolean mShouldAttachNavBarToAppDuringTransition;
 
     // -------- PolicyHandler --------
-    private static final int MSG_REQUEST_TRANSIENT_BARS = 2;
     private static final int MSG_ENABLE_POINTER_LOCATION = 4;
     private static final int MSG_DISABLE_POINTER_LOCATION = 5;
 
-    private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0;
-    private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1;
-
     private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;
 
     private final WindowManagerInternal.AppTransitionListener mAppTransitionListener;
@@ -385,15 +363,6 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case MSG_REQUEST_TRANSIENT_BARS:
-                    synchronized (mLock) {
-                        WindowState targetBar = (msg.arg1 == MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS)
-                                ? getStatusBar() : getNavigationBar();
-                        if (targetBar != null) {
-                            requestTransientBars(targetBar, true /* isGestureOnSystemBar */);
-                        }
-                    }
-                    break;
                 case MSG_ENABLE_POINTER_LOCATION:
                     enablePointerLocation();
                     break;
@@ -438,35 +407,57 @@
         mSystemGestures = new SystemGesturesPointerEventListener(mUiContext, mHandler,
                 new SystemGesturesPointerEventListener.Callbacks() {
 
+                    private static final long MOUSE_GESTURE_DELAY_MS = 500;
+
+                    private Runnable mOnSwipeFromLeft = this::onSwipeFromLeft;
+                    private Runnable mOnSwipeFromTop = this::onSwipeFromTop;
+                    private Runnable mOnSwipeFromRight = this::onSwipeFromRight;
+                    private Runnable mOnSwipeFromBottom = this::onSwipeFromBottom;
+
+                    private Insets getControllableInsets(WindowState win) {
+                        if (win == null) {
+                            return Insets.NONE;
+                        }
+                        final InsetsSourceProvider provider = win.getControllableInsetProvider();
+                        if (provider == null) {
+                            return  Insets.NONE;
+                        }
+                        return provider.getSource().calculateInsets(win.getBounds(),
+                                true /* ignoreVisibility */);
+                    }
+
                     @Override
                     public void onSwipeFromTop() {
                         synchronized (mLock) {
-                            final WindowState bar = mStatusBar != null
-                                    ? mStatusBar
-                                    : findAltBarMatchingPosition(ALT_BAR_TOP);
-                            requestTransientBars(bar, true /* isGestureOnSystemBar */);
+                            requestTransientBars(mTopGestureHost,
+                                    getControllableInsets(mTopGestureHost).top > 0);
                         }
                     }
 
                     @Override
                     public void onSwipeFromBottom() {
                         synchronized (mLock) {
-                            final WindowState bar = mNavigationBar != null
-                                        && mNavigationBarPosition == NAV_BAR_BOTTOM
-                                    ? mNavigationBar
-                                    : findAltBarMatchingPosition(ALT_BAR_BOTTOM);
-                            requestTransientBars(bar, true /* isGestureOnSystemBar */);
+                            requestTransientBars(mBottomGestureHost,
+                                    getControllableInsets(mBottomGestureHost).bottom > 0);
                         }
                     }
 
+                    private boolean allowsSideSwipe(Region excludedRegion) {
+                        return mNavigationBarAlwaysShowOnSideGesture
+                                && !mSystemGestures.currentGestureStartedInRegion(excludedRegion);
+                    }
+
                     @Override
                     public void onSwipeFromRight() {
                         final Region excludedRegion = Region.obtain();
                         synchronized (mLock) {
                             mDisplayContent.calculateSystemGestureExclusion(
                                     excludedRegion, null /* outUnrestricted */);
-                            requestTransientBarsForSideSwipe(excludedRegion, NAV_BAR_RIGHT,
-                                    ALT_BAR_RIGHT);
+                            final boolean hasWindow =
+                                    getControllableInsets(mRightGestureHost).right > 0;
+                            if (hasWindow || allowsSideSwipe(excludedRegion)) {
+                                requestTransientBars(mRightGestureHost, hasWindow);
+                            }
                         }
                         excludedRegion.recycle();
                     }
@@ -477,33 +468,15 @@
                         synchronized (mLock) {
                             mDisplayContent.calculateSystemGestureExclusion(
                                     excludedRegion, null /* outUnrestricted */);
-                            requestTransientBarsForSideSwipe(excludedRegion, NAV_BAR_LEFT,
-                                    ALT_BAR_LEFT);
+                            final boolean hasWindow =
+                                    getControllableInsets(mLeftGestureHost).left > 0;
+                            if (hasWindow || allowsSideSwipe(excludedRegion)) {
+                                requestTransientBars(mLeftGestureHost, hasWindow);
+                            }
                         }
                         excludedRegion.recycle();
                     }
 
-                    private void requestTransientBarsForSideSwipe(Region excludedRegion,
-                            int navBarSide, int altBarSide) {
-                        final WindowState barMatchingSide = mNavigationBar != null
-                                        && mNavigationBarPosition == navBarSide
-                                ? mNavigationBar
-                                : findAltBarMatchingPosition(altBarSide);
-                        final boolean allowSideSwipe = mNavigationBarAlwaysShowOnSideGesture &&
-                                !mSystemGestures.currentGestureStartedInRegion(excludedRegion);
-                        if (barMatchingSide == null && !allowSideSwipe) {
-                            return;
-                        }
-
-                        // Request transient bars on the matching bar, or any bar if we always allow
-                        // side swipes to show the bars
-                        final boolean isGestureOnSystemBar = barMatchingSide != null;
-                        final WindowState bar = barMatchingSide != null
-                                ? barMatchingSide
-                                : findTransientNavOrAltBar();
-                        requestTransientBars(bar, isGestureOnSystemBar);
-                    }
-
                     @Override
                     public void onFling(int duration) {
                         if (mService.mPowerManagerInternal != null) {
@@ -539,24 +512,47 @@
                     }
 
                     @Override
+                    public void onMouseHoverAtLeft() {
+                        mHandler.removeCallbacks(mOnSwipeFromLeft);
+                        mHandler.postDelayed(mOnSwipeFromLeft, MOUSE_GESTURE_DELAY_MS);
+                    }
+
+                    @Override
                     public void onMouseHoverAtTop() {
-                        mHandler.removeMessages(MSG_REQUEST_TRANSIENT_BARS);
-                        Message msg = mHandler.obtainMessage(MSG_REQUEST_TRANSIENT_BARS);
-                        msg.arg1 = MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS;
-                        mHandler.sendMessageDelayed(msg, 500 /* delayMillis */);
+                        mHandler.removeCallbacks(mOnSwipeFromTop);
+                        mHandler.postDelayed(mOnSwipeFromTop, MOUSE_GESTURE_DELAY_MS);
+                    }
+
+                    @Override
+                    public void onMouseHoverAtRight() {
+                        mHandler.removeCallbacks(mOnSwipeFromRight);
+                        mHandler.postDelayed(mOnSwipeFromRight, MOUSE_GESTURE_DELAY_MS);
                     }
 
                     @Override
                     public void onMouseHoverAtBottom() {
-                        mHandler.removeMessages(MSG_REQUEST_TRANSIENT_BARS);
-                        Message msg = mHandler.obtainMessage(MSG_REQUEST_TRANSIENT_BARS);
-                        msg.arg1 = MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION;
-                        mHandler.sendMessageDelayed(msg, 500 /* delayMillis */);
+                        mHandler.removeCallbacks(mOnSwipeFromBottom);
+                        mHandler.postDelayed(mOnSwipeFromBottom, MOUSE_GESTURE_DELAY_MS);
                     }
 
                     @Override
-                    public void onMouseLeaveFromEdge() {
-                        mHandler.removeMessages(MSG_REQUEST_TRANSIENT_BARS);
+                    public void onMouseLeaveFromLeft() {
+                        mHandler.removeCallbacks(mOnSwipeFromLeft);
+                    }
+
+                    @Override
+                    public void onMouseLeaveFromTop() {
+                        mHandler.removeCallbacks(mOnSwipeFromTop);
+                    }
+
+                    @Override
+                    public void onMouseLeaveFromRight() {
+                        mHandler.removeCallbacks(mOnSwipeFromRight);
+                    }
+
+                    @Override
+                    public void onMouseLeaveFromBottom() {
+                        mHandler.removeCallbacks(mOnSwipeFromBottom);
                     }
                 });
         displayContent.registerPointerEventListener(mSystemGestures);
@@ -666,41 +662,6 @@
         }
     }
 
-    /**
-     * Returns the first non-null alt bar window matching the given position.
-     */
-    private WindowState findAltBarMatchingPosition(@WindowManagerPolicy.AltBarPosition int pos) {
-        if (mStatusBarAlt != null && mStatusBarAltPosition == pos) {
-            return mStatusBarAlt;
-        }
-        if (mNavigationBarAlt != null && mNavigationBarAltPosition == pos) {
-            return mNavigationBarAlt;
-        }
-        if (mClimateBarAlt != null && mClimateBarAltPosition == pos) {
-            return mClimateBarAlt;
-        }
-        if (mExtraNavBarAlt != null && mExtraNavBarAltPosition == pos) {
-            return mExtraNavBarAlt;
-        }
-        return null;
-    }
-
-    /**
-     * Finds the first non-null nav bar to request transient for.
-     */
-    private WindowState findTransientNavOrAltBar() {
-        if (mNavigationBar != null) {
-            return mNavigationBar;
-        }
-        if (mNavigationBarAlt != null) {
-            return mNavigationBarAlt;
-        }
-        if (mExtraNavBarAlt != null) {
-            return mExtraNavBarAlt;
-        }
-        return null;
-    }
-
     void systemReady() {
         mSystemGestures.systemReady();
         if (mService.mPointerLocationEnabled) {
@@ -970,20 +931,6 @@
             attrs.privateFlags &= ~PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
         }
 
-        // Check if alternate bars positions were updated.
-        if (mStatusBarAlt == win) {
-            mStatusBarAltPosition = getAltBarPosition(attrs);
-        }
-        if (mNavigationBarAlt == win) {
-            mNavigationBarAltPosition = getAltBarPosition(attrs);
-        }
-        if (mClimateBarAlt == win) {
-            mClimateBarAltPosition = getAltBarPosition(attrs);
-        }
-        if (mExtraNavBarAlt == win) {
-            mExtraNavBarAltPosition = getAltBarPosition(attrs);
-        }
-
         final InsetsSourceProvider provider = win.getControllableInsetProvider();
         if (provider != null && provider.getSource().getInsetsRoundedCornerFrame()
                 != attrs.insetsRoundedCornerFrame) {
@@ -1035,8 +982,7 @@
                 mContext.enforcePermission(
                         android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid,
                         "DisplayPolicy");
-                if ((mStatusBar != null && mStatusBar.isAlive())
-                        || (mStatusBarAlt != null && mStatusBarAlt.isAlive())) {
+                if (mStatusBar != null && mStatusBar.isAlive()) {
                     return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
                 }
                 break;
@@ -1054,8 +1000,7 @@
                 mContext.enforcePermission(
                         android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid,
                         "DisplayPolicy");
-                if ((mNavigationBar != null && mNavigationBar.isAlive())
-                        || (mNavigationBarAlt != null && mNavigationBarAlt.isAlive())) {
+                if (mNavigationBar != null && mNavigationBar.isAlive()) {
                     return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
                 }
                 break;
@@ -1086,34 +1031,6 @@
                         "DisplayPolicy");
             }
             enforceSingleInsetsTypeCorrespondingToWindowType(attrs.providedInsets);
-
-            for (InsetsFrameProvider provider : attrs.providedInsets) {
-                @InternalInsetsType int insetsType = provider.type;
-                switch (insetsType) {
-                    case ITYPE_STATUS_BAR:
-                        if ((mStatusBar != null && mStatusBar.isAlive())
-                                || (mStatusBarAlt != null && mStatusBarAlt.isAlive())) {
-                            return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
-                        }
-                        break;
-                    case ITYPE_NAVIGATION_BAR:
-                        if ((mNavigationBar != null && mNavigationBar.isAlive())
-                                || (mNavigationBarAlt != null && mNavigationBarAlt.isAlive())) {
-                            return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
-                        }
-                        break;
-                    case ITYPE_CLIMATE_BAR:
-                        if (mClimateBarAlt != null && mClimateBarAlt.isAlive()) {
-                            return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
-                        }
-                        break;
-                    case ITYPE_EXTRA_NAVIGATION_BAR:
-                        if (mExtraNavBarAlt != null && mExtraNavBarAlt.isAlive()) {
-                            return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
-                        }
-                        break;
-                }
-            }
         }
         return ADD_OKAY;
     }
@@ -1139,28 +1056,6 @@
         if (attrs.providedInsets != null) {
             for (int i = attrs.providedInsets.length - 1; i >= 0; i--) {
                 final InsetsFrameProvider provider = attrs.providedInsets[i];
-                switch (provider.type) {
-                    case ITYPE_STATUS_BAR:
-                        if (attrs.type != TYPE_STATUS_BAR) {
-                            mStatusBarAlt = win;
-                            mStatusBarAltPosition = getAltBarPosition(attrs);
-                        }
-                        break;
-                    case ITYPE_NAVIGATION_BAR:
-                        if (attrs.type != TYPE_NAVIGATION_BAR) {
-                            mNavigationBarAlt = win;
-                            mNavigationBarAltPosition = getAltBarPosition(attrs);
-                        }
-                        break;
-                    case ITYPE_CLIMATE_BAR:
-                        mClimateBarAlt = win;
-                        mClimateBarAltPosition = getAltBarPosition(attrs);
-                        break;
-                    case ITYPE_EXTRA_NAVIGATION_BAR:
-                        mExtraNavBarAlt = win;
-                        mExtraNavBarAltPosition = getAltBarPosition(attrs);
-                        break;
-                }
                 // The index of the provider and corresponding insets types cannot change at
                 // runtime as ensured in WMS. Make use of the index in the provider directly
                 // to access the latest provided size at runtime.
@@ -1217,22 +1112,6 @@
         };
     }
 
-    @WindowManagerPolicy.AltBarPosition
-    private int getAltBarPosition(WindowManager.LayoutParams params) {
-        switch (params.gravity) {
-            case Gravity.LEFT:
-                return ALT_BAR_LEFT;
-            case Gravity.RIGHT:
-                return ALT_BAR_RIGHT;
-            case Gravity.BOTTOM:
-                return ALT_BAR_BOTTOM;
-            case Gravity.TOP:
-                return ALT_BAR_TOP;
-            default:
-                return ALT_BAR_UNKNOWN;
-        }
-    }
-
     TriConsumer<DisplayFrames, WindowContainer, Rect> getImeSourceFrameProvider() {
         return (displayFrames, windowContainer, inOutFrame) -> {
             WindowState windowState = windowContainer.asWindowState();
@@ -1280,32 +1159,27 @@
      * @param win The window being removed.
      */
     void removeWindowLw(WindowState win) {
-        if (mStatusBar == win || mStatusBarAlt == win) {
+        if (mStatusBar == win) {
             mStatusBar = null;
-            mStatusBarAlt = null;
-            mDisplayContent.setInsetProvider(ITYPE_STATUS_BAR, null, null);
-        } else if (mNavigationBar == win || mNavigationBarAlt == win) {
+        } else if (mNavigationBar == win) {
             mNavigationBar = null;
-            mNavigationBarAlt = null;
-            mDisplayContent.setInsetProvider(ITYPE_NAVIGATION_BAR, null, null);
         } else if (mNotificationShade == win) {
             mNotificationShade = null;
-        } else if (mClimateBarAlt == win) {
-            mClimateBarAlt = null;
-            mDisplayContent.setInsetProvider(ITYPE_CLIMATE_BAR, null, null);
-        } else if (mExtraNavBarAlt == win) {
-            mExtraNavBarAlt = null;
-            mDisplayContent.setInsetProvider(ITYPE_EXTRA_NAVIGATION_BAR, null, null);
         }
         if (mLastFocusedWindow == win) {
             mLastFocusedWindow = null;
         }
 
+        final SparseArray<InsetsSource> sources = win.getProvidedInsetsSources();
+        for (int index = sources.size() - 1; index >= 0; index--) {
+            final @InternalInsetsType int type = sources.keyAt(index);
+            mDisplayContent.setInsetProvider(type, null /* win */, null /* frameProvider */);
+        }
         mInsetsSourceWindowsExceptIme.remove(win);
     }
 
     WindowState getStatusBar() {
-        return mStatusBar != null ? mStatusBar : mStatusBarAlt;
+        return mStatusBar;
     }
 
     WindowState getNotificationShade() {
@@ -1313,7 +1187,7 @@
     }
 
     WindowState getNavigationBar() {
-        return mNavigationBar != null ? mNavigationBar : mNavigationBarAlt;
+        return mNavigationBar;
     }
 
     /**
@@ -1439,6 +1313,10 @@
      * Called following layout of all windows before each window has policy applied.
      */
     public void beginPostLayoutPolicyLw() {
+        mLeftGestureHost = null;
+        mTopGestureHost = null;
+        mRightGestureHost = null;
+        mBottomGestureHost = null;
         mTopFullscreenOpaqueWindowState = null;
         mNavBarColorWindowCandidate = null;
         mNavBarBackgroundWindow = null;
@@ -1480,6 +1358,33 @@
             mIsFreeformWindowOverlappingWithNavBar = true;
         }
 
+        final SparseArray<InsetsSource> sources = win.getProvidedInsetsSources();
+        final Rect bounds = win.getBounds();
+        for (int index = sources.size() - 1; index >= 0; index--) {
+            final InsetsSource source = sources.valueAt(index);
+            if ((source.getType()
+                    & (Type.systemGestures() | Type.mandatorySystemGestures())) == 0) {
+                continue;
+            }
+            if (mLeftGestureHost != null && mTopGestureHost != null
+                    && mRightGestureHost != null && mBottomGestureHost != null) {
+                continue;
+            }
+            final Insets insets = source.calculateInsets(bounds, false /* ignoreVisibility */);
+            if (mLeftGestureHost == null && insets.left > 0) {
+                mLeftGestureHost = win;
+            }
+            if (mTopGestureHost == null && insets.top > 0) {
+                mTopGestureHost = win;
+            }
+            if (mRightGestureHost == null && insets.right > 0) {
+                mRightGestureHost = win;
+            }
+            if (mBottomGestureHost == null && insets.bottom > 0) {
+                mBottomGestureHost = win;
+            }
+        }
+
         if (!affectsSystemUi) {
             return;
         }
@@ -2588,11 +2493,6 @@
         if (mStatusBar != null) {
             pw.print(prefix); pw.print("mStatusBar="); pw.println(mStatusBar);
         }
-        if (mStatusBarAlt != null) {
-            pw.print(prefix); pw.print("mStatusBarAlt="); pw.println(mStatusBarAlt);
-            pw.print(prefix); pw.print("mStatusBarAltPosition=");
-            pw.println(mStatusBarAltPosition);
-        }
         if (mNotificationShade != null) {
             pw.print(prefix); pw.print("mExpandedPanel="); pw.println(mNotificationShade);
         }
@@ -2604,20 +2504,17 @@
             pw.print(prefix); pw.print("mNavigationBarPosition=");
             pw.println(mNavigationBarPosition);
         }
-        if (mNavigationBarAlt != null) {
-            pw.print(prefix); pw.print("mNavigationBarAlt="); pw.println(mNavigationBarAlt);
-            pw.print(prefix); pw.print("mNavigationBarAltPosition=");
-            pw.println(mNavigationBarAltPosition);
+        if (mLeftGestureHost != null) {
+            pw.print(prefix); pw.print("mLeftGestureHost="); pw.println(mLeftGestureHost);
         }
-        if (mClimateBarAlt != null) {
-            pw.print(prefix); pw.print("mClimateBarAlt="); pw.println(mClimateBarAlt);
-            pw.print(prefix); pw.print("mClimateBarAltPosition=");
-            pw.println(mClimateBarAltPosition);
+        if (mTopGestureHost != null) {
+            pw.print(prefix); pw.print("mTopGestureHost="); pw.println(mTopGestureHost);
         }
-        if (mExtraNavBarAlt != null) {
-            pw.print(prefix); pw.print("mExtraNavBarAlt="); pw.println(mExtraNavBarAlt);
-            pw.print(prefix); pw.print("mExtraNavBarAltPosition=");
-            pw.println(mExtraNavBarAltPosition);
+        if (mRightGestureHost != null) {
+            pw.print(prefix); pw.print("mRightGestureHost="); pw.println(mRightGestureHost);
+        }
+        if (mBottomGestureHost != null) {
+            pw.print(prefix); pw.print("mBottomGestureHost="); pw.println(mBottomGestureHost);
         }
         if (mFocusedWindow != null) {
             pw.print(prefix); pw.print("mFocusedWindow="); pw.println(mFocusedWindow);
diff --git a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
index 658f4ef..878b33f 100644
--- a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
@@ -78,7 +78,10 @@
     private int mDownPointers;
     private boolean mSwipeFireable;
     private boolean mDebugFireable;
-    private boolean mMouseHoveringAtEdge;
+    private boolean mMouseHoveringAtLeft;
+    private boolean mMouseHoveringAtTop;
+    private boolean mMouseHoveringAtRight;
+    private boolean mMouseHoveringAtBottom;
     private long mLastFlingTime;
 
     SystemGesturesPointerEventListener(Context context, Handler handler, Callbacks callbacks) {
@@ -174,9 +177,21 @@
                 mDebugFireable = true;
                 mDownPointers = 0;
                 captureDown(event, 0);
-                if (mMouseHoveringAtEdge) {
-                    mMouseHoveringAtEdge = false;
-                    mCallbacks.onMouseLeaveFromEdge();
+                if (mMouseHoveringAtLeft) {
+                    mMouseHoveringAtLeft = false;
+                    mCallbacks.onMouseLeaveFromLeft();
+                }
+                if (mMouseHoveringAtTop) {
+                    mMouseHoveringAtTop = false;
+                    mCallbacks.onMouseLeaveFromTop();
+                }
+                if (mMouseHoveringAtRight) {
+                    mMouseHoveringAtRight = false;
+                    mCallbacks.onMouseLeaveFromRight();
+                }
+                if (mMouseHoveringAtBottom) {
+                    mMouseHoveringAtBottom = false;
+                    mCallbacks.onMouseLeaveFromBottom();
                 }
                 mCallbacks.onDown();
                 break;
@@ -211,16 +226,35 @@
                 break;
             case MotionEvent.ACTION_HOVER_MOVE:
                 if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
-                    if (!mMouseHoveringAtEdge && event.getY() == 0) {
+                    final float eventX = event.getX();
+                    final float eventY = event.getY();
+                    if (!mMouseHoveringAtLeft && eventX == 0) {
+                        mCallbacks.onMouseHoverAtLeft();
+                        mMouseHoveringAtLeft = true;
+                    } else if (mMouseHoveringAtLeft && eventX > 0) {
+                        mCallbacks.onMouseLeaveFromLeft();
+                        mMouseHoveringAtLeft = false;
+                    }
+                    if (!mMouseHoveringAtTop && eventY == 0) {
                         mCallbacks.onMouseHoverAtTop();
-                        mMouseHoveringAtEdge = true;
-                    } else if (!mMouseHoveringAtEdge && event.getY() >= screenHeight - 1) {
+                        mMouseHoveringAtTop = true;
+                    } else if (mMouseHoveringAtTop && eventY > 0) {
+                        mCallbacks.onMouseLeaveFromTop();
+                        mMouseHoveringAtTop = false;
+                    }
+                    if (!mMouseHoveringAtRight && eventX >= screenWidth - 1) {
+                        mCallbacks.onMouseHoverAtRight();
+                        mMouseHoveringAtRight = true;
+                    } else if (mMouseHoveringAtRight && eventX < screenWidth - 1) {
+                        mCallbacks.onMouseLeaveFromRight();
+                        mMouseHoveringAtRight = false;
+                    }
+                    if (!mMouseHoveringAtBottom && eventY >= screenHeight - 1) {
                         mCallbacks.onMouseHoverAtBottom();
-                        mMouseHoveringAtEdge = true;
-                    } else if (mMouseHoveringAtEdge
-                            && (event.getY() > 0 && event.getY() < screenHeight - 1)) {
-                        mCallbacks.onMouseLeaveFromEdge();
-                        mMouseHoveringAtEdge = false;
+                        mMouseHoveringAtBottom = true;
+                    } else if (mMouseHoveringAtBottom && eventY < screenHeight - 1) {
+                        mCallbacks.onMouseLeaveFromBottom();
+                        mMouseHoveringAtBottom = false;
                     }
                 }
                 break;
@@ -373,9 +407,14 @@
         void onFling(int durationMs);
         void onDown();
         void onUpOrCancel();
+        void onMouseHoverAtLeft();
         void onMouseHoverAtTop();
+        void onMouseHoverAtRight();
         void onMouseHoverAtBottom();
-        void onMouseLeaveFromEdge();
+        void onMouseLeaveFromLeft();
+        void onMouseLeaveFromTop();
+        void onMouseLeaveFromRight();
+        void onMouseLeaveFromBottom();
         void onDebug();
     }
 }
diff --git a/services/core/java/com/android/server/wm/utils/StateMachine.java b/services/core/java/com/android/server/wm/utils/StateMachine.java
index 91a5dc4..350784e 100644
--- a/services/core/java/com/android/server/wm/utils/StateMachine.java
+++ b/services/core/java/com/android/server/wm/utils/StateMachine.java
@@ -177,19 +177,18 @@
     }
 
     /**
-     * Process an event. Search handler for a given event and {@link Handler#handle(int)}. If the
-     * handler cannot handle the event, delegate it to a handler for a parent of the given state.
+     * Process an event. Search handler for a given event and {@link Handler#handle(int, Object)}.
+     * If the handler cannot handle the event, delegate it to a handler for a parent of the given
+     * state.
      *
      * @param event Type of an event.
      */
     public void handle(int event, @Nullable Object param) {
-        int state = mState;
-        while (state != 0) {
+        for (int state = mState;; state >>= 4) {
             final Handler h = mStateHandlers.get(state);
-            if (h != null && h.handle(event, param)) {
+            if ((h != null && h.handle(event, param)) || state == 0) {
                 return;
             }
-            state >>= 4;
         }
     }
 
diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
new file mode 100644
index 0000000..6b254bf
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.credentials;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.credentials.ClearCredentialStateRequest;
+import android.credentials.IClearCredentialStateCallback;
+import android.credentials.ui.ProviderData;
+import android.credentials.ui.RequestInfo;
+import android.os.RemoteException;
+import android.service.credentials.CredentialProviderInfo;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * Central session for a single clearCredentialState request. This class listens to the
+ * responses from providers, and updates the provider(S) state.
+ */
+public final class ClearRequestSession extends RequestSession<ClearCredentialStateRequest,
+        IClearCredentialStateCallback>
+        implements ProviderSession.ProviderInternalCallback<Void> {
+    private static final String TAG = "GetRequestSession";
+
+    public ClearRequestSession(Context context, int userId,
+            IClearCredentialStateCallback callback, ClearCredentialStateRequest request,
+            String callingPackage) {
+        super(context, userId, request, callback, RequestInfo.TYPE_UNDEFINED, callingPackage);
+    }
+
+    /**
+     * Creates a new provider session, and adds it list of providers that are contributing to
+     * this session.
+     * @return the provider session created within this request session, for the given provider
+     * info.
+     */
+    @Override
+    @Nullable
+    public ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
+            RemoteCredentialService remoteCredentialService) {
+        ProviderClearSession providerClearSession = ProviderClearSession
+                .createNewSession(mContext, mUserId, providerInfo,
+                        this, remoteCredentialService);
+        if (providerClearSession != null) {
+            Log.i(TAG, "In startProviderSession - provider session created and being added");
+            mProviders.put(providerClearSession.getComponentName().flattenToString(),
+                    providerClearSession);
+        }
+        return providerClearSession;
+    }
+
+    @Override // from provider session
+    public void onProviderStatusChanged(ProviderSession.Status status,
+            ComponentName componentName) {
+        Log.i(TAG, "in onStatusChanged with status: " + status);
+        if (ProviderSession.isTerminatingStatus(status)) {
+            Log.i(TAG, "in onStatusChanged terminating status");
+            onProviderTerminated(componentName);
+        } else if (ProviderSession.isCompletionStatus(status)) {
+            Log.i(TAG, "in onStatusChanged isCompletionStatus status");
+            onProviderResponseComplete(componentName);
+        }
+    }
+
+    @Override
+    public void onFinalResponseReceived(ComponentName componentName, Void response) {
+        respondToClientWithResponseAndFinish();
+    }
+
+    @Override
+    protected void onProviderResponseComplete(ComponentName componentName) {
+        if (!isAnyProviderPending()) {
+            onFinalResponseReceived(componentName, null);
+        }
+    }
+
+    @Override
+    protected void onProviderTerminated(ComponentName componentName) {
+        if (!isAnyProviderPending()) {
+            processResponses();
+        }
+    }
+
+    @Override
+    protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
+        //Not applicable for clearCredential as UI is not needed
+    }
+
+    @Override
+    public void onFinalErrorReceived(ComponentName componentName, String errorType,
+            String message) {
+        //Not applicable for clearCredential as response is not picked by the user
+    }
+
+    private void respondToClientWithResponseAndFinish() {
+        Log.i(TAG, "respondToClientWithResponseAndFinish");
+        try {
+            mClientCallback.onSuccess();
+        } catch (RemoteException e) {
+            Log.i(TAG, "Issue while propagating the response to the client");
+        }
+        finishSession();
+    }
+
+    private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
+        Log.i(TAG, "respondToClientWithErrorAndFinish");
+        try {
+            mClientCallback.onError(errorType, errorMsg);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+        finishSession();
+    }
+    private void processResponses() {
+        for (ProviderSession session: mProviders.values()) {
+            if (session.isProviderResponseSet()) {
+                // If even one provider responded successfully, send back the response
+                // TODO: Aggregate other exceptions
+                respondToClientWithResponseAndFinish();
+                return;
+            }
+        }
+        // TODO: Replace with properly defined error type
+        respondToClientWithErrorAndFinish("unknown", "All providers failed");
+    }
+}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 1f74e93..d29f86e 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -314,9 +314,45 @@
         @Override
         public ICancellationSignal clearCredentialState(ClearCredentialStateRequest request,
                 IClearCredentialStateCallback callback, String callingPackage) {
-            // TODO: implement.
-            Log.i(TAG, "clearCredentialSession");
+            Log.i(TAG, "starting clearCredentialState with callingPackage: " + callingPackage);
+            // TODO : Implement cancellation
             ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+
+            // New request session, scoped for this request only.
+            final ClearRequestSession session =
+                    new ClearRequestSession(
+                            getContext(),
+                            UserHandle.getCallingUserId(),
+                            callback,
+                            request,
+                            callingPackage);
+
+            // Initiate all provider sessions
+            // TODO: Determine if provider needs to have clear capability in their manifest
+            List<ProviderSession> providerSessions =
+                    initiateProviderSessions(session, List.of());
+
+            if (providerSessions.isEmpty()) {
+                try {
+                    // TODO("Replace with properly defined error type")
+                    callback.onError("unknown_type",
+                            "No providers available to fulfill request.");
+                } catch (RemoteException e) {
+                    Log.i(TAG, "Issue invoking onError on IClearCredentialStateCallback "
+                            + "callback: " + e.getMessage());
+                }
+            }
+
+            // Iterate over all provider sessions and invoke the request
+            providerSessions.forEach(
+                    providerClearSession -> {
+                        providerClearSession
+                                .getRemoteCredentialService()
+                                .onClearCredentialState(
+                                        (android.service.credentials.ClearCredentialStateRequest)
+                                                providerClearSession.getProviderRequest(),
+                                        /* callback= */ providerClearSession);
+                    });
             return cancelTransport;
         }
     }
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
index 08fdeed..c03d505 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
@@ -76,7 +76,7 @@
     @GuardedBy("mLock")
     public ProviderSession initiateProviderSessionForRequestLocked(
             RequestSession requestSession, List<String> requestOptions) {
-        if (!isServiceCapableLocked(requestOptions)) {
+        if (!requestOptions.isEmpty() && !isServiceCapableLocked(requestOptions)) {
             Log.i(TAG, "Service is not capable");
             return null;
         }
@@ -88,9 +88,7 @@
         }
         final RemoteCredentialService remoteService = new RemoteCredentialService(
                 getContext(), mInfo.getServiceInfo().getComponentName(), mUserId);
-        ProviderSession providerSession =
-                requestSession.initiateProviderSession(mInfo, remoteService);
-        return providerSession;
+        return requestSession.initiateProviderSession(mInfo, remoteService);
     }
 
     /** Return true if at least one capability found. */
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
new file mode 100644
index 0000000..020552a
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.credentials;
+
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.credentials.ClearCredentialStateException;
+import android.credentials.ui.ProviderData;
+import android.credentials.ui.ProviderPendingIntentResponse;
+import android.service.credentials.CallingAppInfo;
+import android.service.credentials.ClearCredentialStateRequest;
+import android.service.credentials.CredentialProviderInfo;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+
+/**
+ * Central provider session that listens for provider callbacks, and maintains provider state.
+ *
+ * @hide
+ */
+public final class ProviderClearSession extends ProviderSession<ClearCredentialStateRequest,
+        Void>
+        implements
+        RemoteCredentialService.ProviderCallbacks<Void> {
+    private static final String TAG = "ProviderClearSession";
+
+    private ClearCredentialStateException mProviderException;
+
+    /** Creates a new provider session to be used by the request session. */
+    @Nullable public static ProviderClearSession createNewSession(
+            Context context,
+            @UserIdInt int userId,
+            CredentialProviderInfo providerInfo,
+            ClearRequestSession clearRequestSession,
+            RemoteCredentialService remoteCredentialService) {
+        ClearCredentialStateRequest providerRequest =
+                createProviderRequest(
+                        clearRequestSession.mClientRequest,
+                        clearRequestSession.mClientCallingPackage);
+        return new ProviderClearSession(context, providerInfo, clearRequestSession, userId,
+                    remoteCredentialService, providerRequest);
+    }
+
+    @Nullable
+    private static ClearCredentialStateRequest createProviderRequest(
+            android.credentials.ClearCredentialStateRequest clientRequest,
+            String clientCallingPackage
+    ) {
+        // TODO: Determine if provider needs to declare clear capability in manifest
+        return new ClearCredentialStateRequest(
+                new CallingAppInfo(clientCallingPackage, new ArraySet<>()),
+                clientRequest.getData());
+    }
+
+    public ProviderClearSession(Context context,
+            CredentialProviderInfo info,
+            ProviderInternalCallback callbacks,
+            int userId, RemoteCredentialService remoteCredentialService,
+            ClearCredentialStateRequest providerRequest) {
+        super(context, info, providerRequest, callbacks, userId, remoteCredentialService);
+        setStatus(Status.PENDING);
+    }
+
+    @Override
+    public void onProviderResponseSuccess(@Nullable Void response) {
+        Log.i(TAG, "in onProviderResponseSuccess");
+        mProviderResponseSet = true;
+        updateStatusAndInvokeCallback(Status.COMPLETE);
+    }
+
+    /** Called when the provider response resulted in a failure. */
+    @Override // Callback from the remote provider
+    public void onProviderResponseFailure(int errorCode, Exception exception) {
+        if (exception instanceof ClearCredentialStateException) {
+            mProviderException = (ClearCredentialStateException) exception;
+        }
+        updateStatusAndInvokeCallback(toStatus(errorCode));
+    }
+
+    /** Called when provider service dies. */
+    @Override // Callback from the remote provider
+    public void onProviderServiceDied(RemoteCredentialService service) {
+        if (service.getComponentName().equals(mProviderInfo.getServiceInfo().getComponentName())) {
+            updateStatusAndInvokeCallback(Status.SERVICE_DEAD);
+        } else {
+            Slog.i(TAG, "Component names different in onProviderServiceDied - "
+                    + "this should not happen");
+        }
+    }
+
+    @Nullable
+    @Override
+    protected ProviderData prepareUiData() {
+        //Not applicable for clearCredential as response is not picked by the user
+        return null;
+    }
+
+    @Override
+    protected void onUiEntrySelected(String entryType, String entryId,
+            ProviderPendingIntentResponse providerPendingIntentResponse) {
+        //Not applicable for clearCredential as response is not picked by the user
+    }
+}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 7ecae9d..93e816a 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -51,6 +51,7 @@
     @Nullable protected Credential mFinalCredentialResponse;
     @NonNull protected final T mProviderRequest;
     @Nullable protected R mProviderResponse;
+    @NonNull protected Boolean mProviderResponseSet = false;
     @Nullable protected Pair<String, CredentialEntry> mUiRemoteEntry;
 
 
@@ -84,7 +85,8 @@
      */
     public static boolean isCompletionStatus(Status status) {
         return status == Status.CREDENTIAL_RECEIVED_FROM_INTENT
-                || status == Status.CREDENTIAL_RECEIVED_FROM_SELECTION;
+                || status == Status.CREDENTIAL_RECEIVED_FROM_SELECTION
+                || status == Status.COMPLETE;
     }
 
     /**
@@ -130,7 +132,8 @@
         CREDENTIAL_RECEIVED_FROM_INTENT,
         PENDING_INTENT_INVOKED,
         CREDENTIAL_RECEIVED_FROM_SELECTION,
-        SAVE_ENTRIES_RECEIVED, CANCELED
+        SAVE_ENTRIES_RECEIVED, CANCELED,
+        COMPLETE
     }
 
     /** Converts exception to a provider session status. */
@@ -183,6 +186,11 @@
         return mProviderRequest;
     }
 
+    /** Returns whether the provider response is set. */
+    protected Boolean isProviderResponseSet() {
+        return mProviderResponse != null || mProviderResponseSet;
+    }
+
     /** Update the response state stored with the provider session. */
     @Nullable protected R getProviderResponse() {
         return mProviderResponse;
diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
index 6049fd9..8cad6ac 100644
--- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
+++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
@@ -21,6 +21,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.credentials.ClearCredentialStateException;
 import android.credentials.CreateCredentialException;
 import android.credentials.GetCredentialException;
 import android.os.Handler;
@@ -30,10 +31,12 @@
 import android.service.credentials.BeginCreateCredentialResponse;
 import android.service.credentials.BeginGetCredentialRequest;
 import android.service.credentials.BeginGetCredentialResponse;
+import android.service.credentials.ClearCredentialStateRequest;
 import android.service.credentials.CredentialProviderErrors;
 import android.service.credentials.CredentialProviderService;
 import android.service.credentials.IBeginCreateCredentialCallback;
 import android.service.credentials.IBeginGetCredentialCallback;
+import android.service.credentials.IClearCredentialStateCallback;
 import android.service.credentials.ICredentialProviderService;
 import android.text.format.DateUtils;
 import android.util.Log;
@@ -195,6 +198,53 @@
                 handleExecutionResponse(result, error, cancellationSink, callback)));
     }
 
+    /** Main entry point to be called for executing a clearCredentialState call on the remote
+     * provider service.
+     * @param request the request to be sent to the provider
+     * @param callback the callback to be used to send back the provider response to the
+     *                 {@link ProviderClearSession} class that maintains provider state
+     */
+    public void onClearCredentialState(@NonNull ClearCredentialStateRequest request,
+            ProviderCallbacks<Void> callback) {
+        Log.i(TAG, "In onClearCredentialState in RemoteCredentialService");
+        AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
+        AtomicReference<CompletableFuture<Void>> futureRef = new AtomicReference<>();
+
+        CompletableFuture<Void> connectThenExecute =
+                postAsync(service -> {
+                    CompletableFuture<Void> clearCredentialFuture =
+                            new CompletableFuture<>();
+                    ICancellationSignal cancellationSignal = service.onClearCredentialState(
+                            request, new IClearCredentialStateCallback.Stub() {
+                                @Override
+                                public void onSuccess() {
+                                    Log.i(TAG, "In onSuccess onClearCredentialState "
+                                            + "in RemoteCredentialService");
+                                    clearCredentialFuture.complete(null);
+                                }
+
+                                @Override
+                                public void onFailure(String errorType, CharSequence message) {
+                                    Log.i(TAG, "In onFailure in RemoteCredentialService");
+                                    String errorMsg = message == null ? "" :
+                                            String.valueOf(message);
+                                    clearCredentialFuture.completeExceptionally(
+                                            new ClearCredentialStateException(errorType, errorMsg));
+                                }});
+                    CompletableFuture<Void> future = futureRef.get();
+                    if (future != null && future.isCancelled()) {
+                        dispatchCancellationSignal(cancellationSignal);
+                    } else {
+                        cancellationSink.set(cancellationSignal);
+                    }
+                    return clearCredentialFuture;
+                }).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
+
+        futureRef.set(connectThenExecute);
+        connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
+                handleExecutionResponse(result, error, cancellationSink, callback)));
+    }
+
     private <T> void handleExecutionResponse(T result,
             Throwable error,
             AtomicReference<ICancellationSignal> cancellationSink,
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index 937fac9..6d7cb4c 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -144,7 +144,7 @@
         mProviders.clear();
     }
 
-    private boolean isAnyProviderPending() {
+    boolean isAnyProviderPending() {
         for (ProviderSession session : mProviders.values()) {
             if (ProviderSession.isStatusWaitingForRemoteResponse(session.getStatus())) {
                 return true;
diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt
index c4d07fe..f4ecceb 100644
--- a/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt
+++ b/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt
@@ -99,3 +99,10 @@
     }
     return isChanged
 }
+
+inline fun <T, R> IndexedList<T>.mapNotNullIndexed(transform: (T) -> R?): IndexedList<R> =
+    IndexedList<R>().also { destination ->
+        forEachIndexed { _, element ->
+            transform(element)?.let { destination += it }
+        }
+    }
diff --git a/services/permission/java/com/android/server/permission/access/permission/Permission.kt b/services/permission/java/com/android/server/permission/access/permission/Permission.kt
index 88989c4..35f00a7 100644
--- a/services/permission/java/com/android/server/permission/access/permission/Permission.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/Permission.kt
@@ -46,83 +46,86 @@
         @Suppress("DEPRECATION")
         get() = permissionInfo.protectionLevel
 
+    inline val protection: Int
+        get() = permissionInfo.protection
+
     inline val isInternal: Boolean
-        get() = permissionInfo.protection == PermissionInfo.PROTECTION_INTERNAL
+        get() = protection == PermissionInfo.PROTECTION_INTERNAL
 
     inline val isNormal: Boolean
-        get() = permissionInfo.protection == PermissionInfo.PROTECTION_NORMAL
+        get() = protection == PermissionInfo.PROTECTION_NORMAL
 
     inline val isRuntime: Boolean
-        get() = permissionInfo.protection == PermissionInfo.PROTECTION_DANGEROUS
+        get() = protection == PermissionInfo.PROTECTION_DANGEROUS
 
     inline val isSignature: Boolean
-        get() = permissionInfo.protection == PermissionInfo.PROTECTION_SIGNATURE
+        get() = protection == PermissionInfo.PROTECTION_SIGNATURE
+
+    inline val protectionFlags: Int
+        get() = permissionInfo.protectionFlags
 
     inline val isAppOp: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_APPOP)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_APPOP)
 
     inline val isAppPredictor: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_APP_PREDICTOR)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_APP_PREDICTOR)
 
     inline val isCompanion: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_COMPANION)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_COMPANION)
 
     inline val isConfigurator: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_CONFIGURATOR)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_CONFIGURATOR)
 
     inline val isDevelopment: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_DEVELOPMENT)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_DEVELOPMENT)
 
     inline val isIncidentReportApprover: Boolean
-        get() = permissionInfo.protectionFlags
-            .hasBits(PermissionInfo.PROTECTION_FLAG_INCIDENT_REPORT_APPROVER)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_INCIDENT_REPORT_APPROVER)
 
     inline val isInstaller: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_INSTALLER)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_INSTALLER)
 
     inline val isInstant: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_INSTANT)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_INSTANT)
 
     inline val isKnownSigner: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER)
 
     inline val isOem: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_OEM)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_OEM)
 
     inline val isPre23: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_PRE23)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_PRE23)
 
     inline val isPreInstalled: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_PREINSTALLED)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_PREINSTALLED)
 
     inline val isPrivileged: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_PRIVILEGED)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_PRIVILEGED)
 
     inline val isRecents: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_RECENTS)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_RECENTS)
 
     inline val isRetailDemo: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_RETAIL_DEMO)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_RETAIL_DEMO)
 
     inline val isRole: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_ROLE)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_ROLE)
 
     inline val isRuntimeOnly: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY)
 
     inline val isSetup: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_SETUP)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_SETUP)
 
     inline val isSystemTextClassifier: Boolean
-        get() = permissionInfo.protectionFlags
-            .hasBits(PermissionInfo.PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER)
 
     inline val isVendorPrivileged: Boolean
-        get() = permissionInfo.protectionFlags
-            .hasBits(PermissionInfo.PROTECTION_FLAG_VENDOR_PRIVILEGED)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_VENDOR_PRIVILEGED)
 
     inline val isVerifier: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_VERIFIER)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_VERIFIER)
 
     inline val isHardRestricted: Boolean
         get() = permissionInfo.flags.hasBits(PermissionInfo.FLAG_HARD_RESTRICTED)
@@ -133,12 +136,23 @@
     inline val isSoftRestricted: Boolean
         get() = permissionInfo.flags.hasBits(PermissionInfo.FLAG_SOFT_RESTRICTED)
 
+    inline val isHardOrSoftRestricted: Boolean
+        get() = permissionInfo.flags.hasBits(
+            PermissionInfo.FLAG_HARD_RESTRICTED or PermissionInfo.FLAG_SOFT_RESTRICTED
+        )
+
+    inline val isImmutablyRestricted: Boolean
+        get() = permissionInfo.flags.hasBits(PermissionInfo.FLAG_IMMUTABLY_RESTRICTED)
+
     inline val knownCerts: Set<String>
         get() = permissionInfo.knownCerts
 
     inline val hasGids: Boolean
         get() = gids.isNotEmpty()
 
+    inline val footprint: Int
+        get() = name.length + permissionInfo.calculateFootprint()
+
     fun getGidsForUser(userId: Int): IntArray =
         if (areGidsPerUser) {
             IntArray(gids.size) { i -> UserHandle.getUid(userId, gids[i]) }
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index 89a5ed4..e2c2c49 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -40,6 +40,7 @@
 import android.os.UserHandle
 import android.os.UserManager
 import android.permission.IOnPermissionsChangeListener
+import android.permission.PermissionControllerManager
 import android.permission.PermissionManager
 import android.provider.Settings
 import android.util.DebugUtils
@@ -48,10 +49,12 @@
 import com.android.internal.compat.IPlatformCompat
 import com.android.internal.logging.MetricsLogger
 import com.android.internal.logging.nano.MetricsProto
+import com.android.internal.util.DumpUtils
 import com.android.internal.util.Preconditions
 import com.android.server.FgThread
 import com.android.server.LocalManagerRegistry
 import com.android.server.LocalServices
+import com.android.server.PermissionThread
 import com.android.server.ServiceThread
 import com.android.server.SystemConfig
 import com.android.server.permission.access.AccessCheckingService
@@ -80,6 +83,10 @@
 import libcore.util.EmptyArray
 import java.io.FileDescriptor
 import java.io.PrintWriter
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
 
 /**
  * Modern implementation of [PermissionManagerServiceInterface].
@@ -106,6 +113,17 @@
 
     private val mountedStorageVolumes = IndexedSet<String?>()
 
+    private lateinit var permissionControllerManager: PermissionControllerManager
+
+    /**
+     * A permission backup might contain apps that are not installed. In this case we delay the
+     * restoration until the app is installed.
+     *
+     * This array (`userId -> noDelayedBackupLeft`) is `true` for all the users where
+     * there is **no more** delayed backup left.
+     */
+    private val isDelayedPermissionBackupFinished = IntBooleanMap()
+
     fun initialize() {
         metricsLogger = MetricsLogger()
         packageManagerInternal = LocalServices.getService(PackageManagerInternal::class.java)
@@ -161,9 +179,7 @@
                 with(policy) { getPermissionGroups()[permissionGroupName] }
             } ?: return null
 
-            val isPermissionGroupVisible =
-                snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)
-            if (!isPermissionGroupVisible) {
+            if (!snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)) {
                 return null
             }
         }
@@ -199,9 +215,7 @@
                 with(policy) { getPermissions()[permissionName] }
             } ?: return null
 
-            val isPermissionVisible =
-                snapshot.isPackageVisibleToUid(permission.packageName, callingUid)
-            if (!isPermissionVisible) {
+            if (!snapshot.isPackageVisibleToUid(permission.packageName, callingUid)) {
                 return null
             }
 
@@ -274,12 +288,30 @@
         }
     }
 
-    override fun getAllPermissionsWithProtection(protection: Int): List<PermissionInfo> {
-        TODO("Not yet implemented")
-    }
+    override fun getAllPermissionsWithProtection(protection: Int): List<PermissionInfo> =
+        getPermissionsWithProtectionOrProtectionFlags { permission ->
+            permission.protection == protection
+        }
 
-    override fun getAllPermissionsWithProtectionFlags(protectionFlags: Int): List<PermissionInfo> {
-        TODO("Not yet implemented")
+    override fun getAllPermissionsWithProtectionFlags(protectionFlags: Int): List<PermissionInfo> =
+        getPermissionsWithProtectionOrProtectionFlags { permission ->
+            permission.protectionFlags.hasBits(protectionFlags)
+        }
+
+    private inline fun getPermissionsWithProtectionOrProtectionFlags(
+        predicate: (Permission) -> Boolean
+    ): List<PermissionInfo> {
+        service.getState {
+            with(policy) {
+                return getPermissions().mapNotNullIndexed { _, _, permission ->
+                    if (predicate(permission)) {
+                        permission.generatePermissionInfo(0)
+                    } else {
+                        null
+                    }
+                }
+            }
+        }
     }
 
     override fun getPermissionGids(permissionName: String, userId: Int): IntArray {
@@ -290,11 +322,100 @@
     }
 
     override fun addPermission(permissionInfo: PermissionInfo, async: Boolean): Boolean {
-        TODO("Not yet implemented")
+        val permissionName = permissionInfo.name
+        requireNotNull(permissionName) { "permissionName cannot be null" }
+        val callingUid = Binder.getCallingUid()
+        if (packageManagerLocal.withUnfilteredSnapshot().use { it.isUidInstantApp(callingUid) }) {
+            throw SecurityException("Instant apps cannot add permissions")
+        }
+        if (permissionInfo.labelRes == 0 && permissionInfo.nonLocalizedLabel == null) {
+            throw SecurityException("Label must be specified in permission")
+        }
+        val oldPermission: Permission?
+
+        service.mutateState {
+            val permissionTree = getAndEnforcePermissionTree(permissionName)
+            enforcePermissionTreeSize(permissionInfo, permissionTree)
+
+            oldPermission = with(policy) { getPermissions()[permissionName] }
+            if (oldPermission != null && !oldPermission.isDynamic) {
+                throw SecurityException(
+                    "Not allowed to modify non-dynamic permission $permissionName"
+                )
+            }
+
+            permissionInfo.packageName = permissionTree.permissionInfo.packageName
+            @Suppress("DEPRECATION")
+            permissionInfo.protectionLevel =
+                PermissionInfo.fixProtectionLevel(permissionInfo.protectionLevel)
+
+            val newPermission = Permission(
+                permissionInfo, true, Permission.TYPE_DYNAMIC, permissionTree.appId
+            )
+
+            with(policy) { addPermission(newPermission, !async) }
+        }
+
+        return oldPermission == null
     }
 
     override fun removePermission(permissionName: String) {
-        TODO("Not yet implemented")
+        val callingUid = Binder.getCallingUid()
+        if (packageManagerLocal.withUnfilteredSnapshot().use { it.isUidInstantApp(callingUid) }) {
+            throw SecurityException("Instant applications don't have access to this method")
+        }
+        service.mutateState {
+            getAndEnforcePermissionTree(permissionName)
+            val permission = with(policy) { getPermissions()[permissionName] } ?: return@mutateState
+
+            if (!permission.isDynamic) {
+                // TODO(b/67371907): switch to logging if it fails
+                throw SecurityException(
+                    "Not allowed to modify non-dynamic permission $permissionName"
+                )
+            }
+
+            with(policy) { removePermission(permission) }
+        }
+    }
+    private fun GetStateScope.getAndEnforcePermissionTree(permissionName: String): Permission {
+        val callingUid = Binder.getCallingUid()
+        val permissionTree = with(policy) { findPermissionTree(permissionName) }
+        if (permissionTree != null && permissionTree.appId == UserHandle.getAppId(callingUid)) {
+                return permissionTree
+        }
+
+        throw SecurityException(
+            "Calling UID $callingUid is not allowed to add to or remove from the permission tree"
+        )
+    }
+
+    private fun GetStateScope.enforcePermissionTreeSize(
+        permissionInfo: PermissionInfo,
+        permissionTree: Permission
+    ) {
+        // We calculate the max size of permissions defined by this uid and throw
+        // if that plus the size of 'info' would exceed our stated maximum.
+        if (permissionTree.appId != Process.SYSTEM_UID) {
+            val permissionTreeFootprint = calculatePermissionTreeFootprint(permissionTree)
+            if (permissionTreeFootprint + permissionInfo.calculateFootprint() >
+                MAX_PERMISSION_TREE_FOOTPRINT
+            ) {
+                throw SecurityException("Permission tree size cap exceeded")
+            }
+        }
+    }
+
+    private fun GetStateScope.calculatePermissionTreeFootprint(permissionTree: Permission): Int {
+        var size = 0
+        with(policy) {
+            getPermissions().forEachValueIndexed { _, permission ->
+                if (permissionTree.appId == permission.appId) {
+                    size += permission.footprint
+                }
+            }
+        }
+        return size
     }
 
     override fun checkUidPermission(uid: Int, permissionName: String): Int {
@@ -1099,30 +1220,300 @@
         with(policy) { setPermissionFlags(appId, userId, permissionName, newFlags) }
     }
 
+    override fun getAllowlistedRestrictedPermissions(
+        packageName: String,
+        allowlistedFlags: Int,
+        userId: Int
+    ): IndexedList<String>? {
+        requireNotNull(packageName) { "packageName cannot be null" }
+        Preconditions.checkFlagsArgument(allowlistedFlags, PERMISSION_ALLOWLIST_MASK)
+        Preconditions.checkArgumentNonnegative(userId, "userId cannot be null")
+
+        enforceCallingOrSelfCrossUserPermission(
+            userId, enforceFullPermission = false, enforceShellRestriction = false,
+            "getAllowlistedRestrictedPermissions"
+        )
+
+        if (!userManagerInternal.exists(userId)) {
+            Log.w(LOG_TAG, "AllowlistedRestrictedPermission api: Unknown user $userId")
+            return null
+        }
+
+        val callingUid = Binder.getCallingUid()
+        val packageState = packageManagerLocal.withFilteredSnapshot(callingUid, userId)
+            .use { it.getPackageState(packageName) } ?: return null
+        val androidPackage = packageState.androidPackage ?: return null
+
+        val isCallerPrivileged = context.checkCallingOrSelfPermission(
+            Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS
+        ) == PackageManager.PERMISSION_GRANTED
+
+        if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) &&
+            !isCallerPrivileged) {
+            throw SecurityException(
+                "Querying system allowlist requires " +
+                    Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS
+            )
+        }
+
+        val isCallerInstallerOnRecord =
+            packageManagerInternal.isCallerInstallerOfRecord(androidPackage, callingUid)
+
+        if (allowlistedFlags.hasAnyBit(PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE or
+                PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)) {
+            if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
+                throw SecurityException(
+                    "Querying upgrade or installer allowlist requires being installer on record" +
+                        " or ${Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS}"
+                )
+            }
+        }
+
+        return getAllowlistedRestrictedPermissionsUnchecked(
+            packageState.appId, allowlistedFlags, userId
+        )
+    }
+
+    /**
+     * This method does not enforce checks on the caller, should only be called after
+     * required checks.
+     */
+    private fun getAllowlistedRestrictedPermissionsUnchecked(
+        appId: Int,
+        allowlistedFlags: Int,
+        userId: Int
+    ): IndexedList<String>? {
+        val permissionFlags = service.getState {
+            with(policy) { getUidPermissionFlags(appId, userId) }
+        } ?: return null
+
+        var queryFlags = 0
+        if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM)) {
+            queryFlags = queryFlags or PermissionFlags.SYSTEM_EXEMPT
+        }
+        if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE)) {
+            queryFlags = queryFlags or PermissionFlags.UPGRADE_EXEMPT
+        }
+        if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)) {
+            queryFlags = queryFlags or PermissionFlags.INSTALLER_EXEMPT
+        }
+
+        return permissionFlags.mapNotNullIndexed { _, permissionName, flags ->
+            if (flags.hasAnyBit(queryFlags)) permissionName else null
+        }
+    }
+
     override fun addAllowlistedRestrictedPermission(
         packageName: String,
         permissionName: String,
-        flags: Int,
+        allowlistedFlags: Int,
         userId: Int
     ): Boolean {
-        TODO("Not yet implemented")
-    }
+        requireNotNull(permissionName) { "permissionName cannot be null" }
+        if (!enforceRestrictedPermission(permissionName)) {
+            return false
+        }
 
-    override fun getAllowlistedRestrictedPermissions(
-        packageName: String,
-        flags: Int,
-        userId: Int
-    ): MutableList<String> {
-        TODO("Not yet implemented")
+        val permissionNames = getAllowlistedRestrictedPermissions(
+            packageName, allowlistedFlags, userId
+        ) ?: IndexedList(1)
+
+        if (permissionName !in permissionNames) {
+            permissionNames += permissionName
+            return setAllowlistedRestrictedPermissions(
+                packageName, permissionNames, allowlistedFlags, userId, isAddingPermission = true
+            )
+        }
+        return false
     }
 
     override fun removeAllowlistedRestrictedPermission(
         packageName: String,
         permissionName: String,
-        flags: Int,
+        allowlistedFlags: Int,
         userId: Int
     ): Boolean {
-        TODO("Not yet implemented")
+        requireNotNull(permissionName) { "permissionName cannot be null" }
+        if (!enforceRestrictedPermission(permissionName)) {
+            return false
+        }
+
+        val permissions = getAllowlistedRestrictedPermissions(
+            packageName, allowlistedFlags, userId
+        ) ?: return false
+
+        if (permissions.remove(permissionName)) {
+            return setAllowlistedRestrictedPermissions(
+                packageName, permissions, allowlistedFlags, userId, isAddingPermission = false
+            )
+        }
+
+        return false
+    }
+
+    private fun enforceRestrictedPermission(permissionName: String): Boolean {
+        val permission = service.getState { with(policy) { getPermissions()[permissionName] } }
+        if (permission == null) {
+            Log.w(LOG_TAG, "permission definition for $permissionName does not exist")
+            return false
+        }
+
+        if (packageManagerLocal.withFilteredSnapshot()
+                .use { it.getPackageState(permission.packageName) } == null) {
+            return false
+        }
+
+        val isImmutablyRestrictedPermission =
+            permission.isHardOrSoftRestricted && permission.isImmutablyRestricted
+        if (isImmutablyRestrictedPermission && context.checkCallingOrSelfPermission(
+                Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS
+            ) != PackageManager.PERMISSION_GRANTED) {
+            throw SecurityException(
+                "Cannot modify allowlist of an immutably restricted permission: ${permission.name}"
+            )
+        }
+
+        return true
+    }
+
+    private fun setAllowlistedRestrictedPermissions(
+        packageName: String,
+        allowlistedPermissions: List<String>,
+        allowlistedFlags: Int,
+        userId: Int,
+        isAddingPermission: Boolean
+    ): Boolean {
+        Preconditions.checkArgument(allowlistedFlags.countOneBits() == 1)
+
+        val isCallerPrivileged = context.checkCallingOrSelfPermission(
+            Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS
+        ) == PackageManager.PERMISSION_GRANTED
+
+        val callingUid = Binder.getCallingUid()
+        val packageState = packageManagerLocal.withFilteredSnapshot(callingUid, userId)
+            .use { snapshot -> snapshot.packageStates[packageName] ?: return false }
+        val androidPackage = packageState.androidPackage ?: return false
+
+        val isCallerInstallerOnRecord =
+            packageManagerInternal.isCallerInstallerOfRecord(androidPackage, callingUid)
+
+        if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE)) {
+            if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
+                throw SecurityException(
+                    "Modifying upgrade allowlist requires being installer on record or " +
+                        Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS
+                )
+            }
+            if (isAddingPermission && !isCallerPrivileged) {
+                throw SecurityException(
+                    "Adding to upgrade allowlist requires" +
+                        Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS
+                )
+            }
+        }
+
+        setAllowlistedRestrictedPermissionsUnchecked(
+            androidPackage, packageState.appId, allowlistedPermissions, allowlistedFlags, userId
+        )
+
+        return true
+    }
+
+    /**
+     * This method does not enforce checks on the caller, should only be called after
+     * required checks.
+     */
+    private fun setAllowlistedRestrictedPermissionsUnchecked(
+        androidPackage: AndroidPackage,
+        appId: Int,
+        allowlistedPermissions: List<String>,
+        allowlistedFlags: Int,
+        userId: Int
+    ) {
+        service.mutateState {
+            with(policy) {
+                val permissionsFlags =
+                    getUidPermissionFlags(appId, userId) ?: return@mutateState
+
+                val permissions = getPermissions()
+                androidPackage.requestedPermissions.forEachIndexed { _, requestedPermission ->
+                    val permission = permissions[requestedPermission]
+                    if (permission == null || !permission.isHardOrSoftRestricted) {
+                        return@forEachIndexed
+                    }
+
+                    val oldFlags = permissionsFlags[requestedPermission] ?: 0
+                    val wasGranted = PermissionFlags.isPermissionGranted(oldFlags)
+
+                    var newFlags = oldFlags
+                    var mask = 0
+                    var allowlistFlagsCopy = allowlistedFlags
+                    while (allowlistFlagsCopy != 0) {
+                        val flag = 1 shl allowlistFlagsCopy.countTrailingZeroBits()
+                        allowlistFlagsCopy = allowlistFlagsCopy and flag.inv()
+                        when (flag) {
+                            PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM -> {
+                                mask = mask or PermissionFlags.SYSTEM_EXEMPT
+                                newFlags =
+                                    if (allowlistedPermissions.contains(requestedPermission)) {
+                                        newFlags or PermissionFlags.SYSTEM_EXEMPT
+                                    } else {
+                                        newFlags andInv PermissionFlags.SYSTEM_EXEMPT
+                                    }
+                            }
+                            PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE -> {
+                                mask = mask or PermissionFlags.UPGRADE_EXEMPT
+                                newFlags =
+                                    if (allowlistedPermissions.contains(requestedPermission)) {
+                                        newFlags or PermissionFlags.UPGRADE_EXEMPT
+                                    } else {
+                                        newFlags andInv PermissionFlags.UPGRADE_EXEMPT
+                                    }
+                            }
+                            PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER -> {
+                                mask = mask or PermissionFlags.INSTALLER_EXEMPT
+                                newFlags =
+                                    if (allowlistedPermissions.contains(requestedPermission)) {
+                                        newFlags or PermissionFlags.INSTALLER_EXEMPT
+                                    } else {
+                                        newFlags andInv PermissionFlags.INSTALLER_EXEMPT
+                                    }
+                            }
+                        }
+                    }
+
+                    if (oldFlags == newFlags) {
+                        return@forEachIndexed
+                    }
+
+                    val wasAllowlisted = oldFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT)
+                    val isAllowlisted = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT)
+
+                    // If the permission is policy fixed as granted but it is no longer
+                    // on any of the allowlists we need to clear the policy fixed flag
+                    // as allowlisting trumps policy i.e. policy cannot grant a non
+                    // grantable permission.
+                    if (oldFlags.hasBits(PermissionFlags.POLICY_FIXED)) {
+                        if (!isAllowlisted && wasGranted) {
+                            mask = mask or PermissionFlags.POLICY_FIXED
+                            newFlags = newFlags andInv PermissionFlags.POLICY_FIXED
+                        }
+                    }
+
+                    // If we are allowlisting an app that does not support runtime permissions
+                    // we need to make sure it goes through the permission review UI at launch.
+                    if (androidPackage.targetSdkVersion < Build.VERSION_CODES.M &&
+                        !wasAllowlisted && isAllowlisted) {
+                        mask = mask or PermissionFlags.IMPLICIT
+                        newFlags = newFlags or PermissionFlags.IMPLICIT
+                    }
+
+                    updatePermissionFlags(
+                        appId, userId, requestedPermission, mask, newFlags
+                    )
+                }
+            }
+        }
     }
 
     override fun resetRuntimePermissions(androidPackage: AndroidPackage, userId: Int) {
@@ -1159,8 +1550,29 @@
         )
     }
 
+
+
     override fun getAppOpPermissionPackages(permissionName: String): Array<String> {
-        TODO("Not yet implemented")
+        requireNotNull(permissionName) { "permissionName cannot be null" }
+        val packageNames = IndexedSet<String>()
+
+        val permission = service.getState {
+            with(policy) { getPermissions()[permissionName] }
+        }
+        if (permission == null || !permission.isAppOp) {
+            packageNames.toTypedArray()
+        }
+
+        packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
+            snapshot.packageStates.forEach packageStates@{ (_, packageState) ->
+                val androidPackage = packageState.androidPackage ?: return@packageStates
+                if (permissionName in androidPackage.requestedPermissions) {
+                    packageNames += androidPackage.packageName
+                }
+            }
+        }
+
+        return packageNames.toTypedArray()
     }
 
     override fun getAllAppOpPermissionPackages(): Map<String, Set<String>> {
@@ -1183,19 +1595,63 @@
     }
 
     override fun backupRuntimePermissions(userId: Int): ByteArray? {
-        TODO("Not yet implemented")
+        Preconditions.checkArgumentNonnegative(userId, "userId cannot be null")
+        val backup = CompletableFuture<ByteArray>()
+        permissionControllerManager.getRuntimePermissionBackup(
+            UserHandle.of(userId), PermissionThread.getExecutor(), backup::complete
+        )
+
+        return try {
+            backup.get(BACKUP_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
+        } catch (e: Exception) {
+            when (e) {
+                is TimeoutException, is InterruptedException, is ExecutionException -> {
+                    Log.e(LOG_TAG, "Cannot create permission backup for user $userId", e)
+                    null
+                }
+                else -> throw e
+            }
+        }
     }
 
     override fun restoreRuntimePermissions(backup: ByteArray, userId: Int) {
-        TODO("Not yet implemented")
+        requireNotNull(backup) { "backup" }
+        Preconditions.checkArgumentNonnegative(userId, "userId")
+
+        synchronized(isDelayedPermissionBackupFinished) {
+            isDelayedPermissionBackupFinished -= userId
+        }
+        permissionControllerManager.stageAndApplyRuntimePermissionsBackup(
+            backup, UserHandle.of(userId)
+        )
     }
 
     override fun restoreDelayedRuntimePermissions(packageName: String, userId: Int) {
-        TODO("Not yet implemented")
+        requireNotNull(packageName) { "packageName" }
+        Preconditions.checkArgumentNonnegative(userId, "userId")
+
+        synchronized(isDelayedPermissionBackupFinished) {
+            if (isDelayedPermissionBackupFinished.get(userId, false)) {
+                return
+            }
+        }
+        permissionControllerManager.applyStagedRuntimePermissionBackup(
+            packageName, UserHandle.of(userId), PermissionThread.getExecutor()
+        ) { hasMoreBackup ->
+            if (hasMoreBackup) {
+                return@applyStagedRuntimePermissionBackup
+            }
+            synchronized(isDelayedPermissionBackupFinished) {
+                isDelayedPermissionBackupFinished.put(userId, true)
+            }
+        }
     }
 
     override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>?) {
-        TODO("Not yet implemented")
+        if (!DumpUtils.checkDumpPermission(context, LOG_TAG, pw)) {
+            return
+        }
+        context.getSystemService(PermissionControllerManager::class.java)!!.dump(fd, args)
     }
 
     override fun getPermissionTEMP(
@@ -1231,7 +1687,10 @@
     }
 
     override fun onSystemReady() {
-        TODO("Not yet implemented")
+        // TODO STOPSHIP privappPermissionsViolationsfix check
+        permissionControllerManager = PermissionControllerManager(
+            context, PermissionThread.getHandler()
+        )
     }
 
     override fun onUserCreated(userId: Int) {
@@ -1349,13 +1808,12 @@
         if (activityManager != null) {
             val appId = UserHandle.getAppId(uid)
             val userId = UserHandle.getUserId(uid)
-            val identity = Binder.clearCallingIdentity()
-            try {
-                activityManager.killUidForPermissionChange(appId, userId, reason)
-            } catch (e: RemoteException) {
-                /* ignore - same process */
-            } finally {
-                Binder.restoreCallingIdentity(identity)
+            Binder::class.withClearedCallingIdentity {
+                try {
+                    activityManager.killUidForPermissionChange(appId, userId, reason)
+                } catch (e: RemoteException) {
+                    /* ignore - same process */
+                }
             }
         }
     }
@@ -1678,5 +2136,15 @@
         private const val UNREQUESTABLE_MASK = PermissionFlags.RESTRICTION_REVOKED or
             PermissionFlags.SYSTEM_FIXED or PermissionFlags.POLICY_FIXED or
             PermissionFlags.USER_FIXED
+
+        private val BACKUP_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(60)
+
+        /** Cap the size of permission trees that 3rd party apps can define; in characters of text  */
+        private const val MAX_PERMISSION_TREE_FOOTPRINT = 32768
+
+        private const val PERMISSION_ALLOWLIST_MASK =
+            PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE or
+            PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM or
+            PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER
     }
 }
diff --git a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
index 531ab71..4c3ffde 100644
--- a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
@@ -361,7 +361,7 @@
             // Different from the old implementation, which may add an (incomplete) signature
             // permission inside another package's permission tree, we now consistently ignore such
             // permissions.
-            val permissionTree = getPermissionTree(permissionName)
+            val permissionTree = findPermissionTree(permissionName)
             val newPackageName = newPermissionInfo.packageName
             if (permissionTree != null && newPackageName != permissionTree.packageName) {
                 Log.w(
@@ -482,7 +482,7 @@
         if (!permission.isDynamic) {
             return permission
         }
-        val permissionTree = getPermissionTree(permission.name) ?: return permission
+        val permissionTree = findPermissionTree(permission.name) ?: return permission
         @Suppress("DEPRECATION")
         return permission.copy(
             permissionInfo = PermissionInfo(permission.permissionInfo).apply {
@@ -491,18 +491,6 @@
         )
     }
 
-    private fun MutateStateScope.getPermissionTree(permissionName: String): Permission? =
-        newState.systemState.permissionTrees.firstNotNullOfOrNullIndexed {
-            _, permissionTreeName, permissionTree ->
-            if (permissionName.startsWith(permissionTreeName) &&
-                permissionName.length > permissionTreeName.length &&
-                permissionName[permissionTreeName.length] == '.') {
-                permissionTree
-            } else {
-                null
-            }
-        }
-
     private fun MutateStateScope.trimPermissionStates(appId: Int) {
         val requestedPermissions = IndexedSet<String>()
         forEachPackageInAppId(appId) {
@@ -1103,6 +1091,26 @@
         with(persistence) { this@serializeUserState.serializeUserState(state, userId) }
     }
 
+    fun GetStateScope.getPermissionTrees(): IndexedMap<String, Permission> =
+        state.systemState.permissionTrees
+
+    fun GetStateScope.findPermissionTree(permissionName: String): Permission? =
+        state.systemState.permissionTrees.firstNotNullOfOrNullIndexed {
+                _, permissionTreeName, permissionTree ->
+            if (permissionName.startsWith(permissionTreeName) &&
+                permissionName.length > permissionTreeName.length &&
+                permissionName[permissionTreeName.length] == '.') {
+                permissionTree
+            } else {
+                null
+            }
+        }
+
+    fun MutateStateScope.addPermissionTree(permission: Permission) {
+        newState.systemState.permissionTrees[permission.name] = permission
+        newState.systemState.requestWrite()
+    }
+
     /**
      * returns all permission group definitions available in the system
      */
@@ -1115,6 +1123,16 @@
     fun GetStateScope.getPermissions(): IndexedMap<String, Permission> =
         state.systemState.permissions
 
+    fun MutateStateScope.addPermission(permission: Permission, sync: Boolean = false) {
+        newState.systemState.permissions[permission.name] = permission
+        newState.systemState.requestWrite(sync)
+    }
+
+    fun MutateStateScope.removePermission(permission: Permission) {
+        newState.systemState.permissions -= permission.name
+        newState.systemState.requestWrite()
+    }
+
     fun GetStateScope.getUidPermissionFlags(appId: Int, userId: Int): IndexedMap<String, Int>? =
         state.userStates[userId]?.uidPermissionFlags?.get(appId)
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/power/ThermalManagerServiceMockingTest.java b/services/tests/mockingservicestests/src/com/android/server/power/ThermalManagerServiceMockingTest.java
index 34b17c7..c85ed26 100644
--- a/services/tests/mockingservicestests/src/com/android/server/power/ThermalManagerServiceMockingTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/power/ThermalManagerServiceMockingTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import android.hardware.thermal.CoolingType;
@@ -84,6 +85,42 @@
     }
 
     @Test
+    public void setCallback_illegalState_aidl() throws Exception {
+        Mockito.doThrow(new IllegalStateException()).when(
+                mAidlHalMock).registerThermalChangedCallback(Mockito.any());
+        verifyWrapperStatusOnCallbackError();
+    }
+
+    @Test
+    public void setCallback_illegalArgument_aidl() throws Exception {
+        Mockito.doThrow(new IllegalStateException()).when(
+                mAidlHalMock).registerThermalChangedCallback(Mockito.any());
+        verifyWrapperStatusOnCallbackError();
+    }
+
+
+    void verifyWrapperStatusOnCallbackError() throws RemoteException {
+        android.hardware.thermal.Temperature halT1 = new android.hardware.thermal.Temperature();
+        halT1.type = TemperatureType.MODEM;
+        halT1.name = "test1";
+        Mockito.when(mAidlHalMock.getTemperaturesWithType(Mockito.anyInt())).thenReturn(
+                new android.hardware.thermal.Temperature[]{
+                        halT1
+                });
+        List<Temperature> ret = mAidlWrapper.getCurrentTemperatures(true, TemperatureType.MODEM);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getTemperaturesWithType(
+                TemperatureType.MODEM);
+        assertNotNull(ret);
+        Temperature expectedT1 = new Temperature(halT1.value, halT1.type, halT1.name,
+                halT1.throttlingStatus);
+        List<Temperature> expectedRet = List.of(expectedT1);
+        // test that even if the callback fails to register without hal connection error, the
+        // wrapper should still work
+        assertTrue("Got temperature list as " + ret + " with different values compared to "
+                + expectedRet, expectedRet.containsAll(ret));
+    }
+
+    @Test
     public void getCurrentTemperatures_withFilter_aidl() throws RemoteException {
         android.hardware.thermal.Temperature halT1 = new android.hardware.thermal.Temperature();
         halT1.type = TemperatureType.MODEM;
@@ -135,7 +172,42 @@
         List<Temperature> expectedRet = List.of(
                 new Temperature(halTInvalid.value, halTInvalid.type, halTInvalid.name,
                         ThrottlingSeverity.NONE));
-        assertEquals(expectedRet, ret);
+        assertTrue("Got temperature list as " + ret + " with different values compared to "
+                + expectedRet, expectedRet.containsAll(ret));
+    }
+
+    @Test
+    public void getCurrentTemperatures_illegalArgument_aidl() throws RemoteException {
+        Mockito.when(mAidlHalMock.getTemperatures()).thenThrow(new IllegalArgumentException());
+        List<Temperature> ret = mAidlWrapper.getCurrentTemperatures(false, 0);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getTemperatures();
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
+
+        Mockito.when(mAidlHalMock.getTemperaturesWithType(TemperatureType.MODEM)).thenThrow(
+                new IllegalArgumentException());
+        ret = mAidlWrapper.getCurrentTemperatures(true, TemperatureType.MODEM);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getTemperaturesWithType(
+                TemperatureType.MODEM);
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
+    }
+
+    @Test
+    public void getCurrentTemperatures_illegalState_aidl() throws RemoteException {
+        Mockito.when(mAidlHalMock.getTemperatures()).thenThrow(new IllegalStateException());
+        List<Temperature> ret = mAidlWrapper.getCurrentTemperatures(false, 0);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getTemperatures();
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
+
+        Mockito.when(mAidlHalMock.getTemperaturesWithType(TemperatureType.MODEM)).thenThrow(
+                new IllegalStateException());
+        ret = mAidlWrapper.getCurrentTemperatures(true, TemperatureType.MODEM);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getTemperaturesWithType(
+                TemperatureType.MODEM);
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
     }
 
     @Test
@@ -187,6 +259,40 @@
     }
 
     @Test
+    public void getCurrentCoolingDevices_illegalArgument_aidl() throws RemoteException {
+        Mockito.when(mAidlHalMock.getCoolingDevices()).thenThrow(new IllegalArgumentException());
+        List<CoolingDevice> ret = mAidlWrapper.getCurrentCoolingDevices(false, 0);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getCoolingDevices();
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
+
+        Mockito.when(mAidlHalMock.getCoolingDevicesWithType(Mockito.anyInt())).thenThrow(
+                new IllegalArgumentException());
+        ret = mAidlWrapper.getCurrentCoolingDevices(true, CoolingType.SPEAKER);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getCoolingDevicesWithType(
+                CoolingType.SPEAKER);
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
+    }
+
+    @Test
+    public void getCurrentCoolingDevices_illegalState_aidl() throws RemoteException {
+        Mockito.when(mAidlHalMock.getCoolingDevices()).thenThrow(new IllegalStateException());
+        List<CoolingDevice> ret = mAidlWrapper.getCurrentCoolingDevices(false, 0);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getCoolingDevices();
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
+
+        Mockito.when(mAidlHalMock.getCoolingDevicesWithType(Mockito.anyInt())).thenThrow(
+                new IllegalStateException());
+        ret = mAidlWrapper.getCurrentCoolingDevices(true, CoolingType.SPEAKER);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getCoolingDevicesWithType(
+                CoolingType.SPEAKER);
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
+    }
+
+    @Test
     public void getTemperatureThresholds_withFilter_aidl() throws RemoteException {
         TemperatureThreshold halT1 = new TemperatureThreshold();
         halT1.name = "test1";
@@ -215,4 +321,44 @@
         assertArrayEquals(halT1.hotThrottlingThresholds, threshold.hotThrottlingThresholds, 0.1f);
         assertArrayEquals(halT1.coldThrottlingThresholds, threshold.coldThrottlingThresholds, 0.1f);
     }
+
+    @Test
+    public void getTemperatureThresholds_illegalArgument_aidl() throws RemoteException {
+        Mockito.when(mAidlHalMock.getTemperatureThresholdsWithType(Mockito.anyInt())).thenThrow(
+                new IllegalArgumentException());
+        List<TemperatureThreshold> ret = mAidlWrapper.getTemperatureThresholds(true,
+                Temperature.TYPE_SOC);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getTemperatureThresholdsWithType(
+                Temperature.TYPE_SOC);
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
+
+        Mockito.when(mAidlHalMock.getTemperatureThresholds()).thenThrow(
+                new IllegalArgumentException());
+        ret = mAidlWrapper.getTemperatureThresholds(false,
+                Temperature.TYPE_SOC);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getTemperatureThresholds();
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
+    }
+
+    @Test
+    public void getTemperatureThresholds_illegalState_aidl() throws RemoteException {
+        Mockito.when(mAidlHalMock.getTemperatureThresholdsWithType(Mockito.anyInt())).thenThrow(
+                new IllegalStateException());
+        List<TemperatureThreshold> ret = mAidlWrapper.getTemperatureThresholds(true,
+                Temperature.TYPE_SOC);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getTemperatureThresholdsWithType(
+                Temperature.TYPE_SOC);
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
+
+        Mockito.when(mAidlHalMock.getTemperatureThresholds()).thenThrow(
+                new IllegalStateException());
+        ret = mAidlWrapper.getTemperatureThresholds(false,
+                Temperature.TYPE_SOC);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getTemperatureThresholds();
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/StateMachineTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/StateMachineTest.java
index e82a7c2..fb0ce56 100644
--- a/services/tests/wmtests/src/com/android/server/wm/utils/StateMachineTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/StateMachineTest.java
@@ -215,6 +215,20 @@
     }
 
     @Test
+    public void testStateMachineTriggerStateActionDelegateRoot() {
+        final StringBuffer log = new StringBuffer();
+
+        StateMachine stateMachine = new StateMachine(0x2);
+        stateMachine.addStateHandler(0x0, new LoggingHandler(0x0, log));
+        stateMachine.addStateHandler(0x2,
+                new LoggingHandler(0x2, log, false /* handleSelf */));
+
+        // state 0x2 delegate the message handling to its parent state
+        stateMachine.handle(0, null);
+        assertEquals("h0;", log.toString());
+    }
+
+    @Test
     public void testStateMachineNestedTransition() {
         final StringBuffer log = new StringBuffer();
 
diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
index ca11629..ff4268f 100644
--- a/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
+++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
@@ -80,38 +80,46 @@
     /**
      * USB data status is not known.
      */
-    public static final int USB_DATA_STATUS_UNKNOWN = 0;
+    public static final int AIDL_USB_DATA_STATUS_UNKNOWN = 0;
 
     /**
      * USB data is enabled.
      */
-    public static final int USB_DATA_STATUS_ENABLED = 1;
+    public static final int AIDL_USB_DATA_STATUS_ENABLED = 1;
 
     /**
      * USB data is disabled as the port is too hot.
      */
-    public static final int USB_DATA_STATUS_DISABLED_OVERHEAT = 2;
+    public static final int AIDL_USB_DATA_STATUS_DISABLED_OVERHEAT = 2;
 
     /**
      * USB data is disabled due to contaminated port.
      */
-    public static final int USB_DATA_STATUS_DISABLED_CONTAMINANT = 3;
+    public static final int AIDL_USB_DATA_STATUS_DISABLED_CONTAMINANT = 3;
 
     /**
-     * USB data is disabled due to docking event.
+     * USB data(both host mode and device mode) is disabled due to docking event.
      */
-    public static final int USB_DATA_STATUS_DISABLED_DOCK = 4;
+    public static final int AIDL_USB_DATA_STATUS_DISABLED_DOCK = 4;
 
     /**
      * USB data is disabled by
      * {@link UsbPort#enableUsbData UsbPort.enableUsbData}.
      */
-    public static final int USB_DATA_STATUS_DISABLED_FORCE = 5;
+    public static final int AIDL_USB_DATA_STATUS_DISABLED_FORCE = 5;
 
     /**
      * USB data is disabled for debug.
      */
-    public static final int USB_DATA_STATUS_DISABLED_DEBUG = 6;
+    public static final int AIDL_USB_DATA_STATUS_DISABLED_DEBUG = 6;
+    /**
+     * USB host mode disabled due to docking event.
+     */
+    public static final int AIDL_USB_DATA_STATUS_DISABLED_DOCK_HOST_MODE = 7;
+    /**
+     * USB device mode disabled due to docking event.
+     */
+    public static final int AIDL_USB_DATA_STATUS_DISABLED_DOCK_DEVICE_MODE = 8;
 
     public @UsbHalVersion int getUsbHalVersion() throws RemoteException {
         synchronized (mLock) {
@@ -529,24 +537,43 @@
             int usbDataStatus = UsbPortStatus.DATA_STATUS_UNKNOWN;
             for (int i = 0; i < usbDataStatusHal.length; i++) {
                 switch (usbDataStatusHal[i]) {
-                    case USB_DATA_STATUS_ENABLED:
+                    case AIDL_USB_DATA_STATUS_ENABLED:
                         usbDataStatus |= UsbPortStatus.DATA_STATUS_ENABLED;
                         break;
-                    case USB_DATA_STATUS_DISABLED_OVERHEAT:
+                    case AIDL_USB_DATA_STATUS_DISABLED_OVERHEAT:
                         usbDataStatus |= UsbPortStatus.DATA_STATUS_DISABLED_OVERHEAT;
                         break;
-                    case USB_DATA_STATUS_DISABLED_CONTAMINANT:
+                    case AIDL_USB_DATA_STATUS_DISABLED_CONTAMINANT:
                         usbDataStatus |= UsbPortStatus.DATA_STATUS_DISABLED_CONTAMINANT;
                         break;
-                    case USB_DATA_STATUS_DISABLED_DOCK:
+                    /* Indicates both host and gadget mode being disabled. */
+                    case AIDL_USB_DATA_STATUS_DISABLED_DOCK:
                         usbDataStatus |= UsbPortStatus.DATA_STATUS_DISABLED_DOCK;
+                        usbDataStatus |= UsbPortStatus.DATA_STATUS_DISABLED_DOCK_HOST_MODE;
+                        usbDataStatus |= UsbPortStatus.DATA_STATUS_DISABLED_DOCK_DEVICE_MODE;
                         break;
-                    case USB_DATA_STATUS_DISABLED_FORCE:
+                    case AIDL_USB_DATA_STATUS_DISABLED_FORCE:
                         usbDataStatus |= UsbPortStatus.DATA_STATUS_DISABLED_FORCE;
                         break;
-                    case USB_DATA_STATUS_DISABLED_DEBUG:
+                    case AIDL_USB_DATA_STATUS_DISABLED_DEBUG:
                         usbDataStatus |= UsbPortStatus.DATA_STATUS_DISABLED_DEBUG;
                         break;
+                    /*
+                     * Set DATA_STATUS_DISABLED_DOCK when DATA_STATUS_DISABLED_DOCK_HOST_MODE
+                     * is set.
+                     */
+                    case AIDL_USB_DATA_STATUS_DISABLED_DOCK_HOST_MODE:
+                        usbDataStatus |= UsbPortStatus.DATA_STATUS_DISABLED_DOCK_HOST_MODE;
+                        usbDataStatus |= UsbPortStatus.DATA_STATUS_DISABLED_DOCK;
+                        break;
+                    /*
+                     * Set DATA_STATUS_DISABLED_DOCK when DATA_STATUS_DISABLED_DEVICE_DOCK
+                     * is set.
+                     */
+                    case AIDL_USB_DATA_STATUS_DISABLED_DOCK_DEVICE_MODE:
+                        usbDataStatus |= UsbPortStatus.DATA_STATUS_DISABLED_DOCK_DEVICE_MODE;
+                        usbDataStatus |= UsbPortStatus.DATA_STATUS_DISABLED_DOCK;
+                        break;
                     default:
                         usbDataStatus |= UsbPortStatus.DATA_STATUS_UNKNOWN;
                 }
diff --git a/telecomm/java/android/telecom/CallAttributes.aidl b/telecomm/java/android/telecom/CallAttributes.aidl
new file mode 100644
index 0000000..19bada7
--- /dev/null
+++ b/telecomm/java/android/telecom/CallAttributes.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+/**
+ * {@hide}
+ */
+parcelable CallAttributes;
\ No newline at end of file
diff --git a/telecomm/java/android/telecom/CallAttributes.java b/telecomm/java/android/telecom/CallAttributes.java
new file mode 100644
index 0000000..6d87981
--- /dev/null
+++ b/telecomm/java/android/telecom/CallAttributes.java
@@ -0,0 +1,344 @@
+/*
+ * 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.telecom;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * CallAttributes represents a set of properties that define a new Call.  Apps should build an
+ * instance of this class and use {@link TelecomManager#addCall} to start a new call with Telecom.
+ *
+ * <p>
+ * Apps should first register a {@link PhoneAccount} via {@link TelecomManager#registerPhoneAccount}
+ * and use the same {@link PhoneAccountHandle} registered with Telecom when creating an
+ * instance of CallAttributes.
+ */
+public final class CallAttributes implements Parcelable {
+
+    /** PhoneAccountHandle associated with the App managing calls **/
+    private final PhoneAccountHandle mPhoneAccountHandle;
+
+    /** Display name of the person on the other end of the call **/
+    private final CharSequence mDisplayName;
+
+    /** Address of the call. Note, this can be extended to a meeting link **/
+    private final Uri mAddress;
+
+    /** The direction (Outgoing/Incoming) of the new Call **/
+    @Direction private final int mDirection;
+
+    /** Information related to data being transmitted (voice, video, etc. ) **/
+    @CallType private final int mCallType;
+
+    /** Allows a package to opt into capabilities on the telecom side, on a per-call basis **/
+    @CallCapability private final int mCallCapabilities;
+
+    /** @hide **/
+    public static final String CALL_CAPABILITIES_KEY = "TelecomCapabilities";
+
+    private CallAttributes(@NonNull PhoneAccountHandle phoneAccountHandle,
+            @NonNull CharSequence displayName,
+            @NonNull Uri address,
+            int direction,
+            int callType,
+            int callCapabilities) {
+        mPhoneAccountHandle = phoneAccountHandle;
+        mDisplayName = displayName;
+        mAddress = address;
+        mDirection = direction;
+        mCallType = callType;
+        mCallCapabilities = callCapabilities;
+    }
+
+    /** @hide */
+    @IntDef(value = {DIRECTION_INCOMING, DIRECTION_OUTGOING})
+    public @interface Direction {
+    }
+    /**
+     * Indicates that the call is an incoming call.
+     */
+    public static final int DIRECTION_INCOMING = 1;
+    /**
+     * Indicates that the call is an outgoing call.
+     */
+    public static final int DIRECTION_OUTGOING = 2;
+
+    /** @hide */
+    @IntDef(value = {AUDIO_CALL, VIDEO_CALL})
+    public @interface CallType {
+    }
+    /**
+     * Used when answering or dialing a call to indicate that the call does not have a video
+     * component
+     */
+    public static final int AUDIO_CALL = 1;
+    /**
+     * Indicates video transmission is supported
+     */
+    public static final int VIDEO_CALL = 2;
+
+    /** @hide */
+    @IntDef(value = {SUPPORTS_SET_INACTIVE, SUPPORTS_STREAM, SUPPORTS_TRANSFER}, flag = true)
+    public @interface CallCapability {
+    }
+    /**
+     * The call being created can be set to inactive (traditionally referred to as hold).  This
+     * means that once a new call goes active, if the active call needs to be held in order to
+     * place or receive an incoming call, the active call will be placed on hold.  otherwise, the
+     * active call may be disconnected.
+     */
+    public static final int SUPPORTS_SET_INACTIVE = 1 << 1;
+    /**
+     * The call can be streamed from a root device to another device to continue the call without
+     * completely transferring it.
+     */
+    public static final int SUPPORTS_STREAM = 1 << 2;
+    /**
+     * The call can be completely transferred from one endpoint to another
+     */
+    public static final int SUPPORTS_TRANSFER = 1 << 3;
+
+    /**
+     * Build an instance of {@link CallAttributes}. In order to build a valid instance, a
+     * {@link PhoneAccountHandle}, call {@link Direction}, display name, and {@link Uri} address
+     * are required.
+     *
+     * <p>
+     * Note: Pass in the same {@link PhoneAccountHandle} that was used to register a
+     * {@link PhoneAccount} with Telecom. see {@link TelecomManager#registerPhoneAccount}
+     */
+    public static final class Builder {
+        // required and final fields
+        private final PhoneAccountHandle mPhoneAccountHandle;
+        @Direction private final int mDirection;
+        private final CharSequence mDisplayName;
+        private final Uri mAddress;
+        // optional fields
+        @CallType private int mCallType = CallAttributes.AUDIO_CALL;
+        @CallCapability private int mCallCapabilities = SUPPORTS_SET_INACTIVE;
+
+        /**
+         * Constructor for the CallAttributes.Builder class
+         *
+         * @param phoneAccountHandle that belongs to package registered with Telecom
+         * @param callDirection of the new call that will be added to Telecom
+         * @param displayName of the caller for incoming calls or initiating user for outgoing calls
+         * @param address of the caller for incoming calls or destination for outgoing calls
+         */
+        public Builder(@NonNull PhoneAccountHandle phoneAccountHandle,
+                @Direction int callDirection, @NonNull CharSequence displayName,
+                @NonNull Uri address) {
+            if (!isInRange(DIRECTION_INCOMING, DIRECTION_OUTGOING, callDirection)) {
+                throw new IllegalArgumentException(TextUtils.formatSimple("CallDirection=[%d] is"
+                                + " invalid. CallDirections value should be within [%d, %d]",
+                        callDirection, DIRECTION_INCOMING, DIRECTION_OUTGOING));
+            }
+            Objects.requireNonNull(phoneAccountHandle);
+            Objects.requireNonNull(displayName);
+            Objects.requireNonNull(address);
+            mPhoneAccountHandle = phoneAccountHandle;
+            mDirection = callDirection;
+            mDisplayName = displayName;
+            mAddress = address;
+        }
+
+        /**
+         * @param callType see {@link CallType} for valid arguments
+         * @return Builder
+         */
+        @NonNull
+        public Builder setCallType(@CallType int callType) {
+            if (!isInRange(AUDIO_CALL, VIDEO_CALL, callType)) {
+                throw new IllegalArgumentException(TextUtils.formatSimple("CallType=[%d] is"
+                                + " invalid. CallTypes value should be within [%d, %d]",
+                        callType, AUDIO_CALL, VIDEO_CALL));
+            }
+            mCallType = callType;
+            return this;
+        }
+
+        /**
+         * @param callCapabilities see {@link CallCapability} for valid arguments
+         * @return Builder
+         */
+        @NonNull
+        public Builder setCallCapabilities(@CallCapability int callCapabilities) {
+            mCallCapabilities = callCapabilities;
+            return this;
+        }
+
+        /**
+         * Build an instance of {@link CallAttributes} based on the last values passed to the
+         * setters or default values.
+         *
+         * @return an instance of {@link CallAttributes}
+         */
+        @NonNull
+        public CallAttributes build() {
+            return new CallAttributes(mPhoneAccountHandle, mDisplayName, mAddress, mDirection,
+                    mCallType, mCallCapabilities);
+        }
+
+        /** @hide */
+        private boolean isInRange(int floor, int ceiling, int value) {
+            return value >= floor && value <= ceiling;
+        }
+    }
+
+    /**
+     * The {@link PhoneAccountHandle} that should be registered to Telecom to allow calls.  The
+     * {@link PhoneAccountHandle} should be registered before creating a CallAttributes instance.
+     *
+     * @return the {@link PhoneAccountHandle} for this package that allows this call to be created
+     */
+    @NonNull public PhoneAccountHandle getPhoneAccountHandle() {
+        return mPhoneAccountHandle;
+    }
+
+    /**
+     * @return display name of the incoming caller or the person being called for an outgoing call
+     */
+    @NonNull public CharSequence getDisplayName() {
+        return mDisplayName;
+    }
+
+    /**
+     * @return address of the incoming caller
+     *           or the address of the person being called for an outgoing call
+     */
+    @NonNull public Uri getAddress() {
+        return mAddress;
+    }
+
+    /**
+     * @return the direction of the new call.
+     */
+    public @Direction int getDirection() {
+        return mDirection;
+    }
+
+    /**
+     * @return Information related to data being transmitted (voice, video, etc. )
+     */
+    public @CallType int getCallType() {
+        return mCallType;
+    }
+
+    /**
+     * @return The allowed capabilities of the new call
+     */
+    public @CallCapability int getCallCapabilities() {
+        return mCallCapabilities;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@Nullable Parcel dest, int flags) {
+        dest.writeParcelable(mPhoneAccountHandle, flags);
+        dest.writeCharSequence(mDisplayName);
+        dest.writeParcelable(mAddress, flags);
+        dest.writeInt(mDirection);
+        dest.writeInt(mCallType);
+        dest.writeInt(mCallCapabilities);
+    }
+
+    /**
+     * Responsible for creating CallAttribute objects for deserialized Parcels.
+     */
+    public static final @android.annotation.NonNull
+            Parcelable.Creator<CallAttributes> CREATOR =
+            new Parcelable.Creator<>() {
+                @Override
+                public CallAttributes createFromParcel(Parcel source) {
+                    return new CallAttributes(source.readParcelable(getClass().getClassLoader(),
+                            android.telecom.PhoneAccountHandle.class),
+                            source.readCharSequence(),
+                            source.readParcelable(getClass().getClassLoader(),
+                                    android.net.Uri.class),
+                            source.readInt(),
+                            source.readInt(),
+                            source.readInt());
+                }
+
+                @Override
+                public CallAttributes[] newArray(int size) {
+                    return new CallAttributes[size];
+                }
+            };
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append("{ CallAttributes: [phoneAccountHandle: ")
+                .append(mPhoneAccountHandle)  /* PhoneAccountHandle#toString handles PII */
+                .append("], [contactName: ")
+                .append(Log.pii(mDisplayName))
+                .append("], [address=")
+                .append(Log.pii(mAddress))
+                .append("], [direction=")
+                .append(mDirection)
+                .append("], [callType=")
+                .append(mCallType)
+                .append("], [mCallCapabilities=")
+                .append(mCallCapabilities)
+                .append("]  }");
+
+        return sb.toString();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || obj.getClass() != this.getClass()) {
+            return false;
+        }
+        CallAttributes that = (CallAttributes) obj;
+        return this.mDirection == that.mDirection
+                && this.mCallType == that.mCallType
+                && this.mCallCapabilities == that.mCallCapabilities
+                && Objects.equals(this.mPhoneAccountHandle, that.mPhoneAccountHandle)
+                && Objects.equals(this.mAddress, that.mAddress)
+                && Objects.equals(this.mDisplayName, that.mDisplayName);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(mPhoneAccountHandle, mAddress, mDisplayName,
+                mDirection, mCallType, mCallCapabilities);
+    }
+}
diff --git a/telecomm/java/android/telecom/CallControl.aidl b/telecomm/java/android/telecom/CallControl.aidl
new file mode 100644
index 0000000..0f780e6
--- /dev/null
+++ b/telecomm/java/android/telecom/CallControl.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+/**
+ * {@hide}
+ */
+parcelable CallControl;
diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java
new file mode 100644
index 0000000..3bda6f4
--- /dev/null
+++ b/telecomm/java/android/telecom/CallControl.java
@@ -0,0 +1,250 @@
+/*
+ * 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.telecom;
+
+import static android.telecom.CallException.TRANSACTION_EXCEPTION_KEY;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.OutcomeReceiver;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+
+import com.android.internal.telecom.ClientTransactionalServiceRepository;
+import com.android.internal.telecom.ICallControl;
+
+import java.util.concurrent.Executor;
+
+/**
+ * CallControl provides client side control of a call.  Each Call will get an individual CallControl
+ * instance in which the client can alter the state of the associated call.
+ *
+ * <p>
+ * Each method is Transactional meaning that it can succeed or fail. If a transaction succeeds,
+ * the {@link OutcomeReceiver#onResult} will be called by Telecom.  Otherwise, the
+ * {@link OutcomeReceiver#onError} is called and provides a {@link CallException} that details why
+ * the operation failed.
+ */
+public final class CallControl implements AutoCloseable {
+    private static final String TAG = CallControl.class.getSimpleName();
+    private static final String INTERFACE_ERROR_MSG = "Call Control is not available";
+    private final String mCallId;
+    private final ICallControl mServerInterface;
+    private final PhoneAccountHandle mPhoneAccountHandle;
+    private final ClientTransactionalServiceRepository mRepository;
+
+    /** @hide */
+    public CallControl(@NonNull String callId, @Nullable ICallControl serverInterface,
+            @NonNull ClientTransactionalServiceRepository repository,
+            @NonNull PhoneAccountHandle pah) {
+        mCallId = callId;
+        mServerInterface = serverInterface;
+        mRepository = repository;
+        mPhoneAccountHandle = pah;
+    }
+
+    /**
+     * @return the callId Telecom assigned to this CallControl object which should be attached to
+     *  an individual call.
+     */
+    @NonNull
+    public ParcelUuid getCallId() {
+        return ParcelUuid.fromString(mCallId);
+    }
+
+    /**
+     * Request Telecom set the call state to active.
+     *
+     * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
+     *                will be called on.
+     * @param callback that will be completed on the Telecom side that details success or failure
+     *                of the requested operation.
+     *
+     *                 {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
+     *                 switched the call state to active
+     *
+     *                 {@link OutcomeReceiver#onError} will be called if Telecom has failed to set
+     *                 the call state to active.  A {@link CallException} will be passed
+     *                 that details why the operation failed.
+     */
+    public void setActive(@CallbackExecutor @NonNull Executor executor,
+            @NonNull OutcomeReceiver<Void, CallException> callback) {
+        if (mServerInterface != null) {
+            try {
+                mServerInterface.setActive(mCallId,
+                        new CallControlResultReceiver("setActive", executor, callback));
+
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+        } else {
+            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        }
+    }
+
+    /**
+     * Request Telecom set the call state to inactive. This the same as hold for two call endpoints
+     * but can be extended to setting a meeting to inactive.
+     *
+     * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
+     *                will be called on.
+     * @param callback that will be completed on the Telecom side that details success or failure
+     *                of the requested operation.
+     *
+     *                 {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
+     *                 switched the call state to inactive
+     *
+     *                 {@link OutcomeReceiver#onError} will be called if Telecom has failed to set
+     *                 the call state to inactive.  A {@link CallException} will be passed
+     *                 that details why the operation failed.
+     */
+    public void setInactive(@CallbackExecutor @NonNull Executor executor,
+            @NonNull OutcomeReceiver<Void, CallException> callback) {
+        if (mServerInterface != null) {
+            try {
+                mServerInterface.setInactive(mCallId,
+                        new CallControlResultReceiver("setInactive", executor, callback));
+
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+        } else {
+            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        }
+    }
+
+    /**
+     * Request Telecom set the call state to disconnect.
+     *
+     * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
+     *                will be called on.
+     * @param callback that will be completed on the Telecom side that details success or failure
+     *                of the requested operation.
+     *
+     *                 {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
+     *                 disconnected the call.
+     *
+     *                 {@link OutcomeReceiver#onError} will be called if Telecom has failed to
+     *                 disconnect the call.  A {@link CallException} will be passed
+     *                 that details why the operation failed.
+     */
+    public void disconnect(@NonNull DisconnectCause disconnectCause,
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull OutcomeReceiver<Void, CallException> callback) {
+        if (mServerInterface != null) {
+            try {
+                mServerInterface.disconnect(mCallId, disconnectCause,
+                        new CallControlResultReceiver("disconnect", executor, callback));
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+        } else {
+            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        }
+    }
+
+    /**
+     * Request Telecom reject the incoming call.
+     *
+     * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
+     *                will be called on.
+     * @param callback that will be completed on the Telecom side that details success or failure
+     *                of the requested operation.
+     *
+     *                 {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
+     *                 rejected the incoming call.
+     *
+     *                 {@link OutcomeReceiver#onError} will be called if Telecom has failed to
+     *                 reject the incoming call.  A {@link CallException} will be passed
+     *                 that details why the operation failed.
+     */
+    public void rejectCall(@CallbackExecutor @NonNull Executor executor,
+            @NonNull OutcomeReceiver<Void, CallException> callback) {
+        if (mServerInterface != null) {
+            try {
+                mServerInterface.rejectCall(mCallId,
+                        new CallControlResultReceiver("rejectCall", executor, callback));
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+        } else {
+            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        }
+    }
+
+    /**
+     * This method should be called after
+     * {@link CallControl#disconnect(DisconnectCause, Executor, OutcomeReceiver)} or
+     * {@link CallControl#rejectCall(Executor, OutcomeReceiver)}
+     * to destroy all references of this object and avoid memory leaks.
+     */
+    @Override
+    public void close() {
+        mRepository.removeCallFromServiceWrapper(mPhoneAccountHandle, mCallId);
+    }
+
+    /**
+     * Since {@link OutcomeReceiver}s cannot be passed via AIDL, a ResultReceiver (which can) must
+     * wrap the Clients {@link OutcomeReceiver} passed in and await for the Telecom Server side
+     * response in {@link ResultReceiver#onReceiveResult(int, Bundle)}.
+     * @hide */
+    private class CallControlResultReceiver extends ResultReceiver {
+        private final String mCallingMethod;
+        private final Executor mExecutor;
+        private final OutcomeReceiver<Void, CallException> mClientCallback;
+
+        CallControlResultReceiver(String method, Executor executor,
+                OutcomeReceiver<Void, CallException> clientCallback) {
+            super(null);
+            mCallingMethod = method;
+            mExecutor = executor;
+            mClientCallback = clientCallback;
+        }
+
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            Log.d(CallControl.TAG, "%s: oRR: resultCode=[%s]", mCallingMethod, resultCode);
+            super.onReceiveResult(resultCode, resultData);
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                if (resultCode == TelecomManager.TELECOM_TRANSACTION_SUCCESS) {
+                    mExecutor.execute(() -> mClientCallback.onResult(null));
+                } else {
+                    mExecutor.execute(() ->
+                            mClientCallback.onError(getTransactionException(resultData)));
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+    }
+
+    /** @hide */
+    private CallException getTransactionException(Bundle resultData) {
+        String message = "unknown error";
+        if (resultData != null && resultData.containsKey(TRANSACTION_EXCEPTION_KEY)) {
+            return resultData.getParcelable(TRANSACTION_EXCEPTION_KEY,
+                    CallException.class);
+        }
+        return new CallException(message, CallException.CODE_ERROR_UNKNOWN);
+    }
+}
diff --git a/telecomm/java/android/telecom/CallEventCallback.java b/telecomm/java/android/telecom/CallEventCallback.java
new file mode 100644
index 0000000..a26291f
--- /dev/null
+++ b/telecomm/java/android/telecom/CallEventCallback.java
@@ -0,0 +1,103 @@
+/*
+ * 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.telecom;
+
+
+import android.annotation.NonNull;
+
+import java.util.function.Consumer;
+
+/**
+ * CallEventCallback relays updates to a call from the Telecom framework.
+ * This can include operations which the app must implement on a Call due to the presence of other
+ * calls on the device, requests relayed from a Bluetooth device, or from another calling surface.
+ *
+ * <p>
+ * CallEventCallbacks with {@link Consumer}s are transactional, meaning that a client must
+ * complete the {@link Consumer} via {@link Consumer#accept(Object)} in order to complete the
+ * CallEventCallback. If a CallEventCallback can be completed, the
+ * {@link Consumer#accept(Object)} should be called with {@link Boolean#TRUE}. Otherwise,
+ * {@link Consumer#accept(Object)} should be called with {@link Boolean#FALSE} to represent the
+ * CallEventCallback cannot be completed on the client side.
+ *
+ * <p>
+ * Note: Each CallEventCallback has a timeout of 5000 milliseconds. Failing to complete the
+ * {@link Consumer} before the timeout will result in a failed transaction.
+ */
+public interface CallEventCallback {
+    /**
+     * Telecom is informing the client to set the call active
+     *
+     * @param wasCompleted The {@link Consumer} to be completed. If the client can set the call
+     *                     active on their end, the {@link Consumer#accept(Object)} should be
+     *                     called with {@link Boolean#TRUE}. Otherwise,
+     *                     {@link Consumer#accept(Object)} should be called with
+     *                     {@link Boolean#FALSE}.
+     */
+    void onSetActive(@NonNull Consumer<Boolean> wasCompleted);
+
+    /**
+     * Telecom is informing the client to set the call inactive. This is the same as holding a call
+     * for two endpoints but can be extended to setting a meeting inactive.
+     *
+     * @param wasCompleted The {@link Consumer} to be completed. If the client can set the call
+     *                     inactive on their end, the {@link Consumer#accept(Object)} should be
+     *                     called with {@link Boolean#TRUE}. Otherwise,
+     *                     {@link Consumer#accept(Object)} should be called with
+     *                     {@link Boolean#FALSE}.
+     */
+    void onSetInactive(@NonNull Consumer<Boolean> wasCompleted);
+
+    /**
+     * Telecom is informing the client to answer an incoming call and set it to active.
+     *
+     * @param videoState   see {@link android.telecom.CallAttributes.CallType} for valid states
+     * @param wasCompleted The {@link Consumer} to be completed. If the client can answer the call
+     *                     on their end, {@link Consumer#accept(Object)} should be called with
+     *                     {@link Boolean#TRUE}. Otherwise, {@link Consumer#accept(Object)} should
+     *                     be called with {@link Boolean#FALSE}.
+     */
+    void onAnswer(@android.telecom.CallAttributes.CallType int videoState,
+            @NonNull Consumer<Boolean> wasCompleted);
+
+    /**
+     * Telecom is informing the client to reject the incoming call
+     *
+     * @param wasCompleted The {@link Consumer} to be completed. If the client can reject the
+     *                     incoming call, {@link Consumer#accept(Object)} should be called with
+     *                     {@link Boolean#TRUE}. Otherwise, {@link Consumer#accept(Object)}
+     *                     should  be called with {@link Boolean#FALSE}.
+     */
+    void onReject(@NonNull Consumer<Boolean> wasCompleted);
+
+    /**
+     * Telecom is informing the client to disconnect the call
+     *
+     * @param wasCompleted The {@link Consumer} to be completed. If the client can disconnect the
+     *                     call on their end, {@link Consumer#accept(Object)} should be called with
+     *                     {@link Boolean#TRUE}. Otherwise, {@link Consumer#accept(Object)}
+     *                     should  be called with {@link Boolean#FALSE}.
+     */
+    void onDisconnect(@NonNull Consumer<Boolean> wasCompleted);
+
+    /**
+     * update the client on the new {@link CallAudioState}
+     *
+     * @param callAudioState that is currently being used
+     */
+    void onCallAudioStateChanged(@NonNull CallAudioState callAudioState);
+}
diff --git a/telecomm/java/android/telecom/CallException.aidl b/telecomm/java/android/telecom/CallException.aidl
new file mode 100644
index 0000000..a16af12
--- /dev/null
+++ b/telecomm/java/android/telecom/CallException.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+/**
+ * {@hide}
+ */
+parcelable CallException;
\ No newline at end of file
diff --git a/telecomm/java/android/telecom/CallException.java b/telecomm/java/android/telecom/CallException.java
new file mode 100644
index 0000000..0b0de6b
--- /dev/null
+++ b/telecomm/java/android/telecom/CallException.java
@@ -0,0 +1,160 @@
+/*
+ * 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.telecom;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class defines exceptions that can be thrown when using Telecom APIs with
+ * {@link android.os.OutcomeReceiver}s.  Most of these exceptions are thrown when changing a call
+ * state with {@link CallControl}s or {@link CallEventCallback}s.
+ */
+public final class CallException extends RuntimeException implements Parcelable {
+    /** @hide **/
+    public static final String TRANSACTION_EXCEPTION_KEY = "TelecomTransactionalExceptionKey";
+
+    /**
+     * The operation has failed due to an unknown or unspecified error.
+     */
+    public static final int CODE_ERROR_UNKNOWN = 1;
+
+    /**
+     * The operation has failed due to Telecom failing to hold the current active call for the
+     * call attempting to become the new active call.  The client should end the current active call
+     * and re-try the failed operation.
+     */
+    public static final int CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL = 2;
+
+    /**
+     * The operation has failed because Telecom has already removed the call from the server side
+     * and destroyed all the objects associated with it.  The client should re-add the call.
+     */
+    public static final int CODE_CALL_IS_NOT_BEING_TRACKED = 3;
+
+    /**
+     * The operation has failed because Telecom cannot set the requested call as the current active
+     * call.  The client should end the current active call and re-try the operation.
+     */
+    public static final int CODE_CALL_CANNOT_BE_SET_TO_ACTIVE = 4;
+
+    /**
+     * The operation has failed because there is either no PhoneAccount registered with Telecom
+     * for the given operation, or the limit of calls has been reached. The client should end the
+     * current active call and re-try the failed operation.
+     */
+    public static final int CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME = 5;
+
+    /**
+     * The operation has failed because the operation failed to complete before the timeout
+     */
+    public static final int CODE_OPERATION_TIMED_OUT = 6;
+
+    private int mCode = CODE_ERROR_UNKNOWN;
+    private final String mMessage;
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mMessage);
+        dest.writeInt(mCode);
+    }
+
+    /**
+     * Responsible for creating CallAttribute objects for deserialized Parcels.
+     */
+    public static final @android.annotation.NonNull
+            Parcelable.Creator<CallException> CREATOR = new Parcelable.Creator<>() {
+                    @Override
+                    public CallException createFromParcel(Parcel source) {
+                        return new CallException(source.readString8(), source.readInt());
+                    }
+
+                    @Override
+                    public CallException[] newArray(int size) {
+                        return new CallException[size];
+                    }
+            };
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "CODE_ERROR_", value = {
+            CODE_ERROR_UNKNOWN,
+            CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL,
+            CODE_CALL_IS_NOT_BEING_TRACKED,
+            CODE_CALL_CANNOT_BE_SET_TO_ACTIVE,
+            CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME,
+            CODE_OPERATION_TIMED_OUT
+    })
+    public @interface CallErrorCode {
+    }
+
+    /**
+     * Constructor for a new CallException when only message can be specified.
+     * {@code CODE_ERROR_UNKNOWN} will be default code returned when calling {@code getCode}
+     *
+     * @param message related to why the exception was created
+     */
+    public CallException(@Nullable String message) {
+        super(getMessage(message, CODE_ERROR_UNKNOWN));
+        mMessage = message;
+    }
+
+    /**
+     * Constructor for a new CallException that has a defined error code in this class
+     *
+     * @param message related to why the exception was created
+     * @param code defined above that caused this exception to be created
+     */
+    public CallException(@Nullable String message, @CallErrorCode int code) {
+        super(getMessage(message, code));
+        mCode = code;
+        mMessage = message;
+    }
+
+    /**
+     * @return one of the error codes defined in this class that was passed into the constructor
+     */
+    public @CallErrorCode int getCode() {
+        return mCode;
+    }
+
+    private static String getMessage(String message, int code) {
+        StringBuilder builder;
+        if (!TextUtils.isEmpty(message)) {
+            builder = new StringBuilder(message);
+            builder.append(" (code: ");
+            builder.append(code);
+            builder.append(")");
+            return builder.toString();
+        } else {
+            return "code: " + code;
+        }
+    }
+}
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index ec18c6a..047ab3a 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -418,7 +418,26 @@
      */
     public static final int CAPABILITY_VOICE_CALLING_AVAILABLE = 0x20000;
 
-    /* NEXT CAPABILITY: 0x40000 */
+
+    /**
+     * Flag indicating that this {@link PhoneAccount} supports the use TelecomManager APIs that
+     * utilize {@link android.os.OutcomeReceiver}s or {@link java.util.function.Consumer}s.
+     * Be aware, if this capability is set, {@link #CAPABILITY_SELF_MANAGED} will be amended by
+     * Telecom when this {@link PhoneAccount} is registered via
+     * {@link TelecomManager#registerPhoneAccount(PhoneAccount)}.
+     *
+     * <p>
+     * {@link android.os.OutcomeReceiver}s and {@link java.util.function.Consumer}s represent
+     * transactional operations because the operation can succeed or fail.  An app wishing to use
+     * transactional operations should define behavior for a successful and failed TelecomManager
+     * API call.
+     *
+     * @see #CAPABILITY_SELF_MANAGED
+     * @see #getCapabilities
+     */
+    public static final int CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS = 0x40000;
+
+    /* NEXT CAPABILITY: [0x80000, 0x100000, 0x200000] */
 
     /**
      * URI scheme for telephone number URIs.
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index af37ed5..7c86a75a 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -18,6 +18,7 @@
 import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM;
 
 import android.Manifest;
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -38,6 +39,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.OutcomeReceiver;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -49,6 +51,8 @@
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.telecom.ClientTransactionalServiceRepository;
+import com.android.internal.telecom.ClientTransactionalServiceWrapper;
 import com.android.internal.telecom.ITelecomService;
 
 import java.lang.annotation.Retention;
@@ -1056,6 +1060,14 @@
 
     private final ITelecomService mTelecomServiceOverride;
 
+    /** @hide **/
+    private final ClientTransactionalServiceRepository mTransactionalServiceRepository =
+            new ClientTransactionalServiceRepository();
+    /** @hide **/
+    public static final int TELECOM_TRANSACTION_SUCCESS = 0;
+    /** @hide **/
+    public static final String TRANSACTION_CALL_ID_KEY = "TelecomCallId";
+
     /**
      * @hide
      */
@@ -2640,6 +2652,92 @@
     }
 
     /**
+     * Adds a new call with the specified {@link CallAttributes} to the telecom service. This method
+     * can be used to add both incoming and outgoing calls.
+     *
+     * <p>
+     * The difference between this API call and {@link TelecomManager#placeCall(Uri, Bundle)} or
+     * {@link TelecomManager#addNewIncomingCall(PhoneAccountHandle, Bundle)} is that this API
+     * will asynchronously provide an update on whether the new call was added successfully via
+     * an {@link OutcomeReceiver}.  Additionally, callbacks will run on the executor thread that was
+     * passed in.
+     *
+     * <p>
+     * Note: Only packages that register with
+     * {@link PhoneAccount#CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS}
+     * can utilize this API. {@link PhoneAccount}s that set the capabilities
+     * {@link PhoneAccount#CAPABILITY_SIM_SUBSCRIPTION},
+     * {@link PhoneAccount#CAPABILITY_CALL_PROVIDER},
+     * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}
+     * are not supported and will cause an exception to be thrown.
+     *
+     * <p>
+     * Usage example:
+     * <pre>
+     *
+     *  // An app should first define their own construct of a Call that overrides all the
+     *  // {@link CallEventCallback}s
+     *  private class MyVoipCall implements CallEventCallback {
+     *    // override all the {@link CallEventCallback}s
+     *  }
+     *
+     * PhoneAccountHandle handle = new PhoneAccountHandle(
+     *                          new ComponentName("com.example.voip.app",
+     *                                            "com.example.voip.app.NewCallActivity"), "123");
+     *
+     * CallAttributes callAttributes = new CallAttributes.Builder(handle,
+     *                                             CallAttributes.DIRECTION_OUTGOING,
+     *                                            "John Smith", Uri.fromParts("tel", "123", null))
+     *                                            .build();
+     *
+     * telecomManager.addCall(callAttributes, Runnable::run, new OutcomeReceiver() {
+     *                              public void onResult(CallControl callControl) {
+     *                                 // The call has been added successfully
+     *                              }
+     *                           }, new MyVoipCall());
+     * </pre>
+     *
+     * @param callAttributes    attributes of the new call (incoming or outgoing, address, etc. )
+     * @param executor          thread to run background CallEventCallback updates on
+     * @param pendingControl    OutcomeReceiver that receives the result of addCall transaction
+     * @param callEventCallback object that overrides CallEventCallback
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_OWN_CALLS)
+    @SuppressLint("SamShouldBeLast")
+    public void addCall(@NonNull CallAttributes callAttributes,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OutcomeReceiver<CallControl, CallException> pendingControl,
+            @NonNull CallEventCallback callEventCallback) {
+        Objects.requireNonNull(callAttributes);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(pendingControl);
+        Objects.requireNonNull(callEventCallback);
+
+        ITelecomService service = getTelecomService();
+        if (service != null) {
+            try {
+                // create or add the new call to a service wrapper w/ the same phoneAccountHandle
+                ClientTransactionalServiceWrapper transactionalServiceWrapper =
+                        mTransactionalServiceRepository.addNewCallForTransactionalServiceWrapper(
+                                callAttributes.getPhoneAccountHandle());
+
+                // couple all the args passed by the client
+                String newCallId = transactionalServiceWrapper.trackCall(callAttributes, executor,
+                        pendingControl, callEventCallback);
+
+                // send args to server to process new call
+                service.addCall(callAttributes, transactionalServiceWrapper.getCallEventCallback(),
+                        newCallId, mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException addCall: " + e);
+                e.rethrowFromSystemServer();
+            }
+        } else {
+            throw new IllegalStateException("Telecom service is not present");
+        }
+    }
+
+    /**
      * Handles {@link Intent#ACTION_CALL} intents trampolined from UserCallActivity.
      * @param intent The {@link Intent#ACTION_CALL} intent to handle.
      * @param callingPackageProxy The original package that called this before it was trampolined.
diff --git a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceRepository.java b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceRepository.java
new file mode 100644
index 0000000..2eebbdb
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceRepository.java
@@ -0,0 +1,90 @@
+/*
+ * 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.internal.telecom;
+
+import android.telecom.PhoneAccountHandle;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @hide
+ */
+public class ClientTransactionalServiceRepository {
+
+    private static final Map<PhoneAccountHandle, ClientTransactionalServiceWrapper> LOOKUP_TABLE =
+            new ConcurrentHashMap<>();
+
+    /**
+     * creates a new {@link ClientTransactionalServiceWrapper} if this is the first call being
+     * tracked for a particular package Or adds a new call for an existing
+     * {@link ClientTransactionalServiceWrapper}
+     *
+     * @param phoneAccountHandle for a particular package requesting to create a call
+     * @return the {@link ClientTransactionalServiceWrapper} that is tied tot the PhoneAccountHandle
+     */
+    public ClientTransactionalServiceWrapper addNewCallForTransactionalServiceWrapper(
+            PhoneAccountHandle phoneAccountHandle) {
+
+        ClientTransactionalServiceWrapper service = null;
+        if (!hasExistingServiceWrapper(phoneAccountHandle)) {
+            service = new ClientTransactionalServiceWrapper(phoneAccountHandle, this);
+        } else {
+            service = getTransactionalServiceWrapper(phoneAccountHandle);
+        }
+
+        LOOKUP_TABLE.put(phoneAccountHandle, service);
+
+        return service;
+    }
+
+    private ClientTransactionalServiceWrapper getTransactionalServiceWrapper(
+            PhoneAccountHandle pah) {
+        return LOOKUP_TABLE.get(pah);
+    }
+
+    private boolean hasExistingServiceWrapper(PhoneAccountHandle pah) {
+        return LOOKUP_TABLE.containsKey(pah);
+    }
+
+    /**
+     * @param pah that is tied to a particular package with potential tracked calls
+     * @return if the {@link ClientTransactionalServiceWrapper} was successfully removed
+     */
+    public boolean removeServiceWrapper(PhoneAccountHandle pah) {
+        if (!hasExistingServiceWrapper(pah)) {
+            return false;
+        }
+        LOOKUP_TABLE.remove(pah);
+        return true;
+    }
+
+    /**
+     * @param pah    that is tied to a particular package with potential tracked calls
+     * @param callId of the TransactionalCall that you want to remove
+     * @return if the call was successfully removed from the service wrapper
+     */
+    public boolean removeCallFromServiceWrapper(PhoneAccountHandle pah, String callId) {
+        if (!hasExistingServiceWrapper(pah)) {
+            return false;
+        }
+        ClientTransactionalServiceWrapper service = LOOKUP_TABLE.get(pah);
+        service.untrackCall(callId);
+        return true;
+    }
+
+}
diff --git a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
new file mode 100644
index 0000000..682dba1
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
@@ -0,0 +1,263 @@
+/*
+ * 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.internal.telecom;
+
+import static android.telecom.TelecomManager.TELECOM_TRANSACTION_SUCCESS;
+
+import android.os.Binder;
+import android.os.OutcomeReceiver;
+import android.os.ResultReceiver;
+import android.telecom.CallAttributes;
+import android.telecom.CallAudioState;
+import android.telecom.CallControl;
+import android.telecom.CallEventCallback;
+import android.telecom.CallException;
+import android.telecom.PhoneAccountHandle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * wraps {@link CallEventCallback} and {@link CallControl} on a
+ * per-{@link  android.telecom.PhoneAccountHandle} basis to track ongoing calls.
+ *
+ * @hide
+ */
+public class ClientTransactionalServiceWrapper {
+
+    private static final String TAG = ClientTransactionalServiceWrapper.class.getSimpleName();
+    private final PhoneAccountHandle mPhoneAccountHandle;
+    private final ClientTransactionalServiceRepository mRepository;
+    private final ConcurrentHashMap<String, TransactionalCall> mCallIdToTransactionalCall =
+            new ConcurrentHashMap<>();
+    private static final String EXECUTOR_FAIL_MSG =
+            "Telecom hit an exception while handling a CallEventCallback on an executor: ";
+
+    public ClientTransactionalServiceWrapper(PhoneAccountHandle handle,
+            ClientTransactionalServiceRepository repo) {
+        mPhoneAccountHandle = handle;
+        mRepository = repo;
+    }
+
+    /**
+     * remove the given call from the class HashMap
+     *
+     * @param callId that is tied to TransactionalCall object
+     */
+    public void untrackCall(String callId) {
+        Log.i(TAG, TextUtils.formatSimple("removeCall: with id=[%s]", callId));
+        if (mCallIdToTransactionalCall.containsKey(callId)) {
+            // remove the call from the hashmap
+            TransactionalCall call = mCallIdToTransactionalCall.remove(callId);
+            // null out interface to avoid memory leaks
+            CallControl control = call.getCallControl();
+            if (control != null) {
+                call.setCallControl(null);
+            }
+        }
+        // possibly cleanup service wrapper if there are no more calls
+        if (mCallIdToTransactionalCall.size() == 0) {
+            mRepository.removeServiceWrapper(mPhoneAccountHandle);
+        }
+    }
+
+    /**
+     * start tracking a newly created call for a particular package
+     *
+     * @param callAttributes of the new call
+     * @param executor       to run callbacks on
+     * @param pendingControl that allows telecom to call into the client
+     * @param callback       that overrides the CallEventCallback
+     * @return the callId of the newly created call
+     */
+    public String trackCall(CallAttributes callAttributes, Executor executor,
+            OutcomeReceiver<CallControl, CallException> pendingControl,
+            CallEventCallback callback) {
+        // generate a new id for this new call
+        String newCallId = UUID.randomUUID().toString();
+
+        // couple the objects passed from the client side
+        mCallIdToTransactionalCall.put(newCallId, new TransactionalCall(newCallId, callAttributes,
+                executor, pendingControl, callback));
+
+        return newCallId;
+    }
+
+    public ICallEventCallback getCallEventCallback() {
+        return mCallEventCallback;
+    }
+
+    /**
+     * Consumers that is to be completed by the client and the result relayed back to telecom server
+     * side via a {@link ResultReceiver}. see com.android.server.telecom.TransactionalServiceWrapper
+     * for how the response is handled.
+     */
+    private class ReceiverWrapper implements Consumer<Boolean> {
+        private final ResultReceiver mRepeaterReceiver;
+
+        ReceiverWrapper(ResultReceiver resultReceiver) {
+            mRepeaterReceiver = resultReceiver;
+        }
+
+        @Override
+        public void accept(Boolean clientCompletedCallbackSuccessfully) {
+            if (clientCompletedCallbackSuccessfully) {
+                mRepeaterReceiver.send(TELECOM_TRANSACTION_SUCCESS, null);
+            } else {
+                mRepeaterReceiver.send(CallException.CODE_ERROR_UNKNOWN, null);
+            }
+        }
+
+        @Override
+        public Consumer<Boolean> andThen(Consumer<? super Boolean> after) {
+            return Consumer.super.andThen(after);
+        }
+    }
+
+    private final ICallEventCallback mCallEventCallback = new ICallEventCallback.Stub() {
+
+        private static final String ON_SET_ACTIVE = "onSetActive";
+        private static final String ON_SET_INACTIVE = "onSetInactive";
+        private static final String ON_ANSWER = "onAnswer";
+        private static final String ON_REJECT = "onReject";
+        private static final String ON_DISCONNECT = "onDisconnect";
+
+        private void handleCallEventCallback(String action, String callId, int code,
+                ResultReceiver ackResultReceiver) {
+            Log.i(TAG, TextUtils.formatSimple("hCEC: id=[%s], action=[%s]", callId, action));
+            // lookup the callEventCallback associated with the particular call
+            TransactionalCall call = mCallIdToTransactionalCall.get(callId);
+
+            if (call != null) {
+                // Get the CallEventCallback interface
+                CallEventCallback callback = call.getCallEventCallback();
+                // Get Receiver to wait on client ack
+                ReceiverWrapper outcomeReceiverWrapper = new ReceiverWrapper(ackResultReceiver);
+
+                // wait for the client to complete the CallEventCallback
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    call.getExecutor().execute(() -> {
+                        switch (action) {
+                            case ON_SET_ACTIVE:
+                                callback.onSetActive(outcomeReceiverWrapper);
+                                break;
+                            case ON_SET_INACTIVE:
+                                callback.onSetInactive(outcomeReceiverWrapper);
+                                break;
+                            case ON_REJECT:
+                                callback.onReject(outcomeReceiverWrapper);
+                                untrackCall(callId);
+                                break;
+                            case ON_DISCONNECT:
+                                callback.onDisconnect(outcomeReceiverWrapper);
+                                untrackCall(callId);
+                                break;
+                            case ON_ANSWER:
+                                callback.onAnswer(code, outcomeReceiverWrapper);
+                                break;
+                        }
+                    });
+                } catch (Exception e) {
+                    Log.e(TAG, EXECUTOR_FAIL_MSG + e);
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            }
+        }
+
+        @Override
+        public void onAddCallControl(String callId, int resultCode, ICallControl callControl,
+                CallException transactionalException) {
+            Log.i(TAG, TextUtils.formatSimple("oACC: id=[%s], code=[%d]", callId, resultCode));
+            TransactionalCall call = mCallIdToTransactionalCall.get(callId);
+
+            if (call != null) {
+                OutcomeReceiver<CallControl, CallException> pendingControl =
+                        call.getPendingControl();
+
+                if (resultCode == TELECOM_TRANSACTION_SUCCESS) {
+
+                    // create the interface object that the client will interact with
+                    CallControl control = new CallControl(callId, callControl, mRepository,
+                            mPhoneAccountHandle);
+                    // give the client the object via the OR that was passed into addCall
+                    pendingControl.onResult(control);
+
+                    // store for later reference
+                    call.setCallControl(control);
+                } else {
+                    pendingControl.onError(transactionalException);
+                    mCallIdToTransactionalCall.remove(callId);
+                }
+
+            } else {
+                untrackCall(callId);
+                Log.e(TAG, "oACC: TransactionalCall object not found for call w/ id=" + callId);
+            }
+        }
+
+        @Override
+        public void onSetActive(String callId, ResultReceiver resultReceiver) {
+            handleCallEventCallback(ON_SET_ACTIVE, callId, 0, resultReceiver);
+        }
+
+
+        @Override
+        public void onSetInactive(String callId, ResultReceiver resultReceiver) {
+            handleCallEventCallback(ON_SET_INACTIVE, callId, 0, resultReceiver);
+        }
+
+        @Override
+        public void onAnswer(String callId, int videoState, ResultReceiver resultReceiver) {
+            handleCallEventCallback(ON_ANSWER, callId, videoState, resultReceiver);
+        }
+
+        @Override
+        public void onReject(String callId, ResultReceiver resultReceiver) {
+            handleCallEventCallback(ON_REJECT, callId, 0, resultReceiver);
+        }
+
+        @Override
+        public void onDisconnect(String callId, ResultReceiver resultReceiver) {
+            handleCallEventCallback(ON_DISCONNECT, callId, 0, resultReceiver);
+        }
+
+        @Override
+        public void onCallAudioStateChanged(String callId, CallAudioState callAudioState) {
+            Log.i(TAG, TextUtils.formatSimple("onCallAudioStateChanged: callId=[%s]", callId));
+            // lookup the callEventCallback associated with the particular call
+            TransactionalCall call = mCallIdToTransactionalCall.get(callId);
+            if (call != null) {
+                CallEventCallback callback = call.getCallEventCallback();
+                Executor executor = call.getExecutor();
+                executor.execute(() -> {
+                    callback.onCallAudioStateChanged(callAudioState);
+                });
+            }
+        }
+
+        @Override
+        public void removeCallFromTransactionalServiceWrapper(String callId) {
+            untrackCall(callId);
+        }
+    };
+}
diff --git a/telecomm/java/com/android/internal/telecom/ICallControl.aidl b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
new file mode 100644
index 0000000..bf68c5e
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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.internal.telecom;
+
+import android.telecom.CallControl;
+import android.telecom.DisconnectCause;
+import android.os.ResultReceiver;
+
+/**
+ * {@hide}
+ */
+oneway interface ICallControl {
+    void setActive(String callId, in ResultReceiver callback);
+    void setInactive(String callId, in ResultReceiver callback);
+    void disconnect(String callId, in DisconnectCause disconnectCause, in ResultReceiver callback);
+    void rejectCall(String callId, in ResultReceiver callback);
+}
\ No newline at end of file
diff --git a/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl b/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl
new file mode 100644
index 0000000..7f5825a
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.internal.telecom;
+
+import android.telecom.CallControl;
+import com.android.internal.telecom.ICallControl;
+import android.os.ResultReceiver;
+import android.telecom.CallAudioState;
+import android.telecom.CallException;
+
+/**
+ * {@hide}
+ */
+oneway interface ICallEventCallback {
+    // publicly exposed. Client should override
+    void onAddCallControl(String callId, int resultCode, in ICallControl callControl,
+     in CallException exception);
+    void onSetActive(String callId, in ResultReceiver callback);
+    void onSetInactive(String callId, in ResultReceiver callback);
+    void onAnswer(String callId, int videoState, in ResultReceiver callback);
+    void onReject(String callId, in ResultReceiver callback);
+    void onDisconnect(String callId, in ResultReceiver callback);
+    void onCallAudioStateChanged(String callId, in CallAudioState callAudioState);
+    // hidden methods that help with cleanup
+    void removeCallFromTransactionalServiceWrapper(String callId);
+}
\ No newline at end of file
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index f1a6dd1..fdcb974 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -25,6 +25,8 @@
 import android.os.UserHandle;
 import android.telecom.PhoneAccount;
 import android.content.pm.ParceledListSlice;
+import android.telecom.CallAttributes;
+import com.android.internal.telecom.ICallEventCallback;
 
 /**
  * Interface used to interact with Telecom. Mostly this is used by TelephonyManager for passing
@@ -391,4 +393,10 @@
      */
     boolean isInSelfManagedCall(String packageName, in UserHandle userHandle,
         String callingPackage);
+
+    /**
+     * @see TelecomServiceImpl#addCall
+     */
+    void addCall(in CallAttributes callAttributes, in ICallEventCallback callback, String callId,
+        String callingPackage);
 }
diff --git a/telecomm/java/com/android/internal/telecom/TransactionalCall.java b/telecomm/java/com/android/internal/telecom/TransactionalCall.java
new file mode 100644
index 0000000..d9c8210
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/TransactionalCall.java
@@ -0,0 +1,76 @@
+/*
+ * 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.internal.telecom;
+
+import android.os.OutcomeReceiver;
+import android.telecom.CallAttributes;
+import android.telecom.CallControl;
+import android.telecom.CallEventCallback;
+import android.telecom.CallException;
+
+import java.util.concurrent.Executor;
+
+/**
+ * @hide
+ */
+public class TransactionalCall {
+
+    private final String mCallId;
+    private final CallAttributes mCallAttributes;
+    private final Executor mExecutor;
+    private final OutcomeReceiver<CallControl, CallException> mPendingControl;
+    private final CallEventCallback mCallEventCallback;
+    private CallControl mCallControl;
+
+    public TransactionalCall(String callId, CallAttributes callAttributes,
+            Executor executor, OutcomeReceiver<CallControl, CallException>  pendingControl,
+            CallEventCallback callEventCallback) {
+        mCallId = callId;
+        mCallAttributes = callAttributes;
+        mExecutor = executor;
+        mPendingControl = pendingControl;
+        mCallEventCallback = callEventCallback;
+    }
+
+    public void setCallControl(CallControl callControl) {
+        mCallControl = callControl;
+    }
+
+    public CallControl getCallControl() {
+        return mCallControl;
+    }
+
+    public String getCallId() {
+        return mCallId;
+    }
+
+    public CallAttributes getCallAttributes() {
+        return mCallAttributes;
+    }
+
+    public Executor getExecutor() {
+        return mExecutor;
+    }
+
+    public OutcomeReceiver<CallControl, CallException> getPendingControl() {
+        return mPendingControl;
+    }
+
+    public CallEventCallback getCallEventCallback() {
+        return mCallEventCallback;
+    }
+}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index a142177..6f462b1 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4468,6 +4468,18 @@
             "data_switch_validation_timeout_long";
 
     /**
+     * The minimum timeout of UDP port 4500 NAT / firewall entries on the Internet PDN of this
+     * carrier network. This will be used by Android platform VPNs to tune IPsec NAT keepalive
+     * interval. If this value is too low to provide uninterrupted inbound connectivity, then
+     * Android system VPNs may indicate to applications that the VPN cannot support long-lived
+     * TCP connections.
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final String KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT =
+            "min_udp_port_4500_nat_timeout_sec_int";
+
+    /**
      * Specifies whether the system should prefix the EAP method to the anonymous identity.
      * The following prefix will be added if this key is set to TRUE:
      *   EAP-AKA: "0"
@@ -10098,6 +10110,7 @@
         sDefaults.putStringArray(KEY_MMI_TWO_DIGIT_NUMBER_PATTERN_STRING_ARRAY, new String[0]);
         sDefaults.putInt(KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT,
                 CellSignalStrengthLte.USE_RSRP);
+        sDefaults.putInt(KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT, 300);
         // Default wifi configurations.
         sDefaults.putAll(Wifi.getDefaults());
         sDefaults.putBoolean(ENABLE_EAP_METHOD_PREFIX_BOOL, false);