Merge "Make haptics during unfold animation configurable" into 24D1-dev
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 9142aea..118f780 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1329,6 +1329,17 @@
     public static final long OVERRIDE_MIN_ASPECT_RATIO = 174042980L; // buganizer id
 
     /**
+     * This change id restricts treatments that force a given min aspect ratio to
+     * only when an app is connected to the camera
+     *
+     * @hide
+     */
+    @ChangeId
+    @Overridable
+    @Disabled
+    public static final long OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA = 325586858L; // buganizer id
+
+    /**
      * This change id restricts treatments that force a given min aspect ratio to activities
      * whose orientation is fixed to portrait.
      *
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index 254f4f7..453ff06 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -1,6 +1,13 @@
 package: "com.android.window.flags"
 
 flag {
+  name: "disable_thin_letterboxing_reachability"
+  namespace: "large_screen_experiences_app_compat"
+  description: "Whether reachability is disabled in case of thin letterboxing"
+  bug: "334077350"
+}
+
+flag {
   name: "allows_screen_size_decoupled_from_status_bar_and_cutout"
   namespace: "large_screen_experiences_app_compat"
   description: "When necessary, configuration decoupled from status bar and display cutout"
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index e6c652c..bf93b8c 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -109,29 +109,13 @@
         }
     }
 
-    // For curve 25519, KeyMint uses the KM_ALGORITHM_EC constant, but in the Java layer we need
-    // to distinguish between Curve 25519 and other EC algorithms, so we use a different constant
-    // with a value that is outside the range of the enum used for KeyMint algorithms.
-    private static final int ALGORITHM_XDH = KeymasterDefs.KM_ALGORITHM_EC + 1200;
-    private static final int ALGORITHM_ED25519 = ALGORITHM_XDH + 1;
-
     /**
-     * XDH represents Curve 25519 agreement key provider.
+     * XDH represents Curve 25519 providers.
      */
     public static class XDH extends AndroidKeyStoreKeyPairGeneratorSpi {
         // XDH is treated as EC.
         public XDH() {
-            super(ALGORITHM_XDH);
-        }
-    }
-
-    /**
-     * ED25519 represents Curve 25519 signing key provider.
-     */
-    public static class ED25519 extends AndroidKeyStoreKeyPairGeneratorSpi {
-        // ED25519 is treated as EC.
-        public ED25519() {
-            super(ALGORITHM_ED25519);
+            super(KeymasterDefs.KM_ALGORITHM_EC);
         }
     }
 
@@ -257,9 +241,7 @@
 
             KeyGenParameterSpec spec;
             boolean encryptionAtRestRequired = false;
-            int keymasterAlgorithm = (mOriginalKeymasterAlgorithm == ALGORITHM_XDH
-                    || mOriginalKeymasterAlgorithm == ALGORITHM_ED25519)
-                    ? KeymasterDefs.KM_ALGORITHM_EC : mOriginalKeymasterAlgorithm;
+            int keymasterAlgorithm = mOriginalKeymasterAlgorithm;
             if (params instanceof KeyGenParameterSpec) {
                 spec = (KeyGenParameterSpec) params;
             } else if (params instanceof KeyPairGeneratorSpec) {
@@ -628,15 +610,6 @@
                 if (algSpecificSpec instanceof ECGenParameterSpec) {
                     ECGenParameterSpec ecSpec = (ECGenParameterSpec) algSpecificSpec;
                     mEcCurveName = ecSpec.getName();
-                    if (mOriginalKeymasterAlgorithm == ALGORITHM_XDH
-                            && !mEcCurveName.equalsIgnoreCase("x25519")) {
-                        throw new InvalidAlgorithmParameterException("XDH algorithm only supports"
-                                + " x25519 curve.");
-                    } else if (mOriginalKeymasterAlgorithm == ALGORITHM_ED25519
-                            && !mEcCurveName.equalsIgnoreCase("ed25519")) {
-                        throw new InvalidAlgorithmParameterException("Ed25519 algorithm only"
-                                + " supports ed25519 curve.");
-                    }
                     final Integer ecSpecKeySizeBits = SUPPORTED_EC_CURVE_NAME_TO_SIZE.get(
                             mEcCurveName.toLowerCase(Locale.US));
                     if (ecSpecKeySizeBits == null) {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
index 99100de..228d64a 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
@@ -85,14 +85,11 @@
         put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$EC");
         put("KeyPairGenerator.RSA", PACKAGE_NAME +  ".AndroidKeyStoreKeyPairGeneratorSpi$RSA");
         put("KeyPairGenerator.XDH", PACKAGE_NAME +  ".AndroidKeyStoreKeyPairGeneratorSpi$XDH");
-        put("KeyPairGenerator.ED25519", PACKAGE_NAME
-                +  ".AndroidKeyStoreKeyPairGeneratorSpi$ED25519");
 
         // java.security.KeyFactory
         putKeyFactoryImpl("EC");
         putKeyFactoryImpl("RSA");
         putKeyFactoryImpl("XDH");
-        putKeyFactoryImpl("ED25519");
 
         // javax.crypto.KeyGenerator
         put("KeyGenerator.AES", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$AES");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 32442f7..5584f23 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -266,9 +266,9 @@
     }
 
     /** Whether a particular package is same as current pip package. */
-    public boolean isInPipPackage(String packageName) {
+    public boolean isPackageActiveInPip(String packageName) {
         final TaskInfo inPipTask = mPipOrganizer.getTaskInfo();
-        return packageName != null && inPipTask != null
+        return packageName != null && inPipTask != null && mPipOrganizer.isInPip()
                 && packageName.equals(SplitScreenUtils.getPackageName(inPipTask.baseIntent));
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 9dd4c19..68c59ee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -199,7 +199,7 @@
     private final DisplayImeController mDisplayImeController;
     private final DisplayInsetsController mDisplayInsetsController;
     private final TransactionPool mTransactionPool;
-    private final SplitScreenTransitions mSplitTransitions;
+    private SplitScreenTransitions mSplitTransitions;
     private final SplitscreenEventLogger mLogger;
     private final ShellExecutor mMainExecutor;
     // Cache live tile tasks while entering recents, evict them from stages in finish transaction
@@ -397,6 +397,11 @@
         return mSplitTransitions;
     }
 
+    @VisibleForTesting
+    void setSplitTransitions(SplitScreenTransitions splitScreenTransitions) {
+        mSplitTransitions = splitScreenTransitions;
+    }
+
     public boolean isSplitScreenVisible() {
         return mSideStageListener.mVisible && mMainStageListener.mVisible;
     }
@@ -581,7 +586,7 @@
         options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
         wct.startTask(taskId, options);
         // If this should be mixed, send the task to avoid split handle transition directly.
-        if (mMixedHandler != null && mMixedHandler.shouldSplitEnterMixed(taskId, mTaskOrganizer)) {
+        if (mMixedHandler != null && mMixedHandler.isTaskInPip(taskId, mTaskOrganizer)) {
             mTaskOrganizer.applyTransaction(wct);
             return;
         }
@@ -620,7 +625,7 @@
         wct.sendPendingIntent(intent, fillInIntent, options);
 
         // If this should be mixed, just send the intent to avoid split handle transition directly.
-        if (mMixedHandler != null && mMixedHandler.shouldSplitEnterMixed(intent)) {
+        if (mMixedHandler != null && mMixedHandler.isIntentInPip(intent)) {
             mTaskOrganizer.applyTransaction(wct);
             return;
         }
@@ -709,16 +714,7 @@
                 taskId1, taskId2, splitPosition, snapPosition);
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         if (taskId2 == INVALID_TASK_ID) {
-            if (mMainStage.containsTask(taskId1) || mSideStage.containsTask(taskId1)) {
-                prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
-            }
-            if (mRecentTasks.isPresent()) {
-                mRecentTasks.get().removeSplitPair(taskId1);
-            }
-            options1 = options1 != null ? options1 : new Bundle();
-            addActivityOptions(options1, null);
-            wct.startTask(taskId1, options1);
-            mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+            startSingleTask(taskId1, options1, wct, remoteTransition);
             return;
         }
 
@@ -739,11 +735,15 @@
                 "startIntentAndTask: intent=%s task1=%d position=%d snapPosition=%d",
                 pendingIntent.getIntent(), taskId, splitPosition, snapPosition);
         final WindowContainerTransaction wct = new WindowContainerTransaction();
-        if (taskId == INVALID_TASK_ID) {
-            options1 = options1 != null ? options1 : new Bundle();
-            addActivityOptions(options1, null);
-            wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
-            mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+        boolean firstIntentPipped = mMixedHandler.isIntentInPip(pendingIntent);
+        boolean secondTaskPipped = mMixedHandler.isTaskInPip(taskId, mTaskOrganizer);
+        if (taskId == INVALID_TASK_ID || secondTaskPipped) {
+            startSingleIntent(pendingIntent, fillInIntent, options1, wct, remoteTransition);
+            return;
+        }
+
+        if (firstIntentPipped) {
+            startSingleTask(taskId, options2, wct, remoteTransition);
             return;
         }
 
@@ -755,6 +755,24 @@
         startWithTask(wct, taskId, options2, snapPosition, remoteTransition, instanceId);
     }
 
+    /**
+     * @param taskId Starts this task in fullscreen, removing it from existing pairs if it was part
+     *               of one.
+     */
+    private void startSingleTask(int taskId, Bundle options, WindowContainerTransaction wct,
+            RemoteTransition remoteTransition) {
+        if (mMainStage.containsTask(taskId) || mSideStage.containsTask(taskId)) {
+            prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
+        }
+        if (mRecentTasks.isPresent()) {
+            mRecentTasks.get().removeSplitPair(taskId);
+        }
+        options = options != null ? options : new Bundle();
+        addActivityOptions(options, null);
+        wct.startTask(taskId, options);
+        mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+    }
+
     /** Starts a shortcut and a task to a split pair in one transition. */
     void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
             int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
@@ -842,6 +860,21 @@
             return;
         }
 
+        boolean handledForPipSplitLaunch = handlePippedSplitIntentsLaunch(
+                pendingIntent1,
+                pendingIntent2,
+                options1,
+                options2,
+                shortcutInfo1,
+                shortcutInfo2,
+                wct,
+                fillInIntent1,
+                fillInIntent2,
+                remoteTransition);
+        if (handledForPipSplitLaunch) {
+            return;
+        }
+
         if (!mMainStage.isActive()) {
             // Build a request WCT that will launch both apps such that task 0 is on the main stage
             // while task 1 is on the side stage.
@@ -876,6 +909,46 @@
         setEnterInstanceId(instanceId);
     }
 
+    /**
+     * Checks if either of the apps in the desired split launch is currently in Pip. If so, it will
+     * launch the non-pipped app as a fullscreen app, otherwise no-op.
+     */
+    private boolean handlePippedSplitIntentsLaunch(PendingIntent pendingIntent1,
+            PendingIntent pendingIntent2, Bundle options1, Bundle options2,
+            ShortcutInfo shortcutInfo1, ShortcutInfo shortcutInfo2, WindowContainerTransaction wct,
+            Intent fillInIntent1, Intent fillInIntent2, RemoteTransition remoteTransition) {
+        // If one of the split apps to start is in Pip, only launch the non-pip app in fullscreen
+        boolean firstIntentPipped = mMixedHandler.isIntentInPip(pendingIntent1);
+        boolean secondIntentPipped = mMixedHandler.isIntentInPip(pendingIntent2);
+        if (firstIntentPipped || secondIntentPipped) {
+            Bundle options = secondIntentPipped ? options1 : options2;
+            options = options == null ? new Bundle() : options;
+            addActivityOptions(options, null);
+            if (shortcutInfo1 != null || shortcutInfo2 != null) {
+                ShortcutInfo infoToLaunch = secondIntentPipped ? shortcutInfo1 : shortcutInfo2;
+                wct.startShortcut(mContext.getPackageName(), infoToLaunch, options);
+                mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+            } else {
+                PendingIntent intentToLaunch = secondIntentPipped ? pendingIntent1 : pendingIntent2;
+                Intent fillInIntentToLaunch = secondIntentPipped ? fillInIntent1 : fillInIntent2;
+                startSingleIntent(intentToLaunch, fillInIntentToLaunch, options, wct,
+                        remoteTransition);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /** @param pendingIntent Starts this intent in fullscreen */
+    private void startSingleIntent(PendingIntent pendingIntent, Intent fillInIntent, Bundle options,
+            WindowContainerTransaction wct,
+            RemoteTransition remoteTransition) {
+        Bundle optionsToLaunch = options != null ? options : new Bundle();
+        addActivityOptions(optionsToLaunch, null);
+        wct.sendPendingIntent(pendingIntent, fillInIntent, optionsToLaunch);
+        mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+    }
+
     /** Starts a pair of tasks using legacy transition. */
     void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
             int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 8746b8c..99133f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -562,22 +562,23 @@
 
     /** Use to when split use intent to enter, check if this enter transition should be mixed or
      * not.*/
-    public boolean shouldSplitEnterMixed(PendingIntent intent) {
+    public boolean isIntentInPip(PendingIntent intent) {
         // Check if this intent package is same as pip one or not, if true we want let the pip
         // task enter split.
         if (mPipHandler != null) {
-            return mPipHandler.isInPipPackage(SplitScreenUtils.getPackageName(intent.getIntent()));
+            return mPipHandler
+                    .isPackageActiveInPip(SplitScreenUtils.getPackageName(intent.getIntent()));
         }
         return false;
     }
 
     /** Use to when split use taskId to enter, check if this enter transition should be mixed or
      * not.*/
-    public boolean shouldSplitEnterMixed(int taskId, ShellTaskOrganizer shellTaskOrganizer) {
+    public boolean isTaskInPip(int taskId, ShellTaskOrganizer shellTaskOrganizer) {
         // Check if this intent package is same as pip one or not, if true we want let the pip
         // task enter split.
         if (mPipHandler != null) {
-            return mPipHandler.isInPipPackage(
+            return mPipHandler.isPackageActiveInPip(
                     SplitScreenUtils.getPackageName(taskId, shellTaskOrganizer));
         }
         return false;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index d819261..d7c3835 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -40,10 +40,12 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
+import android.app.PendingIntent;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
@@ -51,6 +53,7 @@
 import android.os.Looper;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
+import android.window.RemoteTransition;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
@@ -74,6 +77,7 @@
 import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.DefaultMixedHandler;
 import com.android.wm.shell.transition.HomeTransitionObserver;
 import com.android.wm.shell.transition.Transitions;
 
@@ -111,6 +115,8 @@
     private TransactionPool mTransactionPool;
     @Mock
     private LaunchAdjacentController mLaunchAdjacentController;
+    @Mock
+    private DefaultMixedHandler mDefaultMixedHandler;
 
     private final Rect mBounds1 = new Rect(10, 20, 30, 40);
     private final Rect mBounds2 = new Rect(5, 10, 15, 20);
@@ -370,6 +376,96 @@
         }
     }
 
+    @Test
+    public void testSplitIntentAndTaskWithPippedApp_launchFullscreen() {
+        int taskId = 9;
+        SplitScreenTransitions splitScreenTransitions =
+                spy(mStageCoordinator.getSplitTransitions());
+        mStageCoordinator.setSplitTransitions(splitScreenTransitions);
+        mStageCoordinator.setMixedHandler(mDefaultMixedHandler);
+        PendingIntent pendingIntent = mock(PendingIntent.class);
+        RemoteTransition remoteTransition = mock(RemoteTransition.class);
+        when(remoteTransition.getDebugName()).thenReturn("");
+        // Test launching second task full screen
+        when(mDefaultMixedHandler.isIntentInPip(pendingIntent)).thenReturn(true);
+        mStageCoordinator.startIntentAndTask(
+                pendingIntent,
+                null /*fillInIntent*/,
+                null /*option1*/,
+                taskId,
+                null /*option2*/,
+                0 /*splitPosition*/,
+                1 /*snapPosition*/,
+                remoteTransition /*remoteTransition*/,
+                null /*instanceId*/);
+        verify(splitScreenTransitions, times(1))
+                .startFullscreenTransition(any(), any());
+
+        // Test launching first intent fullscreen
+        when(mDefaultMixedHandler.isIntentInPip(pendingIntent)).thenReturn(false);
+        when(mDefaultMixedHandler.isTaskInPip(taskId, mTaskOrganizer)).thenReturn(true);
+        mStageCoordinator.startIntentAndTask(
+                pendingIntent,
+                null /*fillInIntent*/,
+                null /*option1*/,
+                taskId,
+                null /*option2*/,
+                0 /*splitPosition*/,
+                1 /*snapPosition*/,
+                remoteTransition /*remoteTransition*/,
+                null /*instanceId*/);
+        verify(splitScreenTransitions, times(2))
+                .startFullscreenTransition(any(), any());
+    }
+
+    @Test
+    public void testSplitIntentsWithPippedApp_launchFullscreen() {
+        SplitScreenTransitions splitScreenTransitions =
+                spy(mStageCoordinator.getSplitTransitions());
+        mStageCoordinator.setSplitTransitions(splitScreenTransitions);
+        mStageCoordinator.setMixedHandler(mDefaultMixedHandler);
+        PendingIntent pendingIntent = mock(PendingIntent.class);
+        PendingIntent pendingIntent2 = mock(PendingIntent.class);
+        RemoteTransition remoteTransition = mock(RemoteTransition.class);
+        when(remoteTransition.getDebugName()).thenReturn("");
+        // Test launching second task full screen
+        when(mDefaultMixedHandler.isIntentInPip(pendingIntent)).thenReturn(true);
+        mStageCoordinator.startIntents(
+                pendingIntent,
+                null /*fillInIntent*/,
+                null /*shortcutInfo1*/,
+                new Bundle(),
+                pendingIntent2,
+                null /*fillInIntent2*/,
+                null /*shortcutInfo1*/,
+                new Bundle(),
+                0 /*splitPosition*/,
+                1 /*snapPosition*/,
+                remoteTransition /*remoteTransition*/,
+                null /*instanceId*/);
+        verify(splitScreenTransitions, times(1))
+                .startFullscreenTransition(any(), any());
+
+        // Test launching first intent fullscreen
+        when(mDefaultMixedHandler.isIntentInPip(pendingIntent)).thenReturn(false);
+        when(mDefaultMixedHandler.isIntentInPip(pendingIntent2)).thenReturn(true);
+        mStageCoordinator.startIntents(
+                pendingIntent,
+                null /*fillInIntent*/,
+                null /*shortcutInfo1*/,
+                new Bundle(),
+                pendingIntent2,
+                null /*fillInIntent2*/,
+                null /*shortcutInfo1*/,
+                new Bundle(),
+                0 /*splitPosition*/,
+                1 /*snapPosition*/,
+                remoteTransition /*remoteTransition*/,
+                null /*instanceId*/);
+        verify(splitScreenTransitions, times(2))
+                .startFullscreenTransition(any(), any());
+    }
+
     private Transitions createTestTransitions() {
         ShellInit shellInit = new ShellInit(mMainExecutor);
         final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
diff --git a/packages/SettingsLib/res/values/config.xml b/packages/SettingsLib/res/values/config.xml
index 45253bb..68b81db 100644
--- a/packages/SettingsLib/res/values/config.xml
+++ b/packages/SettingsLib/res/values/config.xml
@@ -22,6 +22,9 @@
     <!-- Threshold in micro watts above which a charger is rated as "fast"; 1.5A @ 5V  -->
     <integer name="config_chargingFastThreshold">7500000</integer>
 
+    <!-- Threshold in micro watts above which a charger is rated as "fast"; 20W  -->
+    <integer name="config_chargingFastThreshold_v2">20000000</integer>
+
     <!-- When true, show 1/2G networks as 3G. -->
     <bool name="config_showMin3G">false</bool>
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
index 2032328..f659e38 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
@@ -145,7 +145,8 @@
         final int slowThreshold = context.getResources().getInteger(
                 R.integer.config_chargingSlowlyThreshold);
         final int fastThreshold = context.getResources().getInteger(
-                R.integer.config_chargingFastThreshold);
+                getFastChargingThresholdResId());
+
         return maxChargingWattage <= 0 ? CHARGING_UNKNOWN :
                 maxChargingWattage < slowThreshold ? CHARGING_SLOWLY :
                         maxChargingWattage > fastThreshold ? CHARGING_FAST :
@@ -382,7 +383,7 @@
                 < context.getResources().getInteger(R.integer.config_chargingSlowlyThreshold)) {
             return CHARGING_SLOWLY;
         } else if (maxChargingMicroWatt
-                > context.getResources().getInteger(R.integer.config_chargingFastThreshold)) {
+                > context.getResources().getInteger(getFastChargingThresholdResId())) {
             return CHARGING_FAST;
         } else {
             return CHARGING_REGULAR;
@@ -410,4 +411,10 @@
             return -1;
         }
     }
+
+    private static int getFastChargingThresholdResId() {
+        return BatteryUtils.isChargingStringV2Enabled()
+                        ? R.integer.config_chargingFastThreshold_v2
+                        : R.integer.config_chargingFastThreshold;
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java
index 327e470..ca3af53 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java
@@ -27,6 +27,7 @@
 import android.util.ArraySet;
 import android.view.accessibility.AccessibilityManager;
 
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
 import java.util.List;
@@ -97,9 +98,18 @@
     /** Used to override the system property to enable or reset for charging string V2. */
     @VisibleForTesting
     public static void setChargingStringV2Enabled(Boolean enabled) {
-        SystemProperties.set(
-                BatteryUtils.PROPERTY_CHARGING_STRING_V2_KEY,
-                enabled == null ? "" : String.valueOf(enabled));
+        setChargingStringV2Enabled(enabled, true /* updateProperty */);
+    }
+
+    /** Used to override the system property to enable or reset for charging string V2. */
+    @VisibleForTesting
+    public static void setChargingStringV2Enabled(
+            @Nullable Boolean enabled, boolean updateProperty) {
+        if (updateProperty) {
+            SystemProperties.set(
+                    BatteryUtils.PROPERTY_CHARGING_STRING_V2_KEY,
+                    enabled == null ? "" : String.valueOf(enabled));
+        }
         BatteryUtils.sChargingStringV2Enabled = enabled;
     }
 }
diff --git a/packages/SettingsLib/tests/unit/src/com/android/settingslib/fuelgague/BatteryStatusTest.kt b/packages/SettingsLib/tests/unit/src/com/android/settingslib/fuelgague/BatteryStatusTest.kt
index 6c0c1a7..4940610 100644
--- a/packages/SettingsLib/tests/unit/src/com/android/settingslib/fuelgague/BatteryStatusTest.kt
+++ b/packages/SettingsLib/tests/unit/src/com/android/settingslib/fuelgague/BatteryStatusTest.kt
@@ -38,6 +38,7 @@
 import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_SLOWLY
 import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_UNKNOWN
 import com.android.settingslib.fuelgauge.BatteryStatus.isBatteryDefender
+import com.android.settingslib.fuelgauge.BatteryUtils
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import java.util.Optional
@@ -253,12 +254,17 @@
         private val maxChargingCurrent: Optional<Int>,
         private val maxChargingVoltage: Optional<Int>,
         private val expectedChargingSpeed: Int,
+        private val chargingStringV2Enabled: Boolean,
     ) {
 
         val context: Context = ApplicationProvider.getApplicationContext()
 
         @Test
         fun getChargingSpeed_() {
+            BatteryUtils.setChargingStringV2Enabled(
+                chargingStringV2Enabled,
+                false /* updateProperty */
+            )
             val batteryChangedIntent =
                 Intent(Intent.ACTION_BATTERY_CHANGED).apply {
                     maxChargingCurrent.ifPresent { putExtra(EXTRA_MAX_CHARGING_CURRENT, it) }
@@ -278,37 +284,57 @@
                         "maxCurrent=n/a, maxVoltage=n/a -> UNKNOWN",
                         Optional.empty<Int>(),
                         Optional.empty<Int>(),
-                        CHARGING_UNKNOWN
+                        CHARGING_UNKNOWN,
+                        false /* chargingStringV2Enabled */
                     ),
                     arrayOf(
                         "maxCurrent=0, maxVoltage=9000000 -> UNKNOWN",
                         Optional.of(0),
                         Optional.of(0),
-                        CHARGING_UNKNOWN
+                        CHARGING_UNKNOWN,
+                        false /* chargingStringV2Enabled */
                     ),
                     arrayOf(
                         "maxCurrent=1500000, maxVoltage=5000000 -> CHARGING_REGULAR",
                         Optional.of(1500000),
                         Optional.of(5000000),
-                        CHARGING_REGULAR
+                        CHARGING_REGULAR,
+                        false /* chargingStringV2Enabled */
                     ),
                     arrayOf(
                         "maxCurrent=1000000, maxVoltage=5000000 -> CHARGING_REGULAR",
                         Optional.of(1000000),
                         Optional.of(5000000),
-                        CHARGING_REGULAR
+                        CHARGING_REGULAR,
+                        false /* chargingStringV2Enabled */
                     ),
                     arrayOf(
                         "maxCurrent=1500001, maxVoltage=5000000 -> CHARGING_FAST",
                         Optional.of(1501000),
                         Optional.of(5000000),
-                        CHARGING_FAST
+                        CHARGING_FAST,
+                        false /* chargingStringV2Enabled */
                     ),
                     arrayOf(
                         "maxCurrent=999999, maxVoltage=5000000 -> CHARGING_SLOWLY",
                         Optional.of(999999),
                         Optional.of(5000000),
-                        CHARGING_SLOWLY
+                        CHARGING_SLOWLY,
+                        false /* chargingStringV2Enabled */
+                    ),
+                    arrayOf(
+                        "maxCurrent=3000000, maxVoltage=9000000 -> CHARGING_FAST",
+                        Optional.of(3000000),
+                        Optional.of(9000000),
+                        CHARGING_FAST,
+                        true /* chargingStringV2Enabled */
+                    ),
+                    arrayOf(
+                        "maxCurrent=2200000, maxVoltage=9000000 -> CHARGING_REGULAR",
+                        Optional.of(2200000),
+                        Optional.of(9000000),
+                        CHARGING_REGULAR,
+                        true /* chargingStringV2Enabled */
                     ),
                 )
         }
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
index c0a873a..989b0de 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
@@ -18,6 +18,7 @@
 import android.content.Context
 import android.os.Bundle
 import android.view.View
+import android.view.WindowInsets
 import android.widget.TextView
 import androidx.core.view.updatePadding
 import com.android.systemui.res.R
@@ -44,7 +45,10 @@
     private lateinit var mirrorButton: TextView
     private lateinit var dismissButton: TextView
     private lateinit var dualDisplayWarning: TextView
+    private lateinit var bottomSheet: View
     private var enabledPressed = false
+    private val defaultDialogBottomInset =
+        context.resources.getDimensionPixelSize(R.dimen.dialog_bottom_padding)
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
@@ -63,6 +67,8 @@
                 visibility = if (showConcurrentDisplayInfo) View.VISIBLE else View.GONE
             }
 
+        bottomSheet = requireViewById(R.id.cd_bottom_sheet)
+
         setOnDismissListener {
             if (!enabledPressed) {
                 onCancelMirroring.onClick(null)
@@ -71,15 +77,17 @@
         setupInsets()
     }
 
-    private fun setupInsets() {
+    private fun setupInsets(navbarInsets: Int = navbarBottomInsetsProvider()) {
         // This avoids overlap between dialog content and navigation bars.
-        requireViewById<View>(R.id.cd_bottom_sheet).apply {
-            val navbarInsets = navbarBottomInsetsProvider()
-            val defaultDialogBottomInset =
-                context.resources.getDimensionPixelSize(R.dimen.dialog_bottom_padding)
-            // we only care about the bottom inset as in all other configuration where navigations
-            // are in other display sides there is no overlap with the dialog.
-            updatePadding(bottom = max(navbarInsets, defaultDialogBottomInset))
+        // we only care about the bottom inset as in all other configuration where navigations
+        // are in other display sides there is no overlap with the dialog.
+        bottomSheet.updatePadding(bottom = max(navbarInsets, defaultDialogBottomInset))
+    }
+
+    override fun onInsetsChanged(changedTypes: Int, insets: WindowInsets) {
+        val navbarType = WindowInsets.Type.navigationBars()
+        if (changedTypes and navbarType != 0) {
+            setupInsets(insets.getInsets(navbarType).bottom)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
index 190062c..fbf0538 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
@@ -32,9 +32,12 @@
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
 import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.FlowPreview
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.debounce
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.launch
@@ -57,6 +60,7 @@
     private var dialog: Dialog? = null
 
     /** Starts listening for pending displays. */
+    @OptIn(FlowPreview::class)
     override fun start() {
         val pendingDisplayFlow = connectedDisplayInteractor.pendingDisplay
         val concurrentDisplaysInProgessFlow =
@@ -66,6 +70,13 @@
                 flow { emit(false) }
             }
         pendingDisplayFlow
+            // Let's debounce for 2 reasons:
+            // - prevent fast dialog flashes in case pending displays are available for just a few
+            // millis
+            // - Prevent jumps related to inset changes: when in 3 buttons navigation, device
+            // unlock triggers a change in insets that might result in a jump of the dialog (if a
+            // display was connected while on the lockscreen).
+            .debounce(200.milliseconds)
             .combine(concurrentDisplaysInProgessFlow) { pendingDisplay, concurrentDisplaysInProgress
                 ->
                 if (pendingDisplay == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index d49a513..07f1ebd 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -35,6 +35,7 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.database.ContentObserver;
 import android.inputmethodservice.InputMethodService;
 import android.net.Uri;
@@ -73,6 +74,7 @@
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import dagger.Lazy;
@@ -100,7 +102,7 @@
         AccessibilityButtonModeObserver.ModeChangedListener,
         AccessibilityButtonTargetsObserver.TargetsChangedListener,
         OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener,
-        Dumpable, CommandQueue.Callbacks {
+        Dumpable, CommandQueue.Callbacks, ConfigurationController.ConfigurationListener {
     private static final String TAG = NavBarHelper.class.getSimpleName();
 
     private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -188,6 +190,7 @@
             UserTracker userTracker,
             DisplayTracker displayTracker,
             NotificationShadeWindowController notificationShadeWindowController,
+            ConfigurationController configurationController,
             DumpManager dumpManager,
             CommandQueue commandQueue,
             @Main Executor mainExecutor) {
@@ -214,6 +217,7 @@
 
         mNavBarMode = navigationModeController.addListener(this);
         mCommandQueue.addCallback(this);
+        configurationController.addCallback(this);
         overviewProxyService.addCallback(this);
         dumpManager.registerDumpable(this);
     }
@@ -358,6 +362,11 @@
         updateA11yState();
     }
 
+    @Override
+    public void onConfigChanged(Configuration newConfig) {
+        mEdgeBackGestureHandler.onConfigurationChanged(newConfig);
+    }
+
     /**
      * Updates the current accessibility button state. The accessibility button state is only
      * used for {@link Secure#ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR} and
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 1793861..13ba9c3 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -874,11 +874,6 @@
             refreshLayout(ld);
         }
         repositionNavigationBar(rotation);
-        // NOTE(b/260220098): In some cases, the recreated nav bar will already have the right
-        // configuration, which means that NavBarView will not receive a configuration change to
-        // propagate to EdgeBackGestureHandler (which is injected into this and NBV). As such, we
-        // should also force-update the gesture handler to ensure it updates to the right bounds
-        mEdgeBackGestureHandler.onConfigurationChanged(newConfig);
         if (canShowSecondaryHandle()) {
             if (rotation != mCurrentRotation) {
                 mCurrentRotation = rotation;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
index 152f193..42a25bd 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
@@ -160,14 +160,11 @@
         mIsLargeScreen = isLargeScreen(mContext);
         boolean willApplyConfig = mConfigChanges.applyNewConfig(mContext.getResources());
         boolean largeScreenChanged = mIsLargeScreen != isOldConfigLargeScreen;
-        // TODO(b/243765256): Disable this logging once b/243765256 is fixed.
+        // TODO(b/332635834): Disable this logging once b/332635834 is fixed.
         Log.i(DEBUG_MISSING_GESTURE_TAG, "NavbarController: newConfig=" + newConfig
                 + " mTaskbarDelegate initialized=" + mTaskbarDelegate.isInitialized()
                 + " willApplyConfigToNavbars=" + willApplyConfig
                 + " navBarCount=" + mNavigationBars.size());
-        if (mTaskbarDelegate.isInitialized()) {
-            mTaskbarDelegate.onConfigurationChanged(newConfig);
-        }
         // If we folded/unfolded while in 3 button, show navbar in folded state, hide in unfolded
         if (largeScreenChanged && updateNavbarForTaskbar()) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index c5190a2..bc0a1da 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -1026,7 +1026,6 @@
         updateIcons(mTmpLastConfiguration);
         updateRecentsIcon();
         updateCurrentRotation();
-        mEdgeBackGestureHandler.onConfigurationChanged(mConfiguration);
         if (uiCarModeChanged || mTmpLastConfiguration.densityDpi != mConfiguration.densityDpi
                 || mTmpLastConfiguration.getLayoutDirection() != mConfiguration.getLayoutDirection()) {
             // If car mode or density changes, we need to reset the icons.
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 5dd1bd8..f67973b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -39,7 +39,6 @@
 import android.app.StatusBarManager;
 import android.app.StatusBarManager.WindowVisibleState;
 import android.content.Context;
-import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.inputmethodservice.InputMethodService;
@@ -72,7 +71,6 @@
 import com.android.systemui.statusbar.AutoHideUiElement;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.AutoHideController;
-import com.android.systemui.statusbar.phone.BarTransitions;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.LightBarTransitionsController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -250,8 +248,6 @@
             mLightBarController.setNavigationBar(mLightBarTransitionsController);
             mPipOptional.ifPresent(this::addPipExclusionBoundsChangeListener);
             mEdgeBackGestureHandler.setBackAnimation(mBackAnimation);
-            mEdgeBackGestureHandler.onConfigurationChanged(
-                    mContext.getResources().getConfiguration());
             mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener);
             mInitialized = true;
         } finally {
@@ -495,10 +491,6 @@
         return mBehavior != BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
     }
 
-    public void onConfigurationChanged(Configuration configuration) {
-        mEdgeBackGestureHandler.onConfigurationChanged(configuration);
-    }
-
     @Override
     public void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {
         mOverviewProxyService.onNavigationBarLumaSamplingEnabled(displayId, enable);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 91c86df..d40949f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -1180,7 +1180,7 @@
             updateDisabledForQuickstep(newConfig);
         }
 
-        // TODO(b/243765256): Disable this logging once b/243765256 is fixed.
+        // TODO(b/332635834): Disable this logging once b/332635834 is fixed.
         Log.i(DEBUG_MISSING_GESTURE_TAG, "Config changed: newConfig=" + newConfig
                 + " lastReportedConfig=" + mLastReportedConfig);
         mLastReportedConfig.updateFrom(newConfig);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
index 71e25e9..541ac48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
@@ -23,6 +23,9 @@
 import android.os.Bundle
 import android.view.Gravity
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.view.WindowInsets
+import android.view.WindowInsets.Type.InsetsType
+import android.view.WindowInsetsAnimation
 import android.view.WindowManager.LayoutParams.MATCH_PARENT
 import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
 import android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
@@ -70,16 +73,43 @@
     override fun onStart() {
         super.onStart()
         configurationController?.addCallback(onConfigChanged)
+        window?.decorView?.setWindowInsetsAnimationCallback(insetsAnimationCallback)
     }
 
     override fun onStop() {
         super.onStop()
         configurationController?.removeCallback(onConfigChanged)
+        window?.decorView?.setWindowInsetsAnimationCallback(null)
     }
 
+    /** Called after any insets change. */
+    open fun onInsetsChanged(@InsetsType changedTypes: Int, insets: WindowInsets) {}
+
     /** Can be overridden by subclasses to receive config changed events. */
     open fun onConfigurationChanged() {}
 
+    private val insetsAnimationCallback =
+        object : WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
+
+            private var lastInsets: WindowInsets? = null
+
+            override fun onEnd(animation: WindowInsetsAnimation) {
+                lastInsets?.let { onInsetsChanged(animation.typeMask, it) }
+            }
+
+            override fun onProgress(
+                insets: WindowInsets,
+                animations: MutableList<WindowInsetsAnimation>,
+            ): WindowInsets {
+                lastInsets = insets
+                onInsetsChanged(changedTypes = allAnimationMasks(animations), insets)
+                return insets
+            }
+
+            private fun allAnimationMasks(animations: List<WindowInsetsAnimation>): Int =
+                animations.fold(0) { acc: Int, it -> acc or it.typeMask }
+        }
+
     private val onConfigChanged =
         object : ConfigurationListener {
             override fun onConfigChanged(newConfig: Configuration?) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
index b25fb6e..30519b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
@@ -16,14 +16,17 @@
 
 package com.android.systemui.display.ui.view
 
+import android.graphics.Insets
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.View
+import android.view.WindowInsets
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.res.R
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -41,6 +44,7 @@
 
     private val onStartMirroringCallback = mock<View.OnClickListener>()
     private val onCancelCallback = mock<View.OnClickListener>()
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -96,10 +100,40 @@
         verify(onStartMirroringCallback).onClick(any())
     }
 
+    @Test
+    fun onInsetsChanged_navBarInsets_updatesBottomPadding() {
+        dialog.show()
+
+        val insets = buildInsets(WindowInsets.Type.navigationBars(), TEST_BOTTOM_INSETS)
+        dialog.onInsetsChanged(WindowInsets.Type.navigationBars(), insets)
+
+        assertThat(dialog.requireViewById<View>(R.id.cd_bottom_sheet).paddingBottom)
+            .isEqualTo(TEST_BOTTOM_INSETS)
+    }
+
+    @Test
+    fun onInsetsChanged_otherType_doesNotUpdateBottomPadding() {
+        dialog.show()
+
+        val insets = buildInsets(WindowInsets.Type.ime(), TEST_BOTTOM_INSETS)
+        dialog.onInsetsChanged(WindowInsets.Type.ime(), insets)
+
+        assertThat(dialog.requireViewById<View>(R.id.cd_bottom_sheet).paddingBottom)
+            .isNotEqualTo(TEST_BOTTOM_INSETS)
+    }
+
+    private fun buildInsets(@WindowInsets.Type.InsetsType type: Int, bottom: Int): WindowInsets {
+        return WindowInsets.Builder().setInsets(type, Insets.of(0, 0, 0, bottom)).build()
+    }
+
     @After
     fun teardown() {
         if (::dialog.isInitialized) {
             dialog.dismiss()
         }
     }
+
+    private companion object {
+        const val TEST_BOTTOM_INSETS = 1000 // arbitrarily high number
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
index e5ba569..277119d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
@@ -35,6 +35,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.ComponentName;
+import android.content.res.Configuration;
 import android.view.IWindowManager;
 import android.view.accessibility.AccessibilityManager;
 
@@ -54,6 +55,8 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.FakeConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
@@ -116,6 +119,7 @@
     EdgeBackGestureHandler.Factory mEdgeBackGestureHandlerFactory;
     @Mock
     NotificationShadeWindowController mNotificationShadeWindowController;
+    ConfigurationController mConfigurationController = new FakeConfigurationController();
 
     private AccessibilityManager.AccessibilityServicesStateChangeListener
             mAccessibilityServicesStateChangeListener;
@@ -143,9 +147,8 @@
                 mSystemActions, mOverviewProxyService, mAssistManagerLazy,
                 () -> Optional.of(mock(CentralSurfaces.class)), mock(KeyguardStateController.class),
                 mNavigationModeController, mEdgeBackGestureHandlerFactory, mWm, mUserTracker,
-                mDisplayTracker, mNotificationShadeWindowController, mDumpManager, mCommandQueue,
-                mSynchronousExecutor);
-
+                mDisplayTracker, mNotificationShadeWindowController, mConfigurationController,
+                mDumpManager, mCommandQueue, mSynchronousExecutor);
     }
 
     @Test
@@ -334,6 +337,12 @@
         assertThat(state2.mWindowState).isNotEqualTo(newState);
     }
 
+    @Test
+    public void configUpdatePropagatesToEdgeBackGestureHandler() {
+        mConfigurationController.onConfigurationChanged(Configuration.EMPTY);
+        verify(mEdgeBackGestureHandler, times(1)).onConfigurationChanged(any());
+    }
+
     private List<String> createFakeShortcutTargets() {
         return new ArrayList<>(List.of("a", "b", "c", "d"));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
index 52859cd..df75ea7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
@@ -37,11 +37,8 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
-import android.content.res.Configuration;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 import android.util.SparseArray;
@@ -292,23 +289,6 @@
     }
 
     @Test
-    public void testConfigurationChange_taskbarNotInitialized() {
-        Configuration configuration = mContext.getResources().getConfiguration();
-        mNavigationBarController.mIsLargeScreen = true;
-        mNavigationBarController.onConfigChanged(configuration);
-        verify(mTaskbarDelegate, never()).onConfigurationChanged(configuration);
-    }
-
-    @Test
-    public void testConfigurationChange_taskbarInitialized() {
-        Configuration configuration = mContext.getResources().getConfiguration();
-        mNavigationBarController.mIsLargeScreen = true;
-        when(mTaskbarDelegate.isInitialized()).thenReturn(true);
-        mNavigationBarController.onConfigChanged(configuration);
-        verify(mTaskbarDelegate, times(1)).onConfigurationChanged(configuration);
-    }
-
-    @Test
     public void testShouldRenderTaskbar_taskbarNotRenderedOnPhone() {
         mNavigationBarController.mIsLargeScreen = false;
         mNavigationBarController.mIsPhone = true;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 31746a2..2382746 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -110,6 +110,7 @@
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.LightBarTransitionsController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.DeviceConfigProxyFake;
@@ -274,8 +275,8 @@
                     mKeyguardStateController, mock(NavigationModeController.class),
                     mEdgeBackGestureHandlerFactory, mock(IWindowManager.class),
                     mock(UserTracker.class), mock(DisplayTracker.class),
-                    mNotificationShadeWindowController, mock(DumpManager.class),
-                    mock(CommandQueue.class), mSynchronousExecutor));
+                    mNotificationShadeWindowController, mock(ConfigurationController.class),
+                    mock(DumpManager.class), mock(CommandQueue.class), mSynchronousExecutor));
             mNavigationBar = createNavBar(mContext);
             mExternalDisplayNavigationBar = createNavBar(mSysuiTestableContextExternal);
         });
diff --git a/services/core/java/com/android/server/audio/MusicFxHelper.java b/services/core/java/com/android/server/audio/MusicFxHelper.java
index 85b3b49..ba45310 100644
--- a/services/core/java/com/android/server/audio/MusicFxHelper.java
+++ b/services/core/java/com/android/server/audio/MusicFxHelper.java
@@ -90,7 +90,6 @@
      *    observer will also be removed, and observer token reset to null
      */
     private class MySparseArray extends SparseArray<PackageSessions> {
-        private final String mMusicFxPackageName = "com.android.musicfx";
 
         @RequiresPermission(anyOf = {
                 android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
@@ -229,6 +228,10 @@
         if (ril != null && ril.size() != 0) {
             ResolveInfo ri = ril.get(0);
             final String senderPackageName = intent.getStringExtra(AudioEffect.EXTRA_PACKAGE_NAME);
+            if (senderPackageName == null) {
+                Log.w(TAG, "Intent package name must not be null");
+                return;
+            }
             try {
                 if (ri != null && ri.activityInfo != null && ri.activityInfo.packageName != null) {
                     final int senderUid = pm.getPackageUidAsUser(senderPackageName,
@@ -265,7 +268,7 @@
                         + senderUid + ", package: " + senderPackageName + ", abort");
                 return false;
             }
-            if (pkgSessions.mPackageName != senderPackageName) {
+            if (!pkgSessions.mPackageName.equals(senderPackageName)) {
                 Log.w(TAG, "Inconsistency package names for UID open: " + senderUid + " prev: "
                         + pkgSessions.mPackageName + ", now: " + senderPackageName);
                 return false;
@@ -297,7 +300,7 @@
             Log.e(TAG, senderPackageName + " UID " + senderUid + " does not exist in map, abort");
             return false;
         }
-        if (pkgSessions.mPackageName != senderPackageName) {
+        if (!pkgSessions.mPackageName.equals(senderPackageName)) {
             Log.w(TAG, "Inconsistency package names for UID " + senderUid + " close, prev: "
                     + pkgSessions.mPackageName + ", now: " + senderPackageName);
             return false;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 93d1b6e..e9fac7e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -407,6 +407,12 @@
             if (session.hasContextMethods()) {
                 try {
                     session.getSession().onContextChanged(ctx);
+                    // TODO(b/317414324): Deprecate setIgnoreDisplayTouches
+                    if (ctx.operationState != null && ctx.operationState.getTag()
+                            == OperationState.fingerprintOperationState) {
+                        session.getSession().setIgnoreDisplayTouches(ctx.operationState
+                                .getFingerprintOperationState().isHardwareIgnoringTouches);
+                    }
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Unable to notify context changed", e);
                 }
diff --git a/services/core/java/com/android/server/policy/SideFpsEventHandler.java b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
index 2d76c50..4ad4353 100644
--- a/services/core/java/com/android/server/policy/SideFpsEventHandler.java
+++ b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
@@ -38,6 +38,7 @@
 import android.view.View;
 import android.view.Window;
 import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
@@ -66,6 +67,7 @@
     private final int mDismissDialogTimeout;
     @Nullable
     private SideFpsToast mDialog;
+    private final AccessibilityManager mAccessibilityManager;
     private final Runnable mTurnOffDialog =
             () -> {
                 dismissDialog("mTurnOffDialog");
@@ -96,6 +98,7 @@
             DialogProvider provider) {
         mContext = context;
         mHandler = handler;
+        mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
         mPowerManager = powerManager;
         mBiometricState = STATE_IDLE;
         mSideFpsEventHandlerReady = new AtomicBoolean(false);
@@ -157,7 +160,9 @@
                                 mHandler.removeCallbacks(mTurnOffDialog);
                             }
                             showDialog(eventTime, "Enroll Power Press");
-                            mHandler.postDelayed(mTurnOffDialog, mDismissDialogTimeout);
+                            if (!mAccessibilityManager.isEnabled()) {
+                                mHandler.postDelayed(mTurnOffDialog, mDismissDialogTimeout);
+                            }
                         });
                 return true;
             case STATE_BP_AUTH:
@@ -231,6 +236,10 @@
                                         public void onBiometricAction(
                                                 @BiometricStateListener.Action int action) {
                                             Log.d(TAG, "onBiometricAction " + action);
+                                            if (mAccessibilityManager != null
+                                                    && mAccessibilityManager.isEnabled()) {
+                                                dismissDialog("mTurnOffDialog");
+                                            }
                                         }
                                     });
                             mSideFpsEventHandlerReady.set(true);
@@ -256,6 +265,9 @@
         mLastPowerPressTime = time;
         mDialog.show();
         mDialog.setOnClickListener(this);
+        if (mAccessibilityManager.isEnabled()) {
+            mDialog.addAccessibilityDelegate();
+        }
     }
 
     interface DialogProvider {
diff --git a/services/core/java/com/android/server/policy/SideFpsToast.java b/services/core/java/com/android/server/policy/SideFpsToast.java
index db07467..c27753c 100644
--- a/services/core/java/com/android/server/policy/SideFpsToast.java
+++ b/services/core/java/com/android/server/policy/SideFpsToast.java
@@ -16,6 +16,7 @@
 
 package com.android.server.policy;
 
+import android.annotation.NonNull;
 import android.app.Dialog;
 import android.content.Context;
 import android.os.Bundle;
@@ -23,6 +24,7 @@
 import android.view.View;
 import android.view.Window;
 import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
 import android.widget.Button;
 
 import com.android.internal.R;
@@ -34,7 +36,6 @@
  * This dialog is used by {@link SideFpsEventHandler}
  */
 public class SideFpsToast extends Dialog {
-
     SideFpsToast(Context context) {
         super(context);
     }
@@ -66,4 +67,27 @@
             turnOffScreen.setOnClickListener(listener);
         }
     }
+
+    /**
+     * When accessibility mode is on, add AccessibilityDelegate to dismiss dialog when focus is
+     * moved away from the dialog.
+     */
+    public void addAccessibilityDelegate() {
+        final Button turnOffScreen = findViewById(R.id.turn_off_screen);
+        if (turnOffScreen != null) {
+            turnOffScreen.setAccessibilityDelegate(new View.AccessibilityDelegate() {
+                @Override
+                public void onInitializeAccessibilityEvent(@NonNull View host,
+                        @NonNull AccessibilityEvent event) {
+                    if (event.getEventType()
+                            == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED
+                            && isShowing()) {
+                        dismiss();
+                    }
+                    super.onInitializeAccessibilityEvent(host, event);
+                }
+            });
+
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 8224958..130baff 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -9766,10 +9766,10 @@
         if (mLetterboxUiController.shouldApplyUserMinAspectRatioOverride()) {
             return mLetterboxUiController.getUserMinAspectRatio();
         }
-        if (!mLetterboxUiController.shouldOverrideMinAspectRatio()) {
+        if (!mLetterboxUiController.shouldOverrideMinAspectRatio()
+                && !mLetterboxUiController.shouldOverrideMinAspectRatioForCamera()) {
             return info.getMinAspectRatio();
         }
-
         if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY)
                 && !ActivityInfo.isFixedOrientationPortrait(
                         getOverrideOrientation())) {
@@ -9890,6 +9890,16 @@
         return updateReportedConfigurationAndSend();
     }
 
+    /**
+     * @return {@code true} if the Camera is active for the current activity
+     */
+    boolean isCameraActive() {
+        return mDisplayContent != null
+                && mDisplayContent.getDisplayRotationCompatPolicy() != null
+                && mDisplayContent.getDisplayRotationCompatPolicy()
+                    .isCameraActive(this, /* mustBeFullscreen */ true);
+    }
+
     boolean updateReportedConfigurationAndSend() {
         if (isConfigurationDispatchPaused()) {
             Slog.wtf(TAG, "trying to update reported(client) config while dispatch is paused");
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index add0f1f..229bb9e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1307,6 +1307,15 @@
         }
     }
 
+    /**
+     * @return The {@link DisplayRotationCompatPolicy} for this DisplayContent
+     */
+    // TODO(b/335387481) Allow access to DisplayRotationCompatPolicy only with getters
+    @Nullable
+    DisplayRotationCompatPolicy getDisplayRotationCompatPolicy() {
+        return mDisplayRotationCompatPolicy;
+    }
+
     @Override
     void migrateToNewSurfaceControl(Transaction t) {
         t.remove(mSurfaceControl);
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index e808dec..816fd39 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -71,7 +71,7 @@
  * R.bool.config_isWindowManagerCameraCompatTreatmentEnabled} is {@code true}.
  */
  // TODO(b/261444714): Consider moving Camera-specific logic outside of the WM Core path
-final class DisplayRotationCompatPolicy {
+class DisplayRotationCompatPolicy {
 
     // Delay for updating display rotation after Camera connection is closed. Needed to avoid
     // rotation flickering when an app is flipping between front and rear cameras or when size
@@ -342,7 +342,8 @@
 
     boolean isActivityEligibleForOrientationOverride(@NonNull ActivityRecord activity) {
         return isTreatmentEnabledForDisplay()
-                && isCameraActive(activity, /* mustBeFullscreen */ true);
+                && isCameraActive(activity, /* mustBeFullscreen */ true)
+                && activity.mLetterboxUiController.shouldForceRotateForCameraCompat();
     }
 
 
@@ -360,6 +361,13 @@
         return isTreatmentEnabledForActivity(activity, /* mustBeFullscreen */ true);
     }
 
+    boolean isCameraActive(@NonNull ActivityRecord activity, boolean mustBeFullscreen) {
+        // Checking windowing mode on activity level because we don't want to
+        // apply treatment in case of activity embedding.
+        return (!mustBeFullscreen || !activity.inMultiWindowMode())
+                && mCameraIdPackageBiMap.containsPackageName(activity.packageName);
+    }
+
     private boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity,
             boolean mustBeFullscreen) {
         return activity != null && isCameraActive(activity, mustBeFullscreen)
@@ -367,14 +375,7 @@
                 // "locked" and "nosensor" values are often used by camera apps that can't
                 // handle dynamic changes so we shouldn't force rotate them.
                 && activity.getOverrideOrientation() != SCREEN_ORIENTATION_NOSENSOR
-                && activity.getOverrideOrientation() != SCREEN_ORIENTATION_LOCKED;
-    }
-
-    private boolean isCameraActive(@NonNull ActivityRecord activity, boolean mustBeFullscreen) {
-        // Checking windowing mode on activity level because we don't want to
-        // apply treatment in case of activity embedding.
-        return (!mustBeFullscreen || !activity.inMultiWindowMode())
-                && mCameraIdPackageBiMap.containsPackageName(activity.packageName)
+                && activity.getOverrideOrientation() != SCREEN_ORIENTATION_LOCKED
                 && activity.mLetterboxUiController.shouldForceRotateForCameraCompat();
     }
 
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index a961e9a..e327f57 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -29,6 +29,7 @@
 import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
 import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE;
 import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
 import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
 import static android.content.pm.ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION;
 import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR;
@@ -509,6 +510,26 @@
     }
 
     /**
+     * Whether we should apply the min aspect ratio per-app override only when an app is connected
+     * to the camera.
+     * When this override is applied the min aspect ratio given in the app's manifest will be
+     * overridden to the largest enabled aspect ratio treatment unless the app's manifest value
+     * is higher. The treatment will also apply if no value is provided in the manifest.
+     *
+     * <p>This method returns {@code true} when the following conditions are met:
+     * <ul>
+     *     <li>Opt-out component property isn't enabled
+     *     <li>Per-app override is enabled
+     * </ul>
+     */
+    boolean shouldOverrideMinAspectRatioForCamera() {
+        return mActivityRecord.isCameraActive()
+                && mAllowMinAspectRatioOverrideOptProp
+                .shouldEnableWithOptInOverrideAndOptOutProperty(
+                        isCompatChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA));
+    }
+
+    /**
      * Whether we should apply the force resize per-app override. When this override is applied it
      * forces the packages it is applied to to be resizable. It won't change whether the app can be
      * put into multi-windowing mode, but allow the app to resize without going into size-compat
@@ -942,7 +963,8 @@
 
     void recomputeConfigurationForCameraCompatIfNeeded() {
         if (isOverrideOrientationOnlyForCameraEnabled()
-                || isCameraCompatSplitScreenAspectRatioAllowed()) {
+                || isCameraCompatSplitScreenAspectRatioAllowed()
+                || shouldOverrideMinAspectRatioForCamera()) {
             mActivityRecord.recomputeConfiguration();
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java
index 472a82c..d5638e9 100644
--- a/services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java
@@ -57,8 +57,9 @@
 
     private ResolveInfo mResolveInfo1 = new ResolveInfo();
     private ResolveInfo mResolveInfo2 = new ResolveInfo();
-    private final String mTestPkg1 = "testPkg1", mTestPkg2 = "testPkg2", mTestPkg3 = "testPkg3";
-    private final String mMusicFxPkgName = "com.android.musicfx";
+    private final String mTestPkg1 = new String("testPkg1"), mTestPkg2 = new String("testPkg2"),
+            mTestPkg3 = new String("testPkg3"), mTestPkg1Equivalent = new String("testPkg1");
+    private final String mMusicFxPkgName = new String("com.android.musicfx");
     private final int mTestUid1 = 1, mTestUid2 = 2, mTestUid3 = 3, mMusicFxUid = 78;
     private final int mTestSession1 = 11, mTestSession2 = 22, mTestSession3 = 33;
 
@@ -191,7 +192,8 @@
     public void testCloseBroadcastIntent() {
         Log.i(TAG, "running testCloseBroadcastIntent");
 
-        closeSessionWithResList(null, 0, 0, null, mTestSession1, mTestUid1);
+        closeSessionWithResList(null, 0 /* unbind */, 0 /* broadcast */, null /* packageName */,
+                mTestSession1, mTestUid1);
     }
 
     /**
@@ -225,8 +227,10 @@
     public void testBroadcastIntentWithNoPackageAndNoBroadcastReceiver() {
         Log.i(TAG, "running testBroadcastIntentWithNoPackageAndNoBroadcastReceiver");
 
-        openSessionWithResList(mEmptyList, 0, 0, null, mTestSession1, mTestUid1);
-        closeSessionWithResList(mEmptyList, 0, 0, null, mTestSession1, mTestUid1);
+        openSessionWithResList(mEmptyList, 0 /* bind */, 0 /* broadcast */, null /* packageName */,
+                mTestSession1, mTestUid1);
+        closeSessionWithResList(mEmptyList, 0 /* unbind */, 0 /* broadcast */,
+                null /* packageName */, mTestSession1, mTestUid1);
     }
 
     /**
@@ -236,26 +240,10 @@
     public void testBroadcastIntentWithNoPackageAndOneBroadcastReceiver() {
         Log.i(TAG, "running testBroadcastIntentWithNoPackageAndOneBroadcastReceiver");
 
-        int broadcasts = 1, bind = 1, unbind = 1;
-        openSessionWithResList(mSingleList, bind, broadcasts, null, mTestSession1, mTestUid1);
-        broadcasts = broadcasts + 1;
-        closeSessionWithResList(mSingleList, unbind, broadcasts, null, mTestSession1, mTestUid1);
-
-        // repeat with different session ID
-        broadcasts = broadcasts + 1;
-        bind = bind + 1;
-        unbind = unbind + 1;
-        openSessionWithResList(mSingleList, bind, broadcasts, null, mTestSession2, mTestUid1);
-        broadcasts = broadcasts + 1;
-        closeSessionWithResList(mSingleList, unbind, broadcasts, null, mTestSession2, mTestUid1);
-
-        // repeat with different UID
-        broadcasts = broadcasts + 1;
-        bind = bind + 1;
-        unbind = unbind + 1;
-        openSessionWithResList(mSingleList, bind, broadcasts, null, mTestSession1, mTestUid2);
-        broadcasts = broadcasts + 1;
-        closeSessionWithResList(mSingleList, unbind, broadcasts, null, mTestSession1, mTestUid2);
+        openSessionWithResList(mSingleList, 0 /* bind */, 0 /* broadcast */,
+                null /* packageName */, mTestSession1, mTestUid1);
+        closeSessionWithResList(mSingleList, 0 /* unbind */, 0 /* broadcast */,
+                null /* packageName */, mTestSession1, mTestUid1);
     }
 
     /**
@@ -265,8 +253,50 @@
     public void testBroadcastIntentWithNoPackageAndTwoBroadcastReceivers() {
         Log.i(TAG, "running testBroadcastIntentWithNoPackageAndTwoBroadcastReceivers");
 
-        openSessionWithResList(mDoubleList, 1, 1, null, mTestSession1, mTestUid1);
-        closeSessionWithResList(mDoubleList, 1, 2, null, mTestSession1, mTestUid1);
+        openSessionWithResList(mDoubleList, 0 /* bind */, 0 /* broadcast */,
+                null /* packageName */, mTestSession1, mTestUid1);
+        closeSessionWithResList(mDoubleList, 0 /* bind */, 0 /* broadcast */,
+                null /* packageName */, mTestSession1, mTestUid1);
+    }
+
+    @Test
+    public void testBroadcastIntentWithPackageAndOneBroadcastReceiver() {
+        Log.i(TAG, "running testBroadcastIntentWithPackageAndOneBroadcastReceiver");
+
+        int broadcasts = 1, bind = 1, unbind = 1;
+        openSessionWithResList(mSingleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+
+        broadcasts = broadcasts + 1;
+        closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg1, mTestSession1,
+                mTestUid1);
+
+        // repeat with different session ID
+        broadcasts = broadcasts + 1;
+        bind = bind + 1;
+        unbind = unbind + 1;
+        openSessionWithResList(mSingleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid1);
+        broadcasts = broadcasts + 1;
+        closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg2, mTestSession2,
+                mTestUid1);
+
+        // repeat with different UID
+        broadcasts = broadcasts + 1;
+        bind = bind + 1;
+        unbind = unbind + 1;
+        openSessionWithResList(mSingleList, bind, broadcasts, mTestPkg3, mTestSession1, mTestUid2);
+        broadcasts = broadcasts + 1;
+        closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg3, mTestSession1,
+                mTestUid2);
+    }
+
+    @Test
+    public void testBroadcastIntentWithPackageAndTwoBroadcastReceivers() {
+        Log.i(TAG, "running testBroadcastIntentWithPackageAndTwoBroadcastReceivers");
+
+        openSessionWithResList(mDoubleList, 1 /* bind */, 1 /* broadcast */,
+                mTestPkg1 /* packageName */, mTestSession1, mTestUid1);
+        closeSessionWithResList(mDoubleList, 1 /* unbind */, 2 /* broadcast */,
+                mTestPkg1 /* packageName */, mTestSession1, mTestUid1);
     }
 
     /**
@@ -639,4 +669,18 @@
         unbind = unbind + 1;
         sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid3, unbind, broadcasts);
     }
+
+    /**
+     * Test audio session open/close with same package name value but different String object.
+     */
+    @Test
+    public void testSessionOpenCloseWithSamePackageNameValueButDiffObject() {
+        Log.i(TAG, "running testSessionOpenCloseWithSamePackageNameValueButDiffObject");
+        int broadcasts = 1;
+        openSessionWithResList(mSingleList, 1 /* bind */, broadcasts, mTestPkg1, mTestSession1,
+                mTestUid1);
+        closeSessionWithResList(mSingleList, 1 /* unbind */, broadcasts + 1, mTestPkg1Equivalent,
+                mTestSession1, mTestUid1);
+    }
+
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 7a77392..3a35563 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -45,6 +45,7 @@
 import android.hardware.biometrics.common.AuthenticateReason;
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.common.OperationState;
 import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.fingerprint.Fingerprint;
@@ -411,8 +412,8 @@
         verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
                 mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
 
-        mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
-                .getValue().toAidlContext());
+        final OperationContextExt operationContext = mOperationContextCaptor.getValue();
+        mStartHalConsumerCaptor.getValue().accept(operationContext.toAidlContext());
         final ArgumentCaptor<OperationContext> captor =
                 ArgumentCaptor.forClass(OperationContext.class);
 
@@ -420,16 +421,20 @@
 
         OperationContext opContext = captor.getValue();
 
-        assertThat(opContext).isSameInstanceAs(
-                mOperationContextCaptor.getValue().toAidlContext());
+        assertThat(opContext).isSameInstanceAs(operationContext.toAidlContext());
 
+        opContext.operationState = new OperationState();
+        opContext.operationState.setFingerprintOperationState(
+                new OperationState.FingerprintOperationState());
         mContextInjector.getValue().accept(opContext);
 
         verify(mHal).onContextChanged(same(opContext));
+        verify(mHal, times(2)).setIgnoreDisplayTouches(
+                opContext.operationState.getFingerprintOperationState().isHardwareIgnoringTouches);
 
         client.stopHalOperation();
 
-        verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
+        verify(mBiometricContext).unsubscribe(same(operationContext));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java b/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
index 669eedf..dabf531 100644
--- a/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
@@ -31,11 +31,13 @@
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
 import android.os.Handler;
 import android.os.PowerManager;
+import android.os.RemoteException;
 import android.os.test.TestLooper;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableContext;
 import android.testing.TestableResources;
 import android.view.Window;
+import android.view.accessibility.AccessibilityManager;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -48,7 +50,8 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 import java.util.List;
 
@@ -72,9 +75,15 @@
     private static final Integer AUTO_DISMISS_DIALOG = 500;
 
     @Rule
+    public MockitoRule rule = MockitoJUnit.rule();
+
+    @Rule
     public TestableContext mContext =
             new TestableContext(InstrumentationRegistry.getContext(), null);
 
+    private final AccessibilityManager mAccessibilityManager =
+            mContext.getSystemService(AccessibilityManager.class);
+
     @Mock
     private PackageManager mPackageManager;
     @Mock
@@ -89,9 +98,8 @@
     private BiometricStateListener mBiometricStateListener;
 
     @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-
+    public void setup() throws RemoteException {
+        disableAccessibility();
         mContext.addMockSystemService(PackageManager.class, mPackageManager);
         mContext.addMockSystemService(FingerprintManager.class, mFingerprintManager);
         TestableResources resources = mContext.getOrCreateTestableResources();
@@ -192,9 +200,8 @@
     }
 
     @Test
-    public void dialogDismissesAfterTime() throws Exception {
+    public void dialogDismissesAfterTime_accessibilityDisabled() throws Exception {
         setupWithSensor(true /* hasSfps */, true /* initialized */);
-
         setBiometricState(BiometricStateListener.STATE_ENROLLING);
         when(mDialog.isShowing()).thenReturn(true);
         assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
@@ -207,9 +214,23 @@
     }
 
     @Test
-    public void dialogDoesNotDismissOnSensorTouch() throws Exception {
+    public void dialogDoesNotDismissAfterTime_accessibilityEnabled() throws Exception {
+        enableAccessibility();
         setupWithSensor(true /* hasSfps */, true /* initialized */);
+        setBiometricState(BiometricStateListener.STATE_ENROLLING);
+        when(mDialog.isShowing()).thenReturn(true);
+        assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
 
+        mLooper.dispatchAll();
+        verify(mDialog).show();
+        mLooper.moveTimeForward(AUTO_DISMISS_DIALOG);
+        mLooper.dispatchAll();
+        verify(mDialog, never()).dismiss();
+    }
+
+    @Test
+    public void dialogDoesNotDismissOnSensorTouch_accessibilityDisabled() throws Exception {
+        setupWithSensor(true /* hasSfps */, true /* initialized */);
         setBiometricState(BiometricStateListener.STATE_ENROLLING);
         when(mDialog.isShowing()).thenReturn(true);
         assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
@@ -218,12 +239,26 @@
         verify(mDialog).show();
 
         mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
-        mLooper.moveTimeForward(AUTO_DISMISS_DIALOG - 1);
         mLooper.dispatchAll();
-
         verify(mDialog, never()).dismiss();
     }
 
+    @Test
+    public void dialogDismissesOnSensorTouch_accessibilityEnabled() throws Exception {
+        enableAccessibility();
+        setupWithSensor(true /* hasSfps */, true /* initialized */);
+        setBiometricState(BiometricStateListener.STATE_ENROLLING);
+        when(mDialog.isShowing()).thenReturn(true);
+        assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
+
+        mLooper.dispatchAll();
+        verify(mDialog).show();
+
+        mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
+        mLooper.dispatchAll();
+        verify(mDialog).dismiss();
+    }
+
     private void setBiometricState(@BiometricStateListener.State int newState) {
         if (mBiometricStateListener != null) {
             mBiometricStateListener.onStateChanged(newState);
@@ -231,6 +266,20 @@
         }
     }
 
+    private void enableAccessibility() throws RemoteException {
+        if (mAccessibilityManager != null) {
+            mAccessibilityManager.getClient().setState(1);
+            mLooper.dispatchAll();
+        }
+    }
+
+    private void disableAccessibility() throws RemoteException {
+        if (mAccessibilityManager != null) {
+            mAccessibilityManager.getClient().setState(0);
+            mLooper.dispatchAll();
+        }
+    }
+
     private void setupWithSensor(boolean hasSfps, boolean initialized) throws Exception {
         when(mPackageManager.hasSystemFeature(eq(PackageManager.FEATURE_FINGERPRINT)))
                 .thenReturn(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 09e7b91..d66473d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -116,6 +116,7 @@
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.when;
 
 import android.app.ActivityOptions;
 import android.app.AppOpsManager;
@@ -3501,6 +3502,23 @@
     }
 
     @Test
+    public void testIsCameraActive() {
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+        final DisplayRotationCompatPolicy displayRotationCompatPolicy = mock(
+                DisplayRotationCompatPolicy.class);
+        when(mDisplayContent.getDisplayRotationCompatPolicy()).thenReturn(
+                displayRotationCompatPolicy);
+
+        when(displayRotationCompatPolicy.isCameraActive(any(ActivityRecord.class),
+                anyBoolean())).thenReturn(false);
+        assertFalse(app.mActivityRecord.isCameraActive());
+
+        when(displayRotationCompatPolicy.isCameraActive(any(ActivityRecord.class),
+                anyBoolean())).thenReturn(true);
+        assertTrue(app.mActivityRecord.isCameraActive());
+    }
+
+    @Test
     public void testUpdateCameraCompatStateFromUser_clickedOnDismiss() throws RemoteException {
         final ActivityRecord activity = createActivityWithTask();
         // Mock a flag being enabled.
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index 0dd0239..397a9a1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -38,6 +38,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -534,6 +535,42 @@
         assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
     }
 
+    @Test
+    public void testIsCameraActiveWhenCallbackInvokedNoMultiWindow_returnTrue() {
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        assertTrue(
+                mDisplayRotationCompatPolicy.isCameraActive(mActivity, /* mustBeFullscreen*/ true));
+    }
+
+    @Test
+    public void testIsCameraActiveWhenCallbackNotInvokedNoMultiWindow_returnFalse() {
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+        assertFalse(
+                mDisplayRotationCompatPolicy.isCameraActive(mActivity, /* mustBeFullscreen*/ true));
+    }
+
+    @Test
+    public void testIsCameraActiveWhenCallbackNotInvokedMultiWindow_returnFalse() {
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+        when(mActivity.inMultiWindowMode()).thenReturn(true);
+
+        assertFalse(
+                mDisplayRotationCompatPolicy.isCameraActive(mActivity, /* mustBeFullscreen*/ true));
+    }
+
+    @Test
+    public void testIsCameraActiveWhenCallbackInvokedMultiWindow_returnFalse() {
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+        when(mActivity.inMultiWindowMode()).thenReturn(true);
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        assertFalse(
+                mDisplayRotationCompatPolicy.isCameraActive(mActivity, /* mustBeFullscreen*/ true));
+    }
+
     private void configureActivity(@ScreenOrientation int activityOrientation) {
         configureActivityAndDisplay(activityOrientation, ORIENTATION_PORTRAIT);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 4e4bbfe..b717d98 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -28,6 +28,7 @@
 import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
 import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE;
 import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
 import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
 import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR;
 import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT;
@@ -71,6 +72,8 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
 import android.annotation.Nullable;
@@ -101,7 +104,7 @@
 import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 
- /**
+/**
  * Test class for {@link LetterboxUiControllerTest}.
  *
  * Build/Install/Run:
@@ -1044,6 +1047,39 @@
         assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
     }
 
+    @Test
+    public void testRecomputeConfigurationForCameraCompatIfNeeded() {
+        spyOn(mController);
+        doReturn(false).when(mController).isOverrideOrientationOnlyForCameraEnabled();
+        doReturn(false).when(mController).isCameraCompatSplitScreenAspectRatioAllowed();
+        doReturn(false).when(mController).shouldOverrideMinAspectRatioForCamera();
+        clearInvocations(mActivity);
+
+        mController.recomputeConfigurationForCameraCompatIfNeeded();
+
+        verify(mActivity, never()).recomputeConfiguration();
+
+        // isOverrideOrientationOnlyForCameraEnabled
+        doReturn(true).when(mController).isOverrideOrientationOnlyForCameraEnabled();
+        clearInvocations(mActivity);
+        mController.recomputeConfigurationForCameraCompatIfNeeded();
+        verify(mActivity).recomputeConfiguration();
+
+        // isCameraCompatSplitScreenAspectRatioAllowed
+        doReturn(false).when(mController).isOverrideOrientationOnlyForCameraEnabled();
+        doReturn(true).when(mController).isCameraCompatSplitScreenAspectRatioAllowed();
+        clearInvocations(mActivity);
+        mController.recomputeConfigurationForCameraCompatIfNeeded();
+        verify(mActivity).recomputeConfiguration();
+
+        // shouldOverrideMinAspectRatioForCamera
+        doReturn(false).when(mController).isCameraCompatSplitScreenAspectRatioAllowed();
+        doReturn(true).when(mController).shouldOverrideMinAspectRatioForCamera();
+        clearInvocations(mActivity);
+        mController.recomputeConfigurationForCameraCompatIfNeeded();
+        verify(mActivity).recomputeConfiguration();
+    }
+
     private void prepareActivityForShouldApplyUserMinAspectRatioOverride(
             boolean orientationRequest) {
         spyOn(mController);
@@ -1238,6 +1274,78 @@
     }
 
     @Test
+    @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+    public void shouldOverrideMinAspectRatioForCamera_overrideEnabled_returnsTrue() {
+        doReturn(true).when(mActivity).isCameraActive();
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertTrue(mController.shouldOverrideMinAspectRatioForCamera());
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+    public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideEnabled_returnsTrue()
+            throws Exception {
+        doReturn(true).when(mActivity).isCameraActive();
+        mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertTrue(mController.shouldOverrideMinAspectRatioForCamera());
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+    public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideEnabled_returnsFalse()
+            throws Exception {
+        doReturn(false).when(mActivity).isCameraActive();
+        mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertFalse(mController.shouldOverrideMinAspectRatioForCamera());
+    }
+
+    @Test
+    @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+    public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideDisabled_returnsFalse()
+            throws Exception {
+        doReturn(true).when(mActivity).isCameraActive();
+        mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertFalse(mController.shouldOverrideMinAspectRatioForCamera());
+    }
+
+    @Test
+    @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+    public void shouldOverrideMinAspectRatioForCamera_overrideDisabled_returnsFalse() {
+        doReturn(true).when(mActivity).isCameraActive();
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertFalse(mController.shouldOverrideMinAspectRatioForCamera());
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+    public void shouldOverrideMinAspectRatioForCamera_propertyFalse_overrideEnabled_returnsFalse()
+            throws Exception {
+        mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false);
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertFalse(mController.shouldOverrideMinAspectRatioForCamera());
+    }
+
+    @Test
+    @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+    public void shouldOverrideMinAspectRatioForCamera_propertyFalse_noOverride_returnsFalse()
+            throws Exception {
+        mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false);
+        doReturn(true).when(mActivity).isCameraActive();
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertFalse(mController.shouldOverrideMinAspectRatioForCamera());
+    }
+
+    @Test
     @EnableCompatChanges({FORCE_RESIZE_APP})
     public void testshouldOverrideForceResizeApp_overrideEnabled_returnsTrue() {
         mController = new LetterboxUiController(mWm, mActivity);
diff --git a/telephony/java/android/telephony/satellite/ISatelliteCommunicationAllowedStateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteCommunicationAllowedStateCallback.aidl
new file mode 100644
index 0000000..a7eda48
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/ISatelliteCommunicationAllowedStateCallback.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+/**
+ * Interface for satellite communication allowed state callback.
+ * @hide
+ */
+oneway interface ISatelliteCommunicationAllowedStateCallback {
+    /**
+     * Telephony does not guarantee that whenever there is a change in communication allowed
+     * state, this API will be called. Telephony does its best to detect the changes and notify
+     * its listners accordingly.
+     *
+     * @param allowed whether satellite communication state or not
+     */
+    void onSatelliteCommunicationAllowedStateChanged(in boolean isAllowed);
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteCommunicationAllowedStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteCommunicationAllowedStateCallback.java
new file mode 100644
index 0000000..1a87020
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteCommunicationAllowedStateCallback.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+
+import com.android.internal.telephony.flags.Flags;
+
+
+/**
+ * A callback class for monitoring satellite communication allowed state changed events.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+public interface SatelliteCommunicationAllowedStateCallback {
+
+    /**
+     * Telephony does not guarantee that whenever there is a change in communication allowed state,
+     * this API will be called. Telephony does its best to detect the changes and notify its
+     * listeners accordingly.
+     *
+     * @param isAllowed {@code true} means satellite allow state is changed,
+     *                  {@code false} satellite allow state is not changed
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    void onSatelliteCommunicationAllowedStateChanged(boolean isAllowed);
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 40ad312..0bb5fd5 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -91,6 +91,11 @@
             ISatelliteSupportedStateCallback> sSatelliteSupportedStateCallbackMap =
             new ConcurrentHashMap<>();
 
+    private static final ConcurrentHashMap<SatelliteCommunicationAllowedStateCallback,
+            ISatelliteCommunicationAllowedStateCallback>
+            sSatelliteCommunicationAllowedStateCallbackMap =
+            new ConcurrentHashMap<>();
+
     private final int mSubId;
 
     /**
@@ -2393,7 +2398,89 @@
         }
     }
 
-    @Nullable private static ITelephony getITelephony() {
+    /**
+     * Registers for the satellite communication allowed state changed.
+     *
+     * @param executor The executor on which the callback will be called.
+     * @param callback The callback to handle satellite communication allowed state changed event.
+     * @return The {@link SatelliteResult} result of the operation.
+     * @throws SecurityException     if the caller doesn't have required permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @SatelliteResult
+    public int registerForCommunicationAllowedStateChanged(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull SatelliteCommunicationAllowedStateCallback callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                ISatelliteCommunicationAllowedStateCallback internalCallback =
+                        new ISatelliteCommunicationAllowedStateCallback.Stub() {
+                            @Override
+                            public void onSatelliteCommunicationAllowedStateChanged(
+                                    boolean isAllowed) {
+                                executor.execute(() -> Binder.withCleanCallingIdentity(
+                                        () -> callback.onSatelliteCommunicationAllowedStateChanged(
+                                                isAllowed)));
+                            }
+                        };
+                sSatelliteCommunicationAllowedStateCallbackMap.put(callback, internalCallback);
+                return telephony.registerForCommunicationAllowedStateChanged(
+                        mSubId, internalCallback);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("registerForCommunicationAllowedStateChanged() RemoteException: " + ex);
+            ex.rethrowAsRuntimeException();
+        }
+        return SATELLITE_RESULT_REQUEST_FAILED;
+    }
+
+    /**
+     * Unregisters for the satellite communication allowed state changed.
+     * If callback was not registered before, the request will be ignored.
+     *
+     * @param callback The callback that was passed to
+     *                 {@link #registerForCommunicationAllowedStateChanged(Executor,
+     *                 SatelliteCommunicationAllowedStateCallback)}
+     * @throws SecurityException     if the caller doesn't have required permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public void unregisterForCommunicationAllowedStateChanged(
+            @NonNull SatelliteCommunicationAllowedStateCallback callback) {
+        Objects.requireNonNull(callback);
+        ISatelliteCommunicationAllowedStateCallback internalCallback =
+                sSatelliteCommunicationAllowedStateCallbackMap.remove(callback);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                if (internalCallback != null) {
+                    telephony.unregisterForCommunicationAllowedStateChanged(mSubId,
+                            internalCallback);
+                } else {
+                    loge("unregisterForCommunicationAllowedStateChanged: No internal callback.");
+                }
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("unregisterForCommunicationAllowedStateChanged() RemoteException: " + ex);
+            ex.rethrowAsRuntimeException();
+        }
+    }
+
+    @Nullable
+    private static ITelephony getITelephony() {
         ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
                 .getTelephonyServiceManager()
                 .getTelephonyServiceRegisterer()
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index f25fc36..f591f40 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -69,6 +69,7 @@
 import android.telephony.ims.aidl.IRcsConfigCallback;
 import android.telephony.satellite.INtnSignalStrengthCallback;
 import android.telephony.satellite.ISatelliteCapabilitiesCallback;
+import android.telephony.satellite.ISatelliteCommunicationAllowedStateCallback;
 import android.telephony.satellite.ISatelliteDatagramCallback;
 import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
@@ -3341,4 +3342,29 @@
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
     void unregisterForSatelliteSupportedStateChanged(int subId,
             in ISatelliteSupportedStateCallback callback);
+
+    /**
+     * Registers for satellite communication allowed state changed.
+     *
+     * @param subId The subId of the subscription to register for communication allowed state.
+     * @param callback The callback to handle the communication allowed state changed event.
+     *
+     * @return The {@link SatelliteError} result of the operation.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    int registerForCommunicationAllowedStateChanged(int subId,
+            in ISatelliteCommunicationAllowedStateCallback callback);
+
+    /**
+     * Unregisters for satellite communication allowed state.
+     * If callback was not registered before, the request will be ignored.
+     *
+     * @param subId The subId of the subscription to unregister for supported state changed.
+     * @param callback The callback that was passed to registerForCommunicationAllowedStateChanged.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    void unregisterForCommunicationAllowedStateChanged(int subId,
+            in ISatelliteCommunicationAllowedStateCallback callback);
 }