Merge "Extra Dim QS Animated Icon" into tm-qpr-dev
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index c3b6df1..17d0b51 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5218,7 +5218,7 @@
     <!-- Vertical position of a center of the letterboxed app window.
         0 corresponds to the upper side of the screen and 1 to the lower side. If given value < 0
         or > 1, it is ignored and central position is used (0.5). -->
-    <item name="config_letterboxVerticalPositionMultiplier" format="float" type="dimen">0.5</item>
+    <item name="config_letterboxVerticalPositionMultiplier" format="float" type="dimen">0.0</item>
 
     <!-- Whether horizontal reachability repositioning is allowed for letterboxed fullscreen apps.
     -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index c39602032..7c3c14e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -55,6 +55,8 @@
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
 import com.android.wm.shell.compatui.CompatUIController;
+import com.android.wm.shell.desktopmode.DesktopMode;
+import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
 import com.android.wm.shell.draganddrop.DragAndDropController;
@@ -477,11 +479,12 @@
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
             TaskStackListenerImpl taskStackListener,
+            Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
             @ShellMainThread ShellExecutor mainExecutor
     ) {
         return Optional.ofNullable(
                 RecentTasksController.create(context, shellInit, shellCommandHandler,
-                        taskStackListener, mainExecutor));
+                        taskStackListener, desktopModeTaskRepository, mainExecutor));
     }
 
     //
@@ -666,6 +669,24 @@
     }
 
     //
+    // Desktop mode (optional feature)
+    //
+
+    @BindsOptionalOf
+    @DynamicOverride
+    abstract DesktopModeTaskRepository optionalDesktopModeTaskRepository();
+
+    @WMSingleton
+    @Provides
+    static Optional<DesktopModeTaskRepository> providesDesktopModeTaskRepository(
+            @DynamicOverride Optional<DesktopModeTaskRepository> desktopModeTaskRepository) {
+        if (DesktopMode.IS_SUPPORTED) {
+            return desktopModeTaskRepository;
+        }
+        return Optional.empty();
+    }
+
+    //
     // Misc
     //
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 247ba60..27d3e35 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -51,6 +51,7 @@
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.desktopmode.DesktopMode;
 import com.android.wm.shell.desktopmode.DesktopModeController;
+import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.freeform.FreeformComponents;
 import com.android.wm.shell.freeform.FreeformTaskListener;
@@ -220,14 +221,14 @@
             Context context,
             ShellInit shellInit,
             ShellTaskOrganizer shellTaskOrganizer,
-            Optional<RecentTasksController> recentTasksController,
+            Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
             WindowDecorViewModel<?> windowDecorViewModel) {
         // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
         //                    override for this controller from the base module
         ShellInit init = FreeformComponents.isFreeformEnabled(context)
                 ? shellInit
                 : null;
-        return new FreeformTaskListener<>(init, shellTaskOrganizer, recentTasksController,
+        return new FreeformTaskListener<>(init, shellTaskOrganizer, desktopModeTaskRepository,
                 windowDecorViewModel);
     }
 
@@ -610,6 +611,13 @@
         }
     }
 
+    @WMSingleton
+    @Provides
+    @DynamicOverride
+    static DesktopModeTaskRepository provideDesktopModeTaskRepository() {
+        return new DesktopModeTaskRepository();
+    }
+
     //
     // Misc
     //
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
new file mode 100644
index 0000000..988601c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.util.ArraySet
+
+/**
+ * Keeps track of task data related to desktop mode.
+ */
+class DesktopModeTaskRepository {
+
+    /**
+     * Set of task ids that are marked as active in desktop mode.
+     * Active tasks in desktop mode are freeform tasks that are visible or have been visible after
+     * desktop mode was activated.
+     * Task gets removed from this list when it vanishes. Or when desktop mode is turned off.
+     */
+    private val activeTasks = ArraySet<Int>()
+    private val listeners = ArraySet<Listener>()
+
+    /**
+     * Add a [Listener] to be notified of updates to the repository.
+     */
+    fun addListener(listener: Listener) {
+        listeners.add(listener)
+    }
+
+    /**
+     * Remove a previously registered [Listener]
+     */
+    fun removeListener(listener: Listener) {
+        listeners.remove(listener)
+    }
+
+    /**
+     * Mark a task with given [taskId] as active.
+     */
+    fun addActiveTask(taskId: Int) {
+        val added = activeTasks.add(taskId)
+        if (added) {
+            listeners.onEach { it.onActiveTasksChanged() }
+        }
+    }
+
+    /**
+     * Remove task with given [taskId] from active tasks.
+     */
+    fun removeActiveTask(taskId: Int) {
+        val removed = activeTasks.remove(taskId)
+        if (removed) {
+            listeners.onEach { it.onActiveTasksChanged() }
+        }
+    }
+
+    /**
+     * Check if a task with the given [taskId] was marked as an active task
+     */
+    fun isActiveTask(taskId: Int): Boolean {
+        return activeTasks.contains(taskId)
+    }
+
+    /**
+     * Get a set of the active tasks
+     */
+    fun getActiveTasks(): ArraySet<Int> {
+        return ArraySet(activeTasks)
+    }
+
+    /**
+     * Defines interface for classes that can listen to changes in repository state.
+     */
+    interface Listener {
+        fun onActiveTasksChanged()
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 1baac71..ac95d4b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -29,8 +29,8 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.desktopmode.DesktopMode;
+import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -49,7 +49,7 @@
     private static final String TAG = "FreeformTaskListener";
 
     private final ShellTaskOrganizer mShellTaskOrganizer;
-    private final Optional<RecentTasksController> mRecentTasksOptional;
+    private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository;
     private final WindowDecorViewModel<T> mWindowDecorationViewModel;
 
     private final SparseArray<State<T>> mTasks = new SparseArray<>();
@@ -64,11 +64,11 @@
     public FreeformTaskListener(
             ShellInit shellInit,
             ShellTaskOrganizer shellTaskOrganizer,
-            Optional<RecentTasksController> recentTasksController,
+            Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
             WindowDecorViewModel<T> windowDecorationViewModel) {
         mShellTaskOrganizer = shellTaskOrganizer;
         mWindowDecorationViewModel = windowDecorationViewModel;
-        mRecentTasksOptional = recentTasksController;
+        mDesktopModeTaskRepository = desktopModeTaskRepository;
         if (shellInit != null) {
             shellInit.addInitCallback(this::onInit, this);
         }
@@ -93,7 +93,7 @@
         if (DesktopMode.IS_SUPPORTED && taskInfo.isVisible) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
                     "Adding active freeform task: #%d", taskInfo.taskId);
-            mRecentTasksOptional.ifPresent(rt -> rt.addActiveFreeformTask(taskInfo.taskId));
+            mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTask(taskInfo.taskId));
         }
     }
 
@@ -126,7 +126,7 @@
         if (DesktopMode.IS_SUPPORTED) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
                     "Removing active freeform task: #%d", taskInfo.taskId);
-            mRecentTasksOptional.ifPresent(rt -> rt.removeActiveFreeformTask(taskInfo.taskId));
+            mDesktopModeTaskRepository.ifPresent(it -> it.removeActiveTask(taskInfo.taskId));
         }
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -154,7 +154,7 @@
             if (taskInfo.isVisible) {
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
                         "Adding active freeform task: #%d", taskInfo.taskId);
-                mRecentTasksOptional.ifPresent(rt -> rt.addActiveFreeformTask(taskInfo.taskId));
+                mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTask(taskInfo.taskId));
             }
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 27bc1a1..ff4b2ed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -44,6 +44,7 @@
 import com.android.wm.shell.common.annotations.ExternalThread;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.desktopmode.DesktopMode;
+import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellInit;
@@ -53,19 +54,20 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 /**
  * Manages the recent task list from the system, caching it as necessary.
  */
 public class RecentTasksController implements TaskStackListenerCallback,
-        RemoteCallable<RecentTasksController> {
+        RemoteCallable<RecentTasksController>, DesktopModeTaskRepository.Listener {
     private static final String TAG = RecentTasksController.class.getSimpleName();
 
     private final Context mContext;
     private final ShellCommandHandler mShellCommandHandler;
+    private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository;
     private final ShellExecutor mMainExecutor;
     private final TaskStackListenerImpl mTaskStackListener;
     private final RecentTasks mImpl = new RecentTasksImpl();
@@ -84,15 +86,6 @@
     private final Map<Integer, SplitBounds> mTaskSplitBoundsMap = new HashMap<>();
 
     /**
-     * Set of taskId's that have been launched in freeform mode.
-     * This includes tasks that are currently running, visible and in freeform mode. And also
-     * includes tasks that are running in the background, are no longer visible, but at some point
-     * were visible to the user.
-     * This is used to decide which freeform apps belong to the user's desktop.
-     */
-    private final HashSet<Integer> mActiveFreeformTasks = new HashSet<>();
-
-    /**
      * Creates {@link RecentTasksController}, returns {@code null} if the feature is not
      * supported.
      */
@@ -102,24 +95,27 @@
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
             TaskStackListenerImpl taskStackListener,
+            Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
             @ShellMainThread ShellExecutor mainExecutor
     ) {
         if (!context.getResources().getBoolean(com.android.internal.R.bool.config_hasRecents)) {
             return null;
         }
         return new RecentTasksController(context, shellInit, shellCommandHandler, taskStackListener,
-                mainExecutor);
+                desktopModeTaskRepository, mainExecutor);
     }
 
     RecentTasksController(Context context,
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
             TaskStackListenerImpl taskStackListener,
+            Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
             ShellExecutor mainExecutor) {
         mContext = context;
         mShellCommandHandler = shellCommandHandler;
         mIsDesktopMode = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
         mTaskStackListener = taskStackListener;
+        mDesktopModeTaskRepository = desktopModeTaskRepository;
         mMainExecutor = mainExecutor;
         shellInit.addInitCallback(this::onInit, this);
     }
@@ -131,6 +127,7 @@
     private void onInit() {
         mShellCommandHandler.addDumpCallback(this::dump, this);
         mTaskStackListener.addListener(this);
+        mDesktopModeTaskRepository.ifPresent(it -> it.addListener(this));
     }
 
     /**
@@ -217,19 +214,8 @@
         notifyRecentTasksChanged();
     }
 
-    /**
-     * Mark a task with given {@code taskId} as active in freeform
-     */
-    public void addActiveFreeformTask(int taskId) {
-        mActiveFreeformTasks.add(taskId);
-        notifyRecentTasksChanged();
-    }
-
-    /**
-     * Remove task with given {@code taskId} from active freeform tasks
-     */
-    public void removeActiveFreeformTask(int taskId) {
-        mActiveFreeformTasks.remove(taskId);
+    @Override
+    public void onActiveTasksChanged() {
         notifyRecentTasksChanged();
     }
 
@@ -312,7 +298,8 @@
                 continue;
             }
 
-            if (desktopModeActive && mActiveFreeformTasks.contains(taskInfo.taskId)) {
+            if (desktopModeActive && mDesktopModeTaskRepository.isPresent()
+                    && mDesktopModeTaskRepository.get().isActiveTask(taskInfo.taskId)) {
                 // Freeform tasks will be added as a separate entry
                 freeformTasks.add(taskInfo);
                 continue;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
new file mode 100644
index 0000000..9b28d11
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopModeTaskRepositoryTest : ShellTestCase() {
+
+    private lateinit var repo: DesktopModeTaskRepository
+
+    @Before
+    fun setUp() {
+        repo = DesktopModeTaskRepository()
+    }
+
+    @Test
+    fun addActiveTask_listenerNotifiedAndTaskIsActive() {
+        val listener = TestListener()
+        repo.addListener(listener)
+
+        repo.addActiveTask(1)
+        assertThat(listener.activeTaskChangedCalls).isEqualTo(1)
+        assertThat(repo.isActiveTask(1)).isTrue()
+    }
+
+    @Test
+    fun addActiveTask_sameTaskDoesNotNotify() {
+        val listener = TestListener()
+        repo.addListener(listener)
+
+        repo.addActiveTask(1)
+        repo.addActiveTask(1)
+        assertThat(listener.activeTaskChangedCalls).isEqualTo(1)
+    }
+
+    @Test
+    fun addActiveTask_multipleTasksAddedNotifiesForEach() {
+        val listener = TestListener()
+        repo.addListener(listener)
+
+        repo.addActiveTask(1)
+        repo.addActiveTask(2)
+        assertThat(listener.activeTaskChangedCalls).isEqualTo(2)
+    }
+
+    @Test
+    fun removeActiveTask_listenerNotifiedAndTaskNotActive() {
+        val listener = TestListener()
+        repo.addListener(listener)
+
+        repo.addActiveTask(1)
+        repo.removeActiveTask(1)
+        // Notify once for add and once for remove
+        assertThat(listener.activeTaskChangedCalls).isEqualTo(2)
+        assertThat(repo.isActiveTask(1)).isFalse()
+    }
+
+    @Test
+    fun removeActiveTask_removeNotExistingTaskDoesNotNotify() {
+        val listener = TestListener()
+        repo.addListener(listener)
+        repo.removeActiveTask(99)
+        assertThat(listener.activeTaskChangedCalls).isEqualTo(0)
+    }
+
+    @Test
+    fun isActiveTask_notExistingTaskReturnsFalse() {
+        assertThat(repo.isActiveTask(99)).isFalse()
+    }
+
+    class TestListener : DesktopModeTaskRepository.Listener {
+        var activeTaskChangedCalls = 0
+        override fun onActiveTasksChanged() {
+            activeTaskChangedCalls++
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index e9a1e25..cadfeb0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -55,6 +55,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.desktopmode.DesktopMode;
+import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.util.GroupedRecentTaskInfo;
@@ -82,6 +83,8 @@
     private TaskStackListenerImpl mTaskStackListener;
     @Mock
     private ShellCommandHandler mShellCommandHandler;
+    @Mock
+    private DesktopModeTaskRepository mDesktopModeTaskRepository;
 
     private ShellTaskOrganizer mShellTaskOrganizer;
     private RecentTasksController mRecentTasksController;
@@ -94,7 +97,8 @@
         when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
         mShellInit = spy(new ShellInit(mMainExecutor));
         mRecentTasksController = spy(new RecentTasksController(mContext, mShellInit,
-                mShellCommandHandler, mTaskStackListener, mMainExecutor));
+                mShellCommandHandler, mTaskStackListener, Optional.of(mDesktopModeTaskRepository),
+                mMainExecutor));
         mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
                 null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController),
                 mMainExecutor);
@@ -195,8 +199,8 @@
         ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
         setRawList(t1, t2, t3, t4);
 
-        mRecentTasksController.addActiveFreeformTask(1);
-        mRecentTasksController.addActiveFreeformTask(3);
+        when(mDesktopModeTaskRepository.isActiveTask(1)).thenReturn(true);
+        when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
 
         ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
                 MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index a5f3df9..7927c5d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -81,8 +81,10 @@
     private int mDeviceMode;
     private long mHiSyncId;
     private int mGroupId;
+
     // Need this since there is no method for getting RSSI
     short mRssi;
+
     // mProfiles and mRemovedProfiles does not do swap() between main and sub device. It is
     // because current sub device is only for HearingAid and its profile is the same.
     private final Collection<LocalBluetoothProfile> mProfiles = new CopyOnWriteArrayList<>();
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java
new file mode 100644
index 0000000..feb5e0b
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.HashMap;
+
+/** Utils class to report hearing aid metrics to statsd */
+public final class HearingAidStatsLogUtils {
+
+    private static final String TAG = "HearingAidStatsLogUtils";
+    private static final HashMap<String, Integer> sDeviceAddressToBondEntryMap = new HashMap<>();
+
+    /**
+     * Sets the mapping from hearing aid device to the bond entry where this device starts it's
+     * bonding(connecting) process.
+     *
+     * @param bondEntry The entry page id where the bonding process starts
+     * @param device The bonding(connecting) hearing aid device
+     */
+    public static void setBondEntryForDevice(int bondEntry, CachedBluetoothDevice device) {
+        sDeviceAddressToBondEntryMap.put(device.getAddress(), bondEntry);
+    }
+
+    /**
+     * Logs hearing aid device information to westworld, including device mode, device side, and
+     * entry page id where the binding(connecting) process starts.
+     *
+     * Only logs the info once after hearing aid is bonded(connected). Clears the map entry of this
+     * device when logging is completed.
+     *
+     * @param device The bonded(connected) hearing aid device
+     */
+    public static void logHearingAidInfo(CachedBluetoothDevice device) {
+        final String deviceAddress = device.getAddress();
+        if (sDeviceAddressToBondEntryMap.containsKey(deviceAddress)) {
+            final int bondEntry = sDeviceAddressToBondEntryMap.getOrDefault(deviceAddress, -1);
+            final int deviceMode = device.getDeviceMode();
+            final int deviceSide = device.getDeviceSide();
+            FrameworkStatsLog.write(FrameworkStatsLog.HEARING_AID_INFO_REPORTED, deviceMode,
+                    deviceSide, bondEntry);
+
+            sDeviceAddressToBondEntryMap.remove(deviceAddress);
+        } else {
+            Log.w(TAG, "The device address was not found. Hearing aid device info is not logged.");
+        }
+    }
+
+    @VisibleForTesting
+    static HashMap<String, Integer> getDeviceAddressToBondEntryMap() {
+        return sDeviceAddressToBondEntryMap;
+    }
+
+    private HearingAidStatsLogUtils() {}
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 4714ff9..8a9f9dd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -352,6 +352,8 @@
                         cachedDevice.setHiSyncId(newHiSyncId);
                     }
                 }
+
+                HearingAidStatsLogUtils.logHearingAidInfo(cachedDevice);
             }
 
             if (getCsipSetCoordinatorProfile() != null
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtilsTest.java
new file mode 100644
index 0000000..0cf5b89
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtilsTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.HashMap;
+
+@RunWith(RobolectricTestRunner.class)
+public class HearingAidStatsLogUtilsTest {
+
+    private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1";
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private CachedBluetoothDevice mCachedBluetoothDevice;
+
+    @Test
+    public void setBondEntryForDevice_addsEntryToDeviceAddressToBondEntryMap() {
+        when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
+
+        HearingAidStatsLogUtils.setBondEntryForDevice(
+                FrameworkStatsLog.HEARING_AID_INFO_REPORTED__BOND_ENTRY__BLUETOOTH,
+                mCachedBluetoothDevice);
+
+        final HashMap<String, Integer> map =
+                HearingAidStatsLogUtils.getDeviceAddressToBondEntryMap();
+        assertThat(map.containsKey(TEST_DEVICE_ADDRESS)).isTrue();
+        assertThat(map.get(TEST_DEVICE_ADDRESS)).isEqualTo(
+                FrameworkStatsLog.HEARING_AID_INFO_REPORTED__BOND_ENTRY__BLUETOOTH);
+    }
+
+    @Test
+    public void logHearingAidInfo_removesEntryFromDeviceAddressToBondEntryMap() {
+        when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
+
+        HearingAidStatsLogUtils.setBondEntryForDevice(
+                FrameworkStatsLog.HEARING_AID_INFO_REPORTED__BOND_ENTRY__BLUETOOTH,
+                mCachedBluetoothDevice);
+        HearingAidStatsLogUtils.logHearingAidInfo(mCachedBluetoothDevice);
+
+        final HashMap<String, Integer> map =
+                HearingAidStatsLogUtils.getDeviceAddressToBondEntryMap();
+        assertThat(map.containsKey(TEST_DEVICE_ADDRESS)).isFalse();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 436b756..8f5cbb7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -54,6 +54,8 @@
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.ScrollView;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
@@ -127,6 +129,7 @@
     private final float mTranslationY;
     @ContainerState private int mContainerState = STATE_UNKNOWN;
     private final Set<Integer> mFailedModalities = new HashSet<Integer>();
+    private final OnBackInvokedCallback mBackCallback = this::onBackInvoked;
 
     private final @Background DelayableExecutor mBackgroundExecutor;
     private int mOrientation;
@@ -362,8 +365,7 @@
                 return false;
             }
             if (event.getAction() == KeyEvent.ACTION_UP) {
-                sendEarlyUserCanceled();
-                animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
+                onBackInvoked();
             }
             return true;
         });
@@ -373,6 +375,11 @@
         requestFocus();
     }
 
+    private void onBackInvoked() {
+        sendEarlyUserCanceled();
+        animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
+    }
+
     void sendEarlyUserCanceled() {
         mConfig.mCallback.onSystemEvent(
                 BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL, getRequestId());
@@ -520,6 +527,11 @@
                         .start();
             });
         }
+        OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
+        if (dispatcher != null) {
+            dispatcher.registerOnBackInvokedCallback(
+                    OnBackInvokedDispatcher.PRIORITY_DEFAULT, mBackCallback);
+        }
     }
 
     private Animator.AnimatorListener getJankListener(View v, String type, long timeout) {
@@ -618,6 +630,10 @@
 
     @Override
     public void onDetachedFromWindow() {
+        OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
+        if (dispatcher != null) {
+            findOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mBackCallback);
+        }
         super.onDetachedFromWindow();
         mWakefulnessLifecycle.removeObserver(this);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index 6eb54f7..0dfb2f4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -197,6 +197,8 @@
         mUiEventLogger.log(InternetDialogEvent.INTERNET_DIALOG_SHOW);
         mDialogView = LayoutInflater.from(mContext).inflate(R.layout.internet_connectivity_dialog,
                 null);
+        mDialogView.setAccessibilityPaneTitle(
+                mContext.getText(R.string.accessibility_desc_quick_settings));
         final Window window = getWindow();
         window.setContentView(mDialogView);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index bf3788e..4a5b23c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -29,6 +29,7 @@
 import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
 import android.testing.ViewUtils
+import android.view.KeyEvent
 import android.view.View
 import android.view.WindowInsets
 import android.view.WindowManager
@@ -92,6 +93,21 @@
     }
 
     @Test
+    fun testDismissesOnBack() {
+        val container = initializeFingerprintContainer(addToView = true)
+        assertThat(container.parent).isNotNull()
+        val root = container.rootView
+
+        // Simulate back invocation
+        container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK))
+        container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK))
+        waitForIdleSync()
+
+        assertThat(container.parent).isNull()
+        assertThat(root.isAttachedToWindow).isFalse()
+    }
+
+    @Test
     fun testIgnoresAnimatedInWhenDismissed() {
         val container = initializeFingerprintContainer(addToView = false)
         container.dismissFromSystemServer()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
index d09a5a1..f922475 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
@@ -143,6 +143,12 @@
     }
 
     @Test
+    public void createInternetDialog_setAccessibilityPaneTitleToQuickSettings() {
+        assertThat(mDialogView.getAccessibilityPaneTitle())
+                .isEqualTo(mContext.getText(R.string.accessibility_desc_quick_settings));
+    }
+
+    @Test
     public void hideWifiViews_WifiViewsGone() {
         mInternetDialog.hideWifiViews();
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 4c0a017..2dbeee9 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -8042,11 +8042,7 @@
         // Horizontal position
         int offsetX = 0;
         if (parentBounds.width() != screenResolvedBounds.width()) {
-            if (screenResolvedBounds.width() >= parentAppBounds.width()) {
-                // If resolved bounds overlap with insets, center within app bounds.
-                offsetX = getCenterOffset(
-                        parentAppBounds.width(), screenResolvedBounds.width());
-            } else {
+            if (screenResolvedBounds.width() <= parentAppBounds.width()) {
                 float positionMultiplier =
                         mLetterboxUiController.getHorizontalPositionMultiplier(
                                 newParentConfiguration);
@@ -8058,11 +8054,7 @@
         // Vertical position
         int offsetY = 0;
         if (parentBounds.height() != screenResolvedBounds.height()) {
-            if (screenResolvedBounds.height() >= parentAppBounds.height()) {
-                // If resolved bounds overlap with insets, center within app bounds.
-                offsetY = getCenterOffset(
-                        parentAppBounds.height(), screenResolvedBounds.height());
-            } else {
+            if (screenResolvedBounds.height() <= parentAppBounds.height()) {
                 float positionMultiplier =
                         mLetterboxUiController.getVerticalPositionMultiplier(
                                 newParentConfiguration);
@@ -8080,6 +8072,15 @@
             offsetBounds(resolvedConfig, offsetX, offsetY);
         }
 
+        // If the top is aligned with parentAppBounds add the vertical insets back so that the app
+        // content aligns with the status bar
+        if (resolvedConfig.windowConfiguration.getAppBounds().top == parentAppBounds.top) {
+            resolvedConfig.windowConfiguration.getBounds().top = parentBounds.top;
+            if (mSizeCompatBounds != null) {
+                mSizeCompatBounds.top = parentBounds.top;
+            }
+        }
+
         // Since bounds has changed, the configuration needs to be computed accordingly.
         getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration);
     }
@@ -8457,7 +8458,7 @@
         // Above coordinates are in "@" space, now place "*" and "#" to screen space.
         final boolean fillContainer = resolvedBounds.equals(containingBounds);
         final int screenPosX = fillContainer ? containerBounds.left : containerAppBounds.left;
-        final int screenPosY  = fillContainer ? containerBounds.top : containerAppBounds.top;
+        final int screenPosY = fillContainer ? containerBounds.top : containerAppBounds.top;
 
         if (screenPosX != 0 || screenPosY != 0) {
             if (mSizeCompatBounds != null) {
@@ -8803,24 +8804,22 @@
         // Also account for the insets (e.g. display cutouts, navigation bar), which will be
         // clipped away later in {@link Task#computeConfigResourceOverrides()}, i.e., the out
         // bounds are the app bounds restricted by aspect ratio + clippable insets. Otherwise,
-        // the app bounds would end up too small.
+        // the app bounds would end up too small. To achieve this we will also add clippable insets
+        // when the corresponding dimension fully fills the parent
+
         int right = activityWidth + containingAppBounds.left;
+        int left = containingAppBounds.left;
         if (right >= containingAppBounds.right) {
-            right += containingBounds.right - containingAppBounds.right;
+            right = containingBounds.right;
+            left = containingBounds.left;
         }
         int bottom = activityHeight + containingAppBounds.top;
+        int top = containingAppBounds.top;
         if (bottom >= containingAppBounds.bottom) {
-            bottom += containingBounds.bottom - containingAppBounds.bottom;
+            bottom = containingBounds.bottom;
+            top = containingBounds.top;
         }
-        outBounds.set(containingBounds.left, containingBounds.top, right, bottom);
-
-        // If the bounds are restricted by fixed aspect ratio, then out bounds should be put in the
-        // container app bounds. Otherwise the entire container bounds are available.
-        if (!outBounds.equals(containingBounds)) {
-            // The horizontal position should not cover insets (e.g. display cutout).
-            outBounds.left = containingAppBounds.left;
-        }
-
+        outBounds.set(left, top, right, bottom);
         return true;
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 60d3f10..181e81d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -210,10 +210,8 @@
         assertFitted();
 
         // After the orientation of activity is changed, the display is rotated, the aspect
-        // ratio should be the same (bounds=[100, 0 - 800, 583], appBounds=[100, 0 - 800, 583]).
+        // ratio should be the same (bounds=[0, 0 - 800, 583], appBounds=[100, 0 - 800, 583]).
         assertEquals(appBounds.width(), appBounds.height() * aspectRatio, 0.5f /* delta */);
-        // The notch is no longer on top.
-        assertEquals(appBounds, mActivity.getBounds());
         // Activity max bounds are sandboxed.
         assertActivityMaxBoundsSandboxed();
 
@@ -467,8 +465,6 @@
         assertEquals(ROTATION_270, mTask.getWindowConfiguration().getRotation());
 
         assertEquals(origBounds.width(), currentBounds.width());
-        // The notch is on horizontal side, so current height changes from 1460 to 1400.
-        assertEquals(origBounds.height() - notchHeight, currentBounds.height());
         // Make sure the app size is the same
         assertEquals(origAppBounds.width(), appBounds.width());
         assertEquals(origAppBounds.height(), appBounds.height());
@@ -2271,7 +2267,7 @@
         prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);
 
         // Bounds are letterboxed to respect the provided max aspect ratio.
-        assertEquals(mActivity.getBounds(), new Rect(0, 850, 1000, 1950));
+        assertEquals(mActivity.getBounds(), new Rect(0, 0, 1000, 1100));
 
         // Move activity to split screen which has landscape size.
         mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents */ false, "test");
@@ -2338,6 +2334,8 @@
 
     @Test
     public void testLetterboxDetailsForStatusBar_letterboxNotOverlappingStatusBar() {
+        // Align to center so that we don't overlap with the status bar
+        mAtm.mWindowManager.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f);
         final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2800)
                 .setNotch(100)
                 .build();
@@ -2354,7 +2352,7 @@
         mActivity.mRootWindowContainer.performSurfacePlacement();
 
         Rect mBounds = new Rect(mActivity.getWindowConfiguration().getBounds());
-        assertEquals(mBounds, new Rect(0, 750, 1000, 1950));
+        assertEquals(mBounds, new Rect(0, 900, 1000, 2000));
 
         DisplayPolicy displayPolicy = mActivity.getDisplayContent().getDisplayPolicy();
         LetterboxDetails[] expectedLetterboxDetails = {new LetterboxDetails(
@@ -2454,7 +2452,7 @@
                 // At launch.
                 /* fixedOrientationLetterbox */ new Rect(0, 0, 700, 1400),
                 // After 90 degree rotation.
-                /* sizeCompatUnscaled */ new Rect(0, 700, 700, 2100),
+                /* sizeCompatUnscaled */ new Rect(0, 0, 700, 1400),
                 // After the display is resized to (700, 1400).
                 /* sizeCompatScaled */ new Rect(0, 0, 350, 700));
     }
@@ -2467,7 +2465,7 @@
                 // At launch.
                 /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
                 // After 90 degree rotation.
-                /* sizeCompatUnscaled */ new Rect(350, 700, 1050, 2100),
+                /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
                 // After the display is resized to (700, 1400).
                 /* sizeCompatScaled */ new Rect(525, 0, 875, 700));
     }
@@ -2482,7 +2480,7 @@
                 // At launch.
                 /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
                 // After 90 degree rotation.
-                /* sizeCompatUnscaled */ new Rect(350, 700, 1050, 2100),
+                /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
                 // After the display is resized to (700, 1400).
                 /* sizeCompatScaled */ new Rect(525, 0, 875, 700));
 
@@ -2492,7 +2490,7 @@
                 // At launch.
                 /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
                 // After 90 degree rotation.
-                /* sizeCompatUnscaled */ new Rect(350, 700, 1050, 2100),
+                /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
                 // After the display is resized to (700, 1400).
                 /* sizeCompatScaled */ new Rect(525, 0, 875, 700));
     }
@@ -2505,7 +2503,7 @@
                 // At launch.
                 /* fixedOrientationLetterbox */ new Rect(2100, 0, 2800, 1400),
                 // After 90 degree rotation.
-                /* sizeCompatUnscaled */ new Rect(700, 700, 1400, 2100),
+                /* sizeCompatUnscaled */ new Rect(700, 0, 1400, 1400),
                 // After the display is resized to (700, 1400).
                 /* sizeCompatScaled */ new Rect(1050, 0, 1400, 700));
     }
@@ -2534,6 +2532,64 @@
     }
 
     @Test
+    public void testApplyAspectRatio_activityAlignWithParentAppVertical() {
+        // The display's app bounds will be (0, 100, 1000, 2350)
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2500)
+                .setCanRotate(false)
+                .setCutout(0, 100, 0, 150)
+                .build();
+
+        setUpApp(display);
+        prepareUnresizable(mActivity, 2.1f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);
+        // The activity height is 2100 and the display's app bounds height is 2250, so the activity
+        // can be aligned inside parentAppBounds
+        assertEquals(mActivity.getBounds(), new Rect(0, 0, 1000, 2200));
+    }
+    @Test
+    public void testApplyAspectRatio_activityCannotAlignWithParentAppVertical() {
+        // The display's app bounds will be (0, 100, 1000, 2150)
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2300)
+                .setCanRotate(false)
+                .setCutout(0, 100, 0, 150)
+                .build();
+
+        setUpApp(display);
+        prepareUnresizable(mActivity, 2.1f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);
+        // The activity height is 2100 and the display's app bounds height is 2050, so the activity
+        // cannot be aligned inside parentAppBounds and it will fill the parentBounds of the display
+        assertEquals(mActivity.getBounds(), display.getBounds());
+    }
+
+    @Test
+    public void testApplyAspectRatio_activityAlignWithParentAppHorizontal() {
+        // The display's app bounds will be (100, 0, 2350, 1000)
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 2500, 1000)
+                .setCanRotate(false)
+                .setCutout(100, 0, 150, 0)
+                .build();
+
+        setUpApp(display);
+        prepareUnresizable(mActivity, 2.1f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);
+        // The activity width is 2100 and the display's app bounds width is 2250, so the activity
+        // can be aligned inside parentAppBounds
+        assertEquals(mActivity.getBounds(), new Rect(175, 0, 2275, 1000));
+    }
+    @Test
+    public void testApplyAspectRatio_activityCannotAlignWithParentAppHorizontal() {
+        // The display's app bounds will be (100, 0, 2150, 1000)
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 2300, 1000)
+                .setCanRotate(false)
+                .setCutout(100, 0, 150, 0)
+                .build();
+
+        setUpApp(display);
+        prepareUnresizable(mActivity, 2.1f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);
+        // The activity width is 2100 and the display's app bounds width is 2050, so the activity
+        // cannot be aligned inside parentAppBounds and it will fill the parentBounds of the display
+        assertEquals(mActivity.getBounds(), display.getBounds());
+    }
+
+    @Test
     public void testUpdateResolvedBoundsHorizontalPosition_activityFillParentWidth() {
         // When activity width equals parent width, multiplier shouldn't have any effect.
         assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity(
@@ -2608,6 +2664,25 @@
                 /* sizeCompatScaled */ new Rect(0, 1050, 700, 1400));
     }
 
+    @Test
+    public void testUpdateResolvedBoundsPosition_alignToTop() {
+        final int notchHeight = 100;
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2800)
+                .setNotch(notchHeight)
+                .build();
+        setUpApp(display);
+
+        // Prepare unresizable activity with max aspect ratio
+        prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);
+
+        Rect mBounds = new Rect(mActivity.getWindowConfiguration().getBounds());
+        Rect appBounds = new Rect(mActivity.getWindowConfiguration().getAppBounds());
+        // The insets should be cut for aspect ratio and then added back because the appBounds
+        // are aligned to the top of the parentAppBounds
+        assertEquals(mBounds, new Rect(0, 0, 1000, 1200));
+        assertEquals(appBounds, new Rect(0, notchHeight, 1000, 1200));
+    }
+
     private void assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity(
             float letterboxVerticalPositionMultiplier, Rect fixedOrientationLetterbox,
             Rect sizeCompatUnscaled, Rect sizeCompatScaled) {
@@ -2955,7 +3030,7 @@
         rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
         assertTrue(mActivity.inSizeCompatMode());
         // Activity is in size compat mode but not scaled.
-        assertEquals(new Rect(0, 1050, 1400, 1750), mActivity.getBounds());
+        assertEquals(new Rect(0, 0, 1400, 700), mActivity.getBounds());
     }
 
     private void assertVerticalPositionForDifferentDisplayConfigsForPortraitActivity(
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index aa3ca18..bf1d1fa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -143,11 +143,24 @@
             mInfo.ownerUid = ownerUid;
             return this;
         }
-        Builder setNotch(int height) {
+        Builder setCutout(int left, int top, int right, int bottom) {
+            final int cutoutFillerSize = 80;
+            Rect boundLeft = left != 0 ? new Rect(0, 0, left, cutoutFillerSize) : null;
+            Rect boundTop = top != 0 ? new Rect(0, 0, cutoutFillerSize, top) : null;
+            Rect boundRight = right != 0 ? new Rect(mInfo.logicalWidth - right, 0,
+                    mInfo.logicalWidth, cutoutFillerSize) : null;
+            Rect boundBottom = bottom != 0
+                    ? new Rect(0, mInfo.logicalHeight - bottom, cutoutFillerSize,
+                    mInfo.logicalHeight) : null;
+
             mInfo.displayCutout = new DisplayCutout(
-                    Insets.of(0, height, 0, 0), null, new Rect(20, 0, 80, height), null, null);
+                    Insets.of(left, top, right, bottom),
+                    boundLeft, boundTop, boundRight, boundBottom);
             return this;
         }
+        Builder setNotch(int height) {
+            return setCutout(0, height, 0, 0);
+        }
         Builder setStatusBarHeight(int height) {
             mStatusBarHeight = height;
             return this;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index b6373b4..ef532f5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -238,7 +238,7 @@
         // Ensure letterbox vertical position multiplier is not overridden on any device target.
         // {@link com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier},
         // may be set on some device form factors.
-        mAtm.mWindowManager.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f);
+        mAtm.mWindowManager.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.0f);
         // Ensure letterbox horizontal reachability treatment isn't overridden on any device target.
         // {@link com.android.internal.R.bool.config_letterboxIsHorizontalReachabilityEnabled},
         // may be set on some device form factors.