Merge "Fix AccessibilityService#getWindows time out" into rvc-qpr-dev
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 322cac8..e2e8049 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -4263,6 +4263,9 @@
      * This method can be called on the {@link DevicePolicyManager} instance returned by
      * {@link #getParentProfileInstance(ComponentName)} in order to lock the parent profile.
      * <p>
+     * NOTE: on {@link android.content.pm.PackageManager#FEATURE_AUTOMOTIVE automotive builds}, this
+     * method doesn't turn off the screen as it would be a driving safety distraction.
+     * <p>
      * Equivalent to calling {@link #lockNow(int)} with no flags.
      *
      * @throws SecurityException if the calling application does not own an active administrator
@@ -4306,6 +4309,9 @@
      * Calling the method twice in this order ensures that all users are locked and does not
      * stop the device admin on the managed profile from issuing a second call to lock its own
      * profile.
+     * <p>
+     * NOTE: on {@link android.content.pm.PackageManager#FEATURE_AUTOMOTIVE automotive builds}, this
+     * method doesn't turn off the screen as it would be a driving safety distraction.
      *
      * @param flags May be 0 or {@link #FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY}.
      * @throws SecurityException if the calling application does not own an active administrator
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 1e781531..1926ea2 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1215,7 +1215,6 @@
                 WindowManager.LayoutParams.TYPE_INPUT_METHOD, Gravity.BOTTOM, false);
         mWindow.getWindow().getAttributes().setFitInsetsTypes(statusBars() | navigationBars());
         mWindow.getWindow().getAttributes().setFitInsetsSides(Side.all() & ~Side.BOTTOM);
-        mWindow.getWindow().getAttributes().setFitInsetsIgnoringVisibility(true);
 
         // IME layout should always be inset by navigation bar, no matter its current visibility,
         // unless automotive requests it. Automotive devices may request the navigation bar to be
diff --git a/data/etc/car/Android.bp b/data/etc/car/Android.bp
index e549271..3c990ab 100644
--- a/data/etc/car/Android.bp
+++ b/data/etc/car/Android.bp
@@ -163,3 +163,10 @@
     src: "com.android.car.ui.paintbooth.xml",
     filename_from_src: true,
 }
+
+prebuilt_etc {
+    name: "allowed_privapp_com.android.carshell",
+    sub_dir: "permissions",
+    src: "com.android.car.shell.xml",
+    filename_from_src: true,
+}
diff --git a/data/etc/car/com.android.car.shell.xml b/data/etc/car/com.android.car.shell.xml
new file mode 100644
index 0000000..32666c8
--- /dev/null
+++ b/data/etc/car/com.android.car.shell.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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
+  -->
+<permissions>
+    <privapp-permissions package="com.android.car.shell">
+        <permission name="android.permission.INSTALL_PACKAGES" />
+        <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
+    </privapp-permissions>
+</permissions>
diff --git a/packages/CarSystemUI/res/layout/headsup_container_bottom.xml b/packages/CarSystemUI/res/layout/headsup_container_bottom.xml
index 5aab0a1..f43f02d 100644
--- a/packages/CarSystemUI/res/layout/headsup_container_bottom.xml
+++ b/packages/CarSystemUI/res/layout/headsup_container_bottom.xml
@@ -45,14 +45,13 @@
         app:layout_constraintBottom_toBottomOf="@+id/gradient_edge"
         app:layout_constraintTop_toTopOf="parent"/>
 
-    <FrameLayout
+    <com.android.car.notification.headsup.HeadsUpContainerView
         android:id="@+id/headsup_content"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginTop="@dimen/headsup_notification_top_margin"
         app:layout_constraintEnd_toStartOf="parent"
         app:layout_constraintStart_toEndOf="parent"
-        app:layout_constraintBottom_toBottomOf="parent"
-    />
+        app:layout_constraintBottom_toBottomOf="parent"/>
 
-</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 03161d0..da0993dc 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1100,6 +1100,8 @@
     <string name="power_remaining_charging_duration_only"><xliff:g id="time">%1$s</xliff:g> left until charged</string>
     <!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration -->
     <string name="power_charging_duration"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="time">%2$s</xliff:g> until charged</string>
+    <!-- [CHAR_LIMIT=40] Label for battery level chart when charge been limited -->
+    <string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Battery limited temporarily</string>
 
     <!-- Battery Info screen. Value for a status item.  Used for diagnostic info screens, precise translation isn't needed -->
     <string name="battery_info_status_unknown">Unknown</string>
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index f7e9fed..d95ab37 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -79,6 +79,9 @@
          is not fully charged, and it's plugged into a slow charger, say that it's charging slowly.  -->
     <string name="keyguard_plugged_in_charging_slowly"><xliff:g id="percentage">%s</xliff:g> • Charging slowly</string>
 
+    <!-- When the lock screen is showing and the phone plugged in, and the defend mode is triggered, say that it's limited temporarily.  -->
+    <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Battery limited temporarily</string>
+
     <!-- When the lock screen is showing and the battery is low, warn user to plug
          in the phone soon. -->
     <string name="keyguard_low_battery">Connect your charger.</string>
diff --git a/packages/SystemUI/res/layout/controls_dialog_pin.xml b/packages/SystemUI/res/layout/controls_dialog_pin.xml
index 170b32b..d0ef10b 100644
--- a/packages/SystemUI/res/layout/controls_dialog_pin.xml
+++ b/packages/SystemUI/res/layout/controls_dialog_pin.xml
@@ -27,6 +27,7 @@
       android:layout_height="wrap_content"
       android:minHeight="48dp"
       android:longClickable="false"
+      android:textAlignment="viewStart"
       android:inputType="numberPassword" />
   <CheckBox
       android:id="@+id/controls_pin_use_alpha"
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 6e27eca..db0eae4 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -325,6 +325,7 @@
     <string name="notification_group_overflow_indicator" msgid="7605120293801012648">"+ <xliff:g id="NUMBER">%s</xliff:g>"</string>
     <plurals name="notification_group_overflow_description" formatted="false" msgid="91483442850649192">
       <item quantity="one"><xliff:g id="NUMBER_1">%s</xliff:g> autre notification à l\'intérieur.</item>
+      <item quantity="many"><xliff:g id="NUMBER_1">%s</xliff:g> more notifications inside.</item>
       <item quantity="other"><xliff:g id="NUMBER_1">%s</xliff:g> autres notifications à l\'intérieur.</item>
     </plurals>
     <string name="notification_summary_message_format" msgid="5158219088501909966">"<xliff:g id="CONTACT_NAME">%1$s</xliff:g> : <xliff:g id="MESSAGE_CONTENT">%2$s</xliff:g>"</string>
@@ -400,6 +401,7 @@
     <string name="quick_settings_hotspot_secondary_label_data_saver_enabled" msgid="1280433136266439372">"Écon. données activé"</string>
     <plurals name="quick_settings_hotspot_secondary_label_num_devices" formatted="false" msgid="3142308865165871976">
       <item quantity="one">%d appareil</item>
+      <item quantity="many">%d devices</item>
       <item quantity="other">%d appareils</item>
     </plurals>
     <string name="quick_settings_notifications_label" msgid="3379631363952582758">"Notifications"</string>
@@ -459,7 +461,7 @@
     <string name="camera_hint" msgid="4519495795000658637">"Balayez à partir de l\'icône pour accéder à l\'appareil photo"</string>
     <string name="interruption_level_none_with_warning" msgid="8394434073508145437">"Aucune interruption : le son des lecteurs d\'écran sera également désactivé."</string>
     <string name="interruption_level_none" msgid="219484038314193379">"Aucune interruption"</string>
-    <string name="interruption_level_priority" msgid="661294280016622209">"Priorités seulement"</string>
+    <string name="interruption_level_priority" msgid="661294280016622209">"Prioritaires seulement"</string>
     <string name="interruption_level_alarms" msgid="2457850481335846959">"Alarmes seulement"</string>
     <string name="interruption_level_none_twoline" msgid="8579382742855486372">"Aucune\ninterruption"</string>
     <string name="interruption_level_priority_twoline" msgid="8523482736582498083">"Priorités\nuniquement"</string>
@@ -492,6 +494,7 @@
     <string name="user_limit_reached_title" msgid="2429229448830346057">"Limite d\'utilisateurs atteinte"</string>
     <plurals name="user_limit_reached_message" formatted="false" msgid="2573535787802908398">
       <item quantity="one">Vous pouvez ajouter jusqu\'à <xliff:g id="COUNT">%d</xliff:g> utilisateur.</item>
+      <item quantity="many">You can add up to <xliff:g id="COUNT">%d</xliff:g> users.</item>
       <item quantity="other">Vous pouvez ajouter jusqu\'à <xliff:g id="COUNT">%d</xliff:g> utilisateurs.</item>
     </plurals>
     <string name="user_remove_user_title" msgid="9124124694835811874">"Supprimer l\'utilisateur?"</string>
@@ -716,7 +719,7 @@
     <string name="notification_channel_summary_bubble" msgid="7235935211580860537">"Garde votre attention à l\'aide d\'un raccourci flottant vers ce contenu."</string>
     <string name="notification_channel_summary_priority" msgid="7952654515769021553">"S\'affiche en haut de la section des conversations sous forme de bulle flottante et affiche la photo du profil sur l\'écran de verrouillage"</string>
     <string name="notification_conversation_channel_settings" msgid="2409977688430606835">"Paramètres"</string>
-    <string name="notification_priority_title" msgid="2079708866333537093">"Priorité"</string>
+    <string name="notification_priority_title" msgid="2079708866333537093">"Prioritaire"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> ne prend pas en charge les fonctionnalités de conversation"</string>
     <string name="bubble_overflow_empty_title" msgid="3120029421991510842">"Aucune bulle récente"</string>
     <string name="bubble_overflow_empty_subtitle" msgid="2030874469510497397">"Les bulles récentes et les bulles ignorées s\'afficheront ici"</string>
@@ -758,10 +761,12 @@
     <string name="snoozed_for_time" msgid="7586689374860469469">"Reporté pour <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
     <plurals name="snoozeHourOptions" formatted="false" msgid="2066838694120718170">
       <item quantity="one">%d heure</item>
+      <item quantity="many">%d hours</item>
       <item quantity="other">%d heures</item>
     </plurals>
     <plurals name="snoozeMinuteOptions" formatted="false" msgid="8998483159208055980">
       <item quantity="one">%d minute</item>
+      <item quantity="many">%d minutes</item>
       <item quantity="other">%d minutes</item>
     </plurals>
     <string name="battery_panel_title" msgid="5931157246673665963">"Utilisation de la pile"</string>
@@ -1040,6 +1045,7 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Sélectionnez l\'application pour laquelle ajouter des commandes"</string>
     <plurals name="controls_number_of_favorites" formatted="false" msgid="1057347832073807380">
       <item quantity="one"><xliff:g id="NUMBER_1">%s</xliff:g> commande ajoutée.</item>
+      <item quantity="many"><xliff:g id="NUMBER_1">%s</xliff:g> controls added.</item>
       <item quantity="other"><xliff:g id="NUMBER_1">%s</xliff:g> commandes ajoutées.</item>
     </plurals>
     <string name="controls_removed" msgid="3731789252222856959">"Supprimé"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index f3f19cb..53cd847 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -325,6 +325,7 @@
     <string name="notification_group_overflow_indicator" msgid="7605120293801012648">"<xliff:g id="NUMBER">%s</xliff:g> autres"</string>
     <plurals name="notification_group_overflow_description" formatted="false" msgid="91483442850649192">
       <item quantity="one"><xliff:g id="NUMBER_1">%s</xliff:g> autre notification à l\'intérieur.</item>
+      <item quantity="many"><xliff:g id="NUMBER_1">%s</xliff:g> more notifications inside.</item>
       <item quantity="other"><xliff:g id="NUMBER_1">%s</xliff:g> autres notifications à l\'intérieur.</item>
     </plurals>
     <string name="notification_summary_message_format" msgid="5158219088501909966">"<xliff:g id="CONTACT_NAME">%1$s</xliff:g> : <xliff:g id="MESSAGE_CONTENT">%2$s</xliff:g>"</string>
@@ -400,6 +401,7 @@
     <string name="quick_settings_hotspot_secondary_label_data_saver_enabled" msgid="1280433136266439372">"Écon. données activé"</string>
     <plurals name="quick_settings_hotspot_secondary_label_num_devices" formatted="false" msgid="3142308865165871976">
       <item quantity="one">%d appareil</item>
+      <item quantity="many">%d devices</item>
       <item quantity="other">%d appareils</item>
     </plurals>
     <string name="quick_settings_notifications_label" msgid="3379631363952582758">"Notifications"</string>
@@ -492,6 +494,7 @@
     <string name="user_limit_reached_title" msgid="2429229448830346057">"Limite nombre utilisateurs atteinte"</string>
     <plurals name="user_limit_reached_message" formatted="false" msgid="2573535787802908398">
       <item quantity="one">Vous pouvez ajouter <xliff:g id="COUNT">%d</xliff:g> profil utilisateur.</item>
+      <item quantity="many">You can add up to <xliff:g id="COUNT">%d</xliff:g> users.</item>
       <item quantity="other">Vous pouvez ajouter jusqu\'à <xliff:g id="COUNT">%d</xliff:g> profils utilisateur.</item>
     </plurals>
     <string name="user_remove_user_title" msgid="9124124694835811874">"Supprimer l\'utilisateur ?"</string>
@@ -758,10 +761,12 @@
     <string name="snoozed_for_time" msgid="7586689374860469469">"Répétée après <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
     <plurals name="snoozeHourOptions" formatted="false" msgid="2066838694120718170">
       <item quantity="one">%d heure</item>
+      <item quantity="many">%d hours</item>
       <item quantity="other">%d heures</item>
     </plurals>
     <plurals name="snoozeMinuteOptions" formatted="false" msgid="8998483159208055980">
       <item quantity="one">%d minute</item>
+      <item quantity="many">%d minutes</item>
       <item quantity="other">%d minutes</item>
     </plurals>
     <string name="battery_panel_title" msgid="5931157246673665963">"Utilisation batterie"</string>
@@ -1040,6 +1045,7 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Sélectionnez l\'appli pour laquelle ajouter des commandes"</string>
     <plurals name="controls_number_of_favorites" formatted="false" msgid="1057347832073807380">
       <item quantity="one"><xliff:g id="NUMBER_1">%s</xliff:g> commande ajoutée.</item>
+      <item quantity="many"><xliff:g id="NUMBER_1">%s</xliff:g> controls added.</item>
       <item quantity="other"><xliff:g id="NUMBER_1">%s</xliff:g> commandes ajoutées.</item>
     </plurals>
     <string name="controls_removed" msgid="3731789252222856959">"Supprimé"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 83532bc..777ed71 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -82,7 +82,7 @@
     <string name="screenshot_saving_title" msgid="2298349784913287333">"Сликата на екранот се зачувува..."</string>
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Сликата од екранот е зачувана"</string>
     <string name="screenshot_saved_text" msgid="7778833104901642442">"Допрете за да ја видите сликата од екранот"</string>
-    <string name="screenshot_failed_title" msgid="3259148215671936891">"Не можеше да се зачува слика од екранот"</string>
+    <string name="screenshot_failed_title" msgid="3259148215671936891">"Не може да се зачува слика од екранот"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Повторно обидете се да направите слика од екранот"</string>
     <string name="screenshot_failed_to_save_text" msgid="8344173457344027501">"Сликата од екранот не може да се зачува поради ограничена меморија"</string>
     <string name="screenshot_failed_to_capture_text" msgid="7818288545874407451">"Апликацијата или вашата организација не дозволува снимање слики од екранот"</string>
@@ -1055,7 +1055,7 @@
     <string name="controls_favorite_removed" msgid="5276978408529217272">"Сите контроли се отстранети"</string>
     <string name="controls_favorite_toast_no_changes" msgid="7094494210840877931">"Промените не се зачувани"</string>
     <string name="controls_favorite_see_other_apps" msgid="7709087332255283460">"Видете други апликации"</string>
-    <string name="controls_favorite_load_error" msgid="5126216176144877419">"Контролите не можеше да се вчитаат. Проверете ја апликацијата <xliff:g id="APP">%s</xliff:g> за да се уверите дека поставките за апликацијата не се променети."</string>
+    <string name="controls_favorite_load_error" msgid="5126216176144877419">"Контролите не може да се вчитаат. Проверете ја апликацијата <xliff:g id="APP">%s</xliff:g> за да се уверите дека поставките за апликацијата не се променети."</string>
     <string name="controls_favorite_load_none" msgid="7687593026725357775">"Нема компатибилни контроли"</string>
     <string name="controls_favorite_other_zone_header" msgid="9089613266575525252">"Друга"</string>
     <string name="controls_dialog_title" msgid="2343565267424406202">"Додајте во контроли за уредите"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 6a77ade..76badc3 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -684,7 +684,7 @@
     <string name="do_not_silence_block" msgid="4361847809775811849">"मौन नगर्नुहोस् वा नरोक्नुहोस्"</string>
     <string name="tuner_full_importance_settings" msgid="1388025816553459059">"सशक्त सूचना नियन्त्रण"</string>
     <string name="tuner_full_importance_settings_on" msgid="917981436602311547">"अन छ"</string>
-    <string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"निष्क्रिय"</string>
+    <string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"अफ"</string>
     <string name="power_notification_controls_description" msgid="1334963837572708952">"सशक्त सूचना नियन्त्रणहरू मार्फत तपाईं अनुप्रयाेगका सूचनाहरूका लागि ० देखि ५ सम्मको महत्व सम्बन्धी स्तर सेट गर्न सक्नुहुन्छ। \n\n"<b>"स्तर ५"</b>" \n- सूचनाको सूचीको माथिल्लो भागमा देखाउने \n- पूर्ण स्क्रिनमा अवरोधका लागि अनुमति दिने \n- सधैँ चियाउने \n\n"<b>"स्तर ४"</b>" \n- पूर्ण स्क्रिनमा अवरोधलाई रोक्ने \n- सधैँ चियाउने \n\n"<b>"स्तर ३"</b>" \n- पूर्ण स्क्रिनमा अवरोधलाई रोक्ने \n- कहिल्यै नचियाउने \n\n"<b>"स्तर २"</b>" \n- पूर्ण स्क्रिनमा अवरोधलाई रोक्ने \n- कहिल्यै नचियाउने \n- कहिल्यै पनि आवाज ननिकाल्ने र कम्पन नगर्ने \n\n"<b>"स्तर १"</b>" \n- पूर्ण स्क्रिनमा अवरोध रोक्ने \n- कहिल्यै नचियाउने \n- कहिल्यै पनि आवाज ननिकाल्ने वा कम्पन नगर्ने \n- लक स्क्रिन र वस्तुस्थिति पट्टीबाट लुकाउने \n- सूचनाको सूचीको तल्लो भागमा देखाउने \n\n"<b>"स्तर ०"</b>" \n- अनुप्रयोगका सबै सूचनाहरूलाई रोक्ने"</string>
     <string name="notification_header_default_channel" msgid="225454696914642444">"सूचनाहरू"</string>
     <string name="notification_channel_disabled" msgid="928065923928416337">"तपाईं अब उप्रान्त यी सूचनाहरू देख्नु हुने छैन"</string>
@@ -824,7 +824,7 @@
     <string name="accessibility_data_saver_on" msgid="5394743820189757731">"डेटा सेभर सक्रिय छ"</string>
     <string name="accessibility_data_saver_off" msgid="58339669022107171">"डेटा सेभर बन्द छ"</string>
     <string name="switch_bar_on" msgid="1770868129120096114">"सक्रिय गर्नुहोस्"</string>
-    <string name="switch_bar_off" msgid="5669805115416379556">"निष्क्रिय"</string>
+    <string name="switch_bar_off" msgid="5669805115416379556">"अफ"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"उपलब्ध छैन"</string>
     <string name="nav_bar" msgid="4642708685386136807">"नेभिगेशन पट्टी"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"लेआउट"</string>
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index dbe5a77..c1233fe 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -96,7 +96,7 @@
 
     @VisibleForTesting final WakefulnessLifecycle mWakefulnessLifecycle;
 
-    private @ContainerState int mContainerState = STATE_UNKNOWN;
+    @VisibleForTesting @ContainerState int mContainerState = STATE_UNKNOWN;
 
     // Non-null only if the dialog is in the act of dismissing and has not sent the reason yet.
     @Nullable @AuthDialogCallback.DismissedReason Integer mPendingCallbackReason;
@@ -607,10 +607,11 @@
         mWindowManager.removeView(this);
     }
 
-    private void onDialogAnimatedIn() {
+    @VisibleForTesting
+    void onDialogAnimatedIn() {
         if (mContainerState == STATE_PENDING_DISMISS) {
             Log.d(TAG, "onDialogAnimatedIn(): mPendingDismissDialog=true, dismissing now");
-            animateAway(false /* sendReason */, 0);
+            animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
             return;
         }
         mContainerState = STATE_SHOWING;
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
index aa11df4..a3a937a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
@@ -301,7 +301,7 @@
     }
 
     fun findNearestStep(value: Float): Float {
-        var minDiff = 1000f
+        var minDiff = Float.MAX_VALUE
 
         var f = rangeTemplate.getMinValue()
         while (f <= rangeTemplate.getMaxValue()) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
index 29d7a52..5f3f3a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
@@ -204,6 +204,16 @@
     }
 
     @Test
+    public void testOnDialogAnimatedIn_sendsCancelReason_whenPendingDismiss() {
+        initializeContainer(Authenticators.BIOMETRIC_WEAK);
+        mAuthContainer.mContainerState = AuthContainerView.STATE_PENDING_DISMISS;
+        mAuthContainer.onDialogAnimatedIn();
+        verify(mCallback).onDismissed(
+                eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
+                eq(null) /* credentialAttestation */);
+    }
+
+    @Test
     public void testLayoutParams_hasSecureWindowFlag() {
         final IBinder windowToken = mock(IBinder.class);
         final WindowManager.LayoutParams layoutParams =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ToggleRangeTemplateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ToggleRangeTemplateTest.kt
new file mode 100644
index 0000000..31e0954
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ToggleRangeTemplateTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.ui
+
+import android.service.controls.templates.RangeTemplate
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ToggleRangeTemplateTest : SysuiTestCase() {
+
+    @Test
+    fun testLargeRangeNearestStep() {
+        val trt = ToggleRangeBehavior()
+        trt.rangeTemplate = RangeTemplate("range", -100000f, 100000f, 0f, 5f, null)
+
+        assertEquals(255f, trt.findNearestStep(253f), 0.1f)
+    }
+
+    @Test
+    fun testLargeRangeNearestStepWithNegativeValues() {
+        val trt = ToggleRangeBehavior()
+        trt.rangeTemplate = RangeTemplate("range", -100000f, 100000f, 0f, 5f, null)
+
+        assertEquals(-7855f, trt.findNearestStep(-7853.2f), 0.1f)
+    }
+
+    @Test
+    fun testFractionalRangeNearestStep() {
+        val trt = ToggleRangeBehavior()
+        trt.rangeTemplate = RangeTemplate("range", 10f, 11f, 10f, .01f, null)
+
+        assertEquals(10.54f, trt.findNearestStep(10.543f), 0.01f)
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
index 8604fe7..17e3a023 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
@@ -104,9 +104,9 @@
     // Shared state information.
     private TouchState mState;
 
-    GestureManifold(Context context, Listener listener, TouchState state) {
+    GestureManifold(Context context, Listener listener, TouchState state, Handler handler) {
         mContext = context;
-        mHandler = new Handler(context.getMainLooper());
+        mHandler = handler;
         mListener = listener;
         mState = state;
         mMultiFingerGesturesEnabled = false;
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index 71d2c2c..c3d0e1a 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -175,7 +175,7 @@
                 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END,
                 mDetermineUserIntentTimeout);
         if (detector == null) {
-            mGestureDetector = new GestureManifold(context, this, mState);
+            mGestureDetector = new GestureManifold(context, this, mState, mHandler);
         } else {
             mGestureDetector = detector;
         }
@@ -353,7 +353,6 @@
     public boolean onGestureStarted() {
         // We have to perform gesture detection, so
         // clear the current state and try to detect.
-        mState.startGestureDetecting();
         mSendHoverEnterAndMoveDelayed.cancel();
         mSendHoverExitDelayed.cancel();
         mExitGestureDetectionModeDelayed.post();
@@ -1107,7 +1106,7 @@
     }
 
     private boolean shouldPerformGestureDetection(MotionEvent event) {
-        if (mState.isDelegating()) {
+        if (mState.isDelegating() || mState.isDragging()) {
             return false;
         }
         if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
@@ -1200,6 +1199,15 @@
         }
 
         public void run() {
+            if (mReceivedPointerTracker.getReceivedPointerDownCount() > 1) {
+                // Multi-finger touch exploration doesn't make sense.
+                Slog.e(
+                        LOG_TAG,
+                        "Attempted touch exploration with "
+                                + mReceivedPointerTracker.getReceivedPointerDownCount()
+                                + " pointers down.");
+                return;
+            }
             // Send an accessibility event to announce the touch exploration start.
             mDispatcher.sendAccessibilityEvent(
                     AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START);
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
index 7a39bc2..6dabe76 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
@@ -208,7 +208,9 @@
                 startGestureDetecting();
                 break;
             case AccessibilityEvent.TYPE_GESTURE_DETECTION_END:
-                startTouchInteracting();
+                // Clear to make sure that we don't accidentally execute passthrough, and that we
+                // are ready for the next interaction.
+                clear();
                 break;
             default:
                 break;
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index 3d22a15..f49b5dc 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -669,10 +669,12 @@
 
     void shutdown() {
         synchronized (mInMemoryLock) {
-            if (mMode != AppOpsManager.HISTORICAL_MODE_DISABLED) {
-                persistPendingHistory();
+            if (mMode == AppOpsManager.HISTORICAL_MODE_DISABLED) {
+                return;
             }
         }
+        // Do not call persistPendingHistory inside the memory lock, due to possible deadlock
+        persistPendingHistory();
     }
 
     void persistPendingHistory() {
diff --git a/services/core/java/com/android/server/notification/InjectableSystemClock.java b/services/core/java/com/android/server/notification/InjectableSystemClock.java
new file mode 100644
index 0000000..4d993d1
--- /dev/null
+++ b/services/core/java/com/android/server/notification/InjectableSystemClock.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+/**
+ * Testable wrapper around {@link android.os.SystemClock}.
+ *
+ * The default implementation at InjectableSystemClockImpl just proxies calls to the real
+ * SystemClock
+ *
+ * In tests, pass an instance of FakeSystemClock, which allows you to control the values returned by
+ * the various getters below.
+ */
+public interface InjectableSystemClock {
+    /** @see android.os.SystemClock#uptimeMillis() */
+    long uptimeMillis();
+
+    /** @see android.os.SystemClock#elapsedRealtime() */
+    long elapsedRealtime();
+
+    /** @see android.os.SystemClock#elapsedRealtimeNanos() */
+    long elapsedRealtimeNanos();
+
+    /** @see android.os.SystemClock#currentThreadTimeMillis() */
+    long currentThreadTimeMillis();
+
+    /** @see System#currentTimeMillis()  */
+    long currentTimeMillis();
+}
+
diff --git a/services/core/java/com/android/server/notification/InjectableSystemClockImpl.java b/services/core/java/com/android/server/notification/InjectableSystemClockImpl.java
new file mode 100644
index 0000000..43d756f
--- /dev/null
+++ b/services/core/java/com/android/server/notification/InjectableSystemClockImpl.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+/**
+ * Default implementation of {@link InjectableSystemClock}.
+ *
+ * @hide
+ */
+public class InjectableSystemClockImpl implements InjectableSystemClock {
+    public InjectableSystemClockImpl() {}
+
+    @Override
+    public long uptimeMillis() {
+        return android.os.SystemClock.uptimeMillis();
+    }
+
+    @Override
+    public long elapsedRealtime() {
+        return android.os.SystemClock.elapsedRealtime();
+    }
+
+    @Override
+    public long elapsedRealtimeNanos() {
+        return android.os.SystemClock.elapsedRealtimeNanos();
+    }
+
+    @Override
+    public long currentThreadTimeMillis() {
+        return android.os.SystemClock.currentThreadTimeMillis();
+    }
+
+    @Override
+    public long currentTimeMillis() {
+        return System.currentTimeMillis();
+    }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 14635cc..1472fbd 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -187,7 +187,6 @@
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.ShellCallback;
-import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -473,6 +472,11 @@
     final ArrayMap<Integer, ArrayMap<String, String>> mAutobundledSummaries = new ArrayMap<>();
     final ArrayList<ToastRecord> mToastQueue = new ArrayList<>();
     final ArrayMap<String, NotificationRecord> mSummaryByGroupKey = new ArrayMap<>();
+    // Keep track of `CancelNotificationRunnable`s which have been delayed due to awaiting
+    // enqueued notifications to post
+    @GuardedBy("mNotificationLock")
+    final ArrayMap<NotificationRecord, ArrayList<CancelNotificationRunnable>> mDelayedCancelations =
+            new ArrayMap<>();
 
     // The last key in this list owns the hardware.
     ArrayList<String> mLights = new ArrayList<>();
@@ -535,6 +539,7 @@
     private NotificationRecordLogger mNotificationRecordLogger;
     private InstanceIdSequence mNotificationInstanceIdSequence;
     private Set<String> mMsgPkgsAllowedAsConvos = new HashSet();
+    private final InjectableSystemClock mSystemClock;
 
     static class Archive {
         final SparseArray<Boolean> mEnabled;
@@ -748,7 +753,7 @@
                         parser, mAllowedManagedServicePackages, forRestore, userId);
                 migratedManagedServices = true;
             } else if (mSnoozeHelper.XML_TAG_NAME.equals(parser.getName())) {
-                mSnoozeHelper.readXml(parser, System.currentTimeMillis());
+                mSnoozeHelper.readXml(parser, mSystemClock.currentTimeMillis());
             }
             if (LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_TAG.equals(parser.getName())) {
                 if (forRestore && userId != UserHandle.USER_SYSTEM) {
@@ -901,7 +906,7 @@
                     Slog.w(TAG, "No notification with key: " + key);
                     return;
                 }
-                final long now = System.currentTimeMillis();
+                final long now = mSystemClock.currentTimeMillis();
                 MetricsLogger.action(r.getItemLogMaker()
                         .setType(MetricsEvent.TYPE_ACTION)
                         .addTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX, nv.rank)
@@ -933,7 +938,7 @@
                     Slog.w(TAG, "No notification with key: " + key);
                     return;
                 }
-                final long now = System.currentTimeMillis();
+                final long now = mSystemClock.currentTimeMillis();
                 MetricsLogger.action(r.getLogMaker(now)
                         .setCategory(MetricsEvent.NOTIFICATION_ITEM_ACTION)
                         .setType(MetricsEvent.TYPE_ACTION)
@@ -1697,15 +1702,18 @@
     public NotificationManagerService(Context context) {
         this(context,
                 new NotificationRecordLoggerImpl(),
+                new InjectableSystemClockImpl(),
                 new InstanceIdSequence(NOTIFICATION_INSTANCE_ID_MAX));
     }
 
     @VisibleForTesting
     public NotificationManagerService(Context context,
             NotificationRecordLogger notificationRecordLogger,
+            InjectableSystemClock systemClock,
             InstanceIdSequence notificationInstanceIdSequence) {
         super(context);
         mNotificationRecordLogger = notificationRecordLogger;
+        mSystemClock = systemClock;
         mNotificationInstanceIdSequence = notificationInstanceIdSequence;
         Notification.processWhitelistToken = WHITELIST_TOKEN;
     }
@@ -2064,6 +2072,11 @@
         return getContext().getResources().getStringArray(key);
     }
 
+    @VisibleForTesting
+    protected Handler getWorkHandler() {
+        return mHandler;
+    }
+
     @Override
     public void onStart() {
         SnoozeHelper snoozeHelper = new SnoozeHelper(getContext(), (userId, r, muteOnReturn) -> {
@@ -2337,7 +2350,8 @@
             mHistoryManager.onBootPhaseAppsCanStart();
             registerDeviceConfigChange();
         } else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
-            mSnoozeHelper.scheduleRepostsForPersistedNotifications(System.currentTimeMillis());
+            mSnoozeHelper.scheduleRepostsForPersistedNotifications(
+                    mSystemClock.currentTimeMillis());
         }
     }
 
@@ -2697,7 +2711,7 @@
                         .setUserId(r.getSbn().getNormalizedUserId())
                         .setChannelId(r.getChannel().getId())
                         .setChannelName(r.getChannel().getName().toString())
-                        .setPostedTimeMs(System.currentTimeMillis())
+                        .setPostedTimeMs(mSystemClock.currentTimeMillis())
                         .setTitle(getHistoryTitle(r.getNotification()))
                         .setText(getHistoryText(
                                 r.getSbn().getPackageContext(getContext()), r.getNotification()))
@@ -5225,7 +5239,7 @@
                                 GroupHelper.AUTOGROUP_KEY, adjustedSbn.getUid(),
                                 adjustedSbn.getInitialPid(), summaryNotification,
                                 adjustedSbn.getUser(), GroupHelper.AUTOGROUP_KEY,
-                                System.currentTimeMillis());
+                                mSystemClock.currentTimeMillis());
                 summaryRecord = new NotificationRecord(getContext(), summarySbn,
                         notificationRecord.getChannel());
                 summaryRecord.setIsAppImportanceLocked(
@@ -5460,6 +5474,22 @@
 
                     mSnoozeHelper.dump(pw, filter);
                 }
+
+                // Log delayed notification cancels
+                pw.println();
+                pw.println("  Delayed notification cancels:");
+                if (mDelayedCancelations.isEmpty()) {
+                    pw.println("    None");
+                } else {
+                    Set<NotificationRecord> delayedKeys = mDelayedCancelations.keySet();
+                    for (NotificationRecord record : delayedKeys) {
+                        ArrayList<CancelNotificationRunnable> queuedCancels =
+                                mDelayedCancelations.get(record);
+                        pw.println("    (" + queuedCancels.size() + ") cancels enqueued for"
+                                + record.getKey());
+                    }
+                }
+                pw.println();
             }
 
             if (!zenOnly) {
@@ -5678,7 +5708,7 @@
 
         final StatusBarNotification n = new StatusBarNotification(
                 pkg, opPkg, id, tag, notificationUid, callingPid, notification,
-                user, null, System.currentTimeMillis());
+                user, null, mSystemClock.currentTimeMillis());
 
         // setup local book-keeping
         String channelId = notification.getChannelId();
@@ -6011,7 +6041,7 @@
                     final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);
                     if (appEnqueueRate > mMaxPackageEnqueueRate) {
                         mUsageStats.registerOverRateQuota(pkg);
-                        final long now = SystemClock.elapsedRealtime();
+                        final long now = mSystemClock.elapsedRealtime();
                         if ((now - mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) {
                             Slog.e(TAG, "Package enqueue rate is " + appEnqueueRate
                                     + ". Shedding " + r.getSbn().getKey() + ". package=" + pkg);
@@ -6223,7 +6253,73 @@
             this.mRank = rank;
             this.mCount = count;
             this.mListener = listener;
-            this.mWhen = System.currentTimeMillis();
+            this.mWhen = mSystemClock.currentTimeMillis();
+        }
+
+        // Move the work to this function so it can be called from PostNotificationRunnable
+        private void doNotificationCancelLocked() {
+            // Look for the notification in the posted list, since we already checked enqueued.
+            String listenerName = mListener == null ? null : mListener.component.toShortString();
+            NotificationRecord r =
+                    findNotificationByListLocked(mNotificationList, mPkg, mTag, mId, mUserId);
+            if (r != null) {
+                // The notification was found, check if it should be removed.
+
+                // Ideally we'd do this in the caller of this method. However, that would
+                // require the caller to also find the notification.
+                if (mReason == REASON_CLICK) {
+                    mUsageStats.registerClickedByUser(r);
+                }
+
+                if (mReason == REASON_LISTENER_CANCEL
+                        && (r.getNotification().flags & FLAG_BUBBLE) != 0) {
+                    mNotificationDelegate.onBubbleNotificationSuppressionChanged(
+                            r.getKey(), /* suppressed */ true);
+                    return;
+                }
+
+                if ((r.getNotification().flags & mMustHaveFlags) != mMustHaveFlags) {
+                    return;
+                }
+                if ((r.getNotification().flags & mMustNotHaveFlags) != 0) {
+                    return;
+                }
+
+                // Bubbled children get to stick around if the summary was manually cancelled
+                // (user removed) from systemui.
+                FlagChecker childrenFlagChecker = null;
+                if (mReason == REASON_CANCEL
+                        || mReason == REASON_CLICK
+                        || mReason == REASON_CANCEL_ALL) {
+                    childrenFlagChecker = (flags) -> {
+                        if ((flags & FLAG_BUBBLE) != 0) {
+                            return false;
+                        }
+                        return true;
+                    };
+                }
+
+                // Cancel the notification.
+                boolean wasPosted = removePreviousFromNotificationListsLocked(r, mWhen);
+                cancelNotificationLocked(
+                        r, mSendDelete, mReason, mRank, mCount, wasPosted, listenerName);
+                cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName,
+                        mSendDelete, childrenFlagChecker, mReason);
+                updateLightsLocked();
+                if (mShortcutHelper != null) {
+                    mShortcutHelper.maybeListenForShortcutChangesForBubbles(r,
+                            true /* isRemoved */,
+                            mHandler);
+                }
+            } else {
+                // No notification was found, assume that it is snoozed and cancel it.
+                if (mReason != REASON_SNOOZED) {
+                    final boolean wasSnoozed = mSnoozeHelper.cancel(mUserId, mPkg, mTag, mId);
+                    if (wasSnoozed) {
+                        handleSavePolicyFile();
+                    }
+                }
+            }
         }
 
         @Override
@@ -6239,91 +6335,24 @@
                 // chance to post yet.
                 List<NotificationRecord> enqueued = findEnqueuedNotificationsForCriteria(
                         mPkg, mTag, mId, mUserId);
-                boolean repost = false;
                 if (enqueued.size() > 0) {
-                    // Found something, let's see what it was
-                    repost = true;
-                    // If all enqueues happened before this cancel then wait for them to happen,
-                    // otherwise we should let this cancel through so the next enqueue happens
-                    for (NotificationRecord r : enqueued) {
-                        if (r.mUpdateTimeMs > mWhen) {
-                            // At least one enqueue was posted after the cancel, so we're invalid
-                            Slog.i(TAG, "notification cancel ignored due to newer enqueued entry"
-                                    + "key=" + r.getSbn().getKey());
-                            return;
-                        }
+                    // We have found notifications that were enqueued before this cancel, but not
+                    // yet posted. Attach this cancel to the last enqueue (the most recent), and
+                    // we will be executed in that notification's PostNotificationRunnable
+                    NotificationRecord enqueuedToAttach = enqueued.get(enqueued.size() - 1);
+
+                    ArrayList<CancelNotificationRunnable> delayed =
+                            mDelayedCancelations.get(enqueuedToAttach);
+                    if (delayed == null) {
+                        delayed = new ArrayList<>();
                     }
-                }
-                if (repost) {
-                    mHandler.post(this);
+
+                    delayed.add(this);
+                    mDelayedCancelations.put(enqueuedToAttach, delayed);
                     return;
                 }
 
-                // Look for the notification in the posted list, since we already checked enqueued.
-                NotificationRecord r =
-                        findNotificationByListLocked(mNotificationList, mPkg, mTag, mId, mUserId);
-                if (r != null) {
-                    // The notification was found, check if it should be removed.
-
-                    // Ideally we'd do this in the caller of this method. However, that would
-                    // require the caller to also find the notification.
-                    if (mReason == REASON_CLICK) {
-                        mUsageStats.registerClickedByUser(r);
-                    }
-
-                    if (mReason == REASON_LISTENER_CANCEL
-                        && (r.getNotification().flags & FLAG_BUBBLE) != 0) {
-                        mNotificationDelegate.onBubbleNotificationSuppressionChanged(
-                            r.getKey(), /* suppressed */ true);
-                        return;
-                    }
-
-                    if ((r.getNotification().flags & mMustHaveFlags) != mMustHaveFlags) {
-                        return;
-                    }
-                    if ((r.getNotification().flags & mMustNotHaveFlags) != 0) {
-                        return;
-                    }
-                    if (r.getUpdateTimeMs() > mWhen) {
-                        // In this case, a post must have slipped by when this runnable reposted
-                        return;
-                    }
-
-                    // Bubbled children get to stick around if the summary was manually cancelled
-                    // (user removed) from systemui.
-                    FlagChecker childrenFlagChecker = null;
-                    if (mReason == REASON_CANCEL
-                            || mReason == REASON_CLICK
-                            || mReason == REASON_CANCEL_ALL) {
-                        childrenFlagChecker = (flags) -> {
-                            if ((flags & FLAG_BUBBLE) != 0) {
-                                return false;
-                            }
-                            return true;
-                        };
-                    }
-
-                    // Cancel the notification.
-                    boolean wasPosted = removeFromNotificationListsLocked(r);
-                    cancelNotificationLocked(
-                            r, mSendDelete, mReason, mRank, mCount, wasPosted, listenerName);
-                    cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName,
-                            mSendDelete, childrenFlagChecker, mReason);
-                    updateLightsLocked();
-                    if (mShortcutHelper != null) {
-                        mShortcutHelper.maybeListenForShortcutChangesForBubbles(r,
-                                true /* isRemoved */,
-                                mHandler);
-                    }
-                } else {
-                    // No notification was found, assume that it is snoozed and cancel it.
-                    if (mReason != REASON_SNOOZED) {
-                        final boolean wasSnoozed = mSnoozeHelper.cancel(mUserId, mPkg, mTag, mId);
-                        if (wasSnoozed) {
-                            handleSavePolicyFile();
-                        }
-                    }
-                }
+                doNotificationCancelLocked();
             }
         }
     }
@@ -6346,7 +6375,7 @@
                         mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
                                 r.getUser().getIdentifier(),
                                 r.getSbn().getPackageName(), r.getSbn().getKey());
-                final long currentTime = System.currentTimeMillis();
+                final long currentTime = mSystemClock.currentTimeMillis();
                 if (snoozeAt.longValue() > currentTime) {
                     (new SnoozeNotificationRunnable(r.getSbn().getKey(),
                             snoozeAt.longValue() - currentTime, null)).snoozeLocked(r);
@@ -6406,18 +6435,29 @@
                             enqueueStatus);
                 }
 
-                // tell the assistant service about the notification
-                if (mAssistants.isEnabled()) {
-                    mAssistants.onNotificationEnqueuedLocked(r);
-                    mHandler.postDelayed(new PostNotificationRunnable(r.getKey()),
-                            DELAY_FOR_ASSISTANT_TIME);
-                } else {
-                    mHandler.post(new PostNotificationRunnable(r.getKey()));
-                }
+                postPostNotificationRunnableMaybeDelayedLocked(
+                        r, new PostNotificationRunnable(r.getKey()));
             }
         }
     }
 
+    /**
+     * Mainly needed as a hook for tests which require setting up enqueued-but-not-posted
+     * notification records
+     */
+    @GuardedBy("mNotificationLock")
+    protected void postPostNotificationRunnableMaybeDelayedLocked(
+            NotificationRecord r,
+            PostNotificationRunnable runnable) {
+        // tell the assistant service about the notification
+        if (mAssistants.isEnabled()) {
+            mAssistants.onNotificationEnqueuedLocked(r);
+            mHandler.postDelayed(runnable, DELAY_FOR_ASSISTANT_TIME);
+        } else {
+            mHandler.post(runnable);
+        }
+    }
+
     @GuardedBy("mNotificationLock")
     boolean isPackagePausedOrSuspended(String pkg, int uid) {
         boolean isPaused;
@@ -6566,13 +6606,23 @@
                             buzzBeepBlinkLoggingCode, getGroupInstanceId(n.getGroupKey()));
                 } finally {
                     int N = mEnqueuedNotifications.size();
+                    NotificationRecord enqueued = null;
                     for (int i = 0; i < N; i++) {
-                        final NotificationRecord enqueued = mEnqueuedNotifications.get(i);
+                        enqueued = mEnqueuedNotifications.get(i);
                         if (Objects.equals(key, enqueued.getKey())) {
                             mEnqueuedNotifications.remove(i);
                             break;
                         }
                     }
+
+                    // If the enqueued notification record had a cancel attached after it, execute
+                    // it right now
+                    if (enqueued != null && mDelayedCancelations.get(enqueued) != null) {
+                        for (CancelNotificationRunnable r : mDelayedCancelations.get(enqueued)) {
+                            r.doNotificationCancelLocked();
+                        }
+                        mDelayedCancelations.remove(enqueued);
+                    }
                 }
             }
         }
@@ -6801,7 +6851,8 @@
                             .putExtra(EXTRA_KEY, record.getKey()),
                     PendingIntent.FLAG_UPDATE_CURRENT);
             mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                    SystemClock.elapsedRealtime() + record.getNotification().getTimeoutAfter(), pi);
+                    mSystemClock.elapsedRealtime() + record.getNotification().getTimeoutAfter(),
+                    pi);
         }
     }
 
@@ -7329,7 +7380,7 @@
                     || visibilityChanged
                     || interruptiveChanged;
             if (interceptBefore && !record.isIntercepted()
-                    && record.isNewEnoughForAlerting(System.currentTimeMillis())) {
+                    && record.isNewEnoughForAlerting(mSystemClock.currentTimeMillis())) {
                 buzzBeepBlinkLocked(record);
             }
         }
@@ -7616,6 +7667,34 @@
         return wasPosted;
     }
 
+    /**
+     * Similar to the above method, removes all NotificationRecords with the same key as the given
+     * NotificationRecord, but skips any records which are newer than the given one.
+     */
+    private boolean removePreviousFromNotificationListsLocked(NotificationRecord r,
+            long removeBefore) {
+        // Remove notification records that occurred before the given record from both lists,
+        // specifically allowing newer ones to respect ordering
+        boolean wasPosted = false;
+        List<NotificationRecord> matching =
+                findNotificationsByListLocked(mNotificationList, r.getKey());
+        for (NotificationRecord record : matching) {
+            // We don't need to check against update time for posted notifs
+            mNotificationList.remove(record);
+            mNotificationsByKey.remove(record.getSbn().getKey());
+            wasPosted = true;
+        }
+
+        matching = findNotificationsByListLocked(mEnqueuedNotifications, r.getKey());
+        for (NotificationRecord record : matching) {
+            if (record.getUpdateTimeMs() <= removeBefore) {
+                mNotificationList.remove(record);
+            }
+        }
+
+        return wasPosted;
+    }
+
     @GuardedBy("mNotificationLock")
     private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete,
             @NotificationListenerService.NotificationCancelReason int reason,
@@ -7731,7 +7810,7 @@
         // Save it for users of getHistoricalNotifications()
         mArchive.record(r.getSbn(), reason);
 
-        final long now = System.currentTimeMillis();
+        final long now = mSystemClock.currentTimeMillis();
         final LogMaker logMaker = r.getItemLogMaker()
                 .setType(MetricsEvent.TYPE_DISMISS)
                 .setSubtype(reason);
@@ -8287,6 +8366,21 @@
         return null;
     }
 
+    @GuardedBy("mNotificationLock")
+    private List<NotificationRecord> findNotificationsByListLocked(
+            ArrayList<NotificationRecord> list,
+            String key) {
+        List<NotificationRecord> matching = new ArrayList<>();
+        final int n = list.size();
+        for (int i = 0; i < n; i++) {
+            NotificationRecord r = list.get(i);
+            if (key.equals(r.getKey())) {
+                matching.add(r);
+            }
+        }
+        return matching;
+    }
+
     /**
      * There may be multiple records that match your criteria. For instance if there have been
      * multiple notifications posted which are enqueued for the same pkg, tag, id, userId. This
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index daec4d7..29881cc 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -549,11 +549,10 @@
                         synchronized (mLock) {
                             mDisplayContent.calculateSystemGestureExclusion(
                                     excludedRegion, null /* outUnrestricted */);
-                            final boolean sideAllowed = mNavigationBarAlwaysShowOnSideGesture
-                                    || mNavigationBarPosition == NAV_BAR_RIGHT;
-                            if (mNavigationBar != null && sideAllowed
-                                    && !mSystemGestures.currentGestureStartedInRegion(
-                                            excludedRegion)) {
+                            final boolean excluded =
+                                    mSystemGestures.currentGestureStartedInRegion(excludedRegion);
+                            if (mNavigationBar != null && (mNavigationBarPosition == NAV_BAR_RIGHT
+                                    || !excluded && mNavigationBarAlwaysShowOnSideGesture)) {
                                 requestTransientBars(mNavigationBar);
                             }
                             checkAltBarSwipeForTransientBars(ALT_BAR_RIGHT);
@@ -567,11 +566,10 @@
                         synchronized (mLock) {
                             mDisplayContent.calculateSystemGestureExclusion(
                                     excludedRegion, null /* outUnrestricted */);
-                            final boolean sideAllowed = mNavigationBarAlwaysShowOnSideGesture
-                                    || mNavigationBarPosition == NAV_BAR_LEFT;
-                            if (mNavigationBar != null && sideAllowed
-                                    && !mSystemGestures.currentGestureStartedInRegion(
-                                            excludedRegion)) {
+                            final boolean excluded =
+                                    mSystemGestures.currentGestureStartedInRegion(excludedRegion);
+                            if (mNavigationBar != null && (mNavigationBarPosition == NAV_BAR_LEFT
+                                    || !excluded && mNavigationBarAlwaysShowOnSideGesture)) {
                                 requestTransientBars(mNavigationBar);
                             }
                             checkAltBarSwipeForTransientBars(ALT_BAR_LEFT);
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 72cd32f..14316fa 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -994,9 +994,6 @@
             }
         }
 
-        // Remove all deferred displays stacks, tasks, and activities.
-        handleCompleteDeferredRemoval();
-
         forAllDisplays(dc -> {
             dc.getInputMonitor().updateInputWindowsLw(true /*force*/);
             dc.updateSystemGestureExclusion();
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 9d0bac9..9d07304 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -143,6 +143,9 @@
         ProtoLog.i(WM_SHOW_TRANSACTIONS, ">>> OPEN TRANSACTION animate");
         mService.openSurfaceTransaction();
         try {
+            // Remove all deferred displays, tasks, and activities.
+            mService.mRoot.handleCompleteDeferredRemoval();
+
             final AccessibilityController accessibilityController =
                     mService.mAccessibilityController;
             final int numDisplays = mDisplayContentsAnimators.size();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index dd5afdd..6cd6a57 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1060,7 +1060,7 @@
                 // descendant. E.g. if a display is pending to be removed because it contains an
                 // activity with {@link ActivityRecord#mIsExiting} is true, the display may be
                 // removed when completing the removal of the last activity from
-                // {@link ActivityRecord#checkCompleteDeferredRemoval}.
+                // {@link ActivityRecord#handleCompleteDeferredRemoval}.
                 return false;
             }
         }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c6b93d6..ce3cdea 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -641,6 +641,11 @@
     final boolean mIsWatch;
 
     /**
+     * Whether or not this device is an automotive.
+     */
+    private final boolean mIsAutomotive;
+
+    /**
      * Whether this device has the telephony feature.
      */
     final boolean mHasTelephonyFeature;
@@ -2567,6 +2572,8 @@
                 .hasSystemFeature(PackageManager.FEATURE_WATCH);
         mHasTelephonyFeature = mInjector.getPackageManager()
                 .hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
+        mIsAutomotive = mInjector.getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
         mBackgroundHandler = BackgroundThread.getHandler();
 
         // Needed when mHasFeature == false, because it controls the certificate warning text.
@@ -6080,9 +6087,16 @@
 
                 // Require authentication for the device or profile
                 if (userToLock == UserHandle.USER_ALL) {
-                    // Power off the display
-                    mInjector.powerManagerGoToSleep(SystemClock.uptimeMillis(),
-                            PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN, 0);
+                    if (mIsAutomotive) {
+                        if (VERBOSE_LOG) {
+                            Slog.v(LOG_TAG, "lockNow(): not powering off display on automotive"
+                                    + " build");
+                        }
+                    } else {
+                        // Power off the display
+                        mInjector.powerManagerGoToSleep(SystemClock.uptimeMillis(),
+                                PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN, 0);
+                    }
                     mInjector.getIWindowManager().lockNow(null);
                 } else {
                     mInjector.getTrustManager().setDeviceLockedForUser(userToLock, true);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/GestureManifoldTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/GestureManifoldTest.java
index 538e2d5..0e78785 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/GestureManifoldTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/GestureManifoldTest.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.graphics.Point;
 import android.graphics.PointF;
+import android.os.Handler;
 import android.view.MotionEvent;
 
 import androidx.test.InstrumentationRegistry;
@@ -56,7 +57,8 @@
         // Construct a testable GestureManifold.
         mResultListener = mock(GestureManifold.Listener.class);
         mState = new TouchState();
-        mManifold = new GestureManifold(context, mResultListener, mState);
+        Handler handler = new Handler(context.getMainLooper());
+        mManifold = new GestureManifold(context, mResultListener, mState, handler);
         // Play the role of touch explorer in updating the shared state.
         when(mResultListener.onGestureStarted()).thenReturn(onGestureStarted());
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
index afd10dd..2e0f199 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -103,6 +103,7 @@
     NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake();
     private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake(
             1 << 30);
+    private InjectableSystemClock mSystemClock = new FakeSystemClock();
 
     private NotificationManagerService mService;
     private String mPkg = "com.android.server.notification";
@@ -154,7 +155,7 @@
         assertTrue(accessibilityManager.isEnabled());
 
         mService = spy(new NotificationManagerService(getContext(), mNotificationRecordLogger,
-                mNotificationInstanceIdSequence));
+                mSystemClock, mNotificationInstanceIdSequence));
         mService.setAudioManager(mAudioManager);
         mService.setVibrator(mVibrator);
         mService.setSystemReady(true);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/FakeSystemClock.java b/services/tests/uiservicestests/src/com/android/server/notification/FakeSystemClock.java
new file mode 100644
index 0000000..c960f17
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/FakeSystemClock.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A fake {@link InjectableSystemClock}
+ *
+ * Attempts to simulate the behavior of a real system clock. Time can be moved forward but not
+ * backwards. uptimeMillis, elapsedRealtime, and currentThreadTimeMillis are all kept in sync.
+ *
+ * Unless otherwise specified, uptimeMillis and elapsedRealtime will advance the same amount with
+ * every call to {@link #advanceTime(long)}. Thread time always lags by 50% of the uptime
+ * advancement to simulate time loss due to scheduling.
+ *
+ * @hide
+ */
+public class FakeSystemClock implements InjectableSystemClock {
+    private long mUptimeMillis = 10000;
+    private long mElapsedRealtime = 10000;
+    private long mCurrentThreadTimeMillis = 10000;
+
+    private long mCurrentTimeMillis = 1555555500000L;
+
+    private final List<ClockTickListener> mListeners = new ArrayList<>();
+    @Override
+    public long uptimeMillis() {
+        return mUptimeMillis;
+    }
+
+    @Override
+    public long elapsedRealtime() {
+        return mElapsedRealtime;
+    }
+
+    @Override
+    public long elapsedRealtimeNanos() {
+        return mElapsedRealtime * 1000000 + 447;
+    }
+
+    @Override
+    public long currentThreadTimeMillis() {
+        return mCurrentThreadTimeMillis;
+    }
+
+    @Override
+    public long currentTimeMillis() {
+        return mCurrentTimeMillis;
+    }
+
+    public void setUptimeMillis(long uptime) {
+        advanceTime(uptime - mUptimeMillis);
+    }
+
+    public void setCurrentTimeMillis(long millis) {
+        mCurrentTimeMillis = millis;
+    }
+
+    public void advanceTime(long uptime) {
+        advanceTime(uptime, 0);
+    }
+
+    public void advanceTime(long uptime, long sleepTime) {
+        if (uptime < 0 || sleepTime < 0) {
+            throw new IllegalArgumentException("Time cannot go backwards.");
+        }
+
+        if (uptime > 0 || sleepTime > 0) {
+            mUptimeMillis += uptime;
+            mElapsedRealtime += uptime + sleepTime;
+            mCurrentTimeMillis += uptime + sleepTime;
+
+            mCurrentThreadTimeMillis += Math.ceil(uptime * 0.5);
+
+            for (ClockTickListener listener : mListeners) {
+                listener.onClockTick();
+            }
+        }
+    }
+
+    public void addListener(ClockTickListener listener) {
+        mListeners.add(listener);
+    }
+
+    public void removeListener(ClockTickListener listener) {
+        mListeners.remove(listener);
+    }
+
+    public interface ClockTickListener {
+        void onClockTick();
+    }
+
+    private static final long START_TIME = 10000;
+}
+
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 52e0818..7f0b4db 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -199,6 +199,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.function.Consumer;
 
 
@@ -287,18 +288,26 @@
             1 << 30);
     @Mock
     StatusBarManagerInternal mStatusBar;
+    private final FakeSystemClock mSystemClock = new FakeSystemClock();
 
     // Use a Testable subclass so we can simulate calls from the system without failing.
     private static class TestableNotificationManagerService extends NotificationManagerService {
         int countSystemChecks = 0;
         boolean isSystemUid = true;
         int countLogSmartSuggestionsVisible = 0;
+        // If true, don't enqueue the PostNotificationRunnables, just trap them
+        boolean trapEnqueuedNotifications = false;
+        final ArrayList<NotificationManagerService.PostNotificationRunnable> trappedRunnables =
+                new ArrayList<>();
         @Nullable
         NotificationAssistantAccessGrantedCallback mNotificationAssistantAccessGrantedCallback;
 
-        TestableNotificationManagerService(Context context, NotificationRecordLogger logger,
+        TestableNotificationManagerService(
+                Context context,
+                NotificationRecordLogger logger,
+                InjectableSystemClock systemClock,
                 InstanceIdSequence notificationInstanceIdSequence) {
-            super(context, logger, notificationInstanceIdSequence);
+            super(context, logger, systemClock, notificationInstanceIdSequence);
         }
 
         RankingHelper getRankingHelper() {
@@ -353,6 +362,23 @@
             return new String[] {PKG_O};
         }
 
+        @Override
+        protected void postPostNotificationRunnableMaybeDelayedLocked(NotificationRecord record,
+                PostNotificationRunnable runnable) {
+            if (trapEnqueuedNotifications) {
+                trappedRunnables.add(runnable);
+                return;
+            }
+
+            super.postPostNotificationRunnableMaybeDelayedLocked(record, runnable);
+        }
+
+        void drainTrappedRunnableQueue() {
+            for (Runnable r : trappedRunnables) {
+                getWorkHandler().post(r);
+            }
+        }
+
         private void setNotificationAssistantAccessGrantedCallback(
                 @Nullable NotificationAssistantAccessGrantedCallback callback) {
             this.mNotificationAssistantAccessGrantedCallback = callback;
@@ -402,7 +428,7 @@
         doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any());
 
         mService = new TestableNotificationManagerService(mContext, mNotificationRecordLogger,
-                mNotificationInstanceIdSequence);
+                mSystemClock, mNotificationInstanceIdSequence);
 
         // Use this testable looper.
         mTestableLooper = TestableLooper.get(this);
@@ -1344,10 +1370,10 @@
         final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", sbn.getId(),
                 sbn.getNotification(), sbn.getUserId());
-        Thread.sleep(1);  // make sure the system clock advances before the next step
+        mSystemClock.advanceTime(1);
         // THEN it is canceled
         mBinderService.cancelNotificationWithTag(PKG, PKG, "tag", sbn.getId(), sbn.getUserId());
-        Thread.sleep(1);  // here too
+        mSystemClock.advanceTime(1);
         // THEN it is posted again (before the cancel has a chance to finish)
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", sbn.getId(),
                 sbn.getNotification(), sbn.getUserId());
@@ -1362,6 +1388,60 @@
     }
 
     @Test
+    public void testChangeSystemTimeAfterPost_thenCancel_noFgs() throws Exception {
+        // GIVEN time X
+        mSystemClock.setCurrentTimeMillis(10000);
+
+        // GIVEN a notification is posted
+        final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", sbn.getId(),
+                sbn.getNotification(), sbn.getUserId());
+        mSystemClock.advanceTime(1);
+        waitForIdle();
+
+        // THEN the system time is changed to an earlier time
+        mSystemClock.setCurrentTimeMillis(5000);
+
+        // THEN a cancel is requested
+        mBinderService.cancelNotificationWithTag(PKG, PKG, "tag", sbn.getId(), sbn.getUserId());
+        waitForIdle();
+
+        // It should work
+        StatusBarNotification[] notifs =
+                mBinderService.getActiveNotifications(PKG);
+        assertEquals(0, notifs.length);
+        assertEquals(0, mService.getNotificationRecordCount());
+    }
+
+    @Test
+    public void testChangeSystemTimeAfterPost_thenCancel_fgs() throws Exception {
+        // GIVEN time X
+        mSystemClock.setCurrentTimeMillis(10000);
+
+        // GIVEN a notification is posted
+        final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
+        sbn.getNotification().flags =
+                Notification.FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE;
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", sbn.getId(),
+                sbn.getNotification(), sbn.getUserId());
+        mSystemClock.advanceTime(1);
+        waitForIdle();
+
+        // THEN the system time is changed to an earlier time
+        mSystemClock.setCurrentTimeMillis(5000);
+
+        // THEN a cancel is requested
+        mBinderService.cancelNotificationWithTag(PKG, PKG, "tag", sbn.getId(), sbn.getUserId());
+        waitForIdle();
+
+        // It should work
+        StatusBarNotification[] notifs =
+                mBinderService.getActiveNotifications(PKG);
+        assertEquals(0, notifs.length);
+        assertEquals(0, mService.getNotificationRecordCount());
+    }
+
+    @Test
     public void testCancelNotificationWhilePostedAndEnqueued() throws Exception {
         mBinderService.enqueueNotificationWithTag(PKG, PKG,
                 "testCancelNotificationWhilePostedAndEnqueued", 0,
@@ -1383,6 +1463,56 @@
     }
 
     @Test
+    public void testDelayCancelWhenEnqueuedHasNotPosted() throws Exception {
+        // Don't allow PostNotificationRunnables to execute so we can set up problematic state
+        mService.trapEnqueuedNotifications = true;
+        // GIVEN an enqueued notification
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testDelayCancelWhenEnqueuedHasNotPosted", 0,
+                generateNotificationRecord(null).getNotification(), 0);
+        mSystemClock.advanceTime(1);
+        // WHEN a cancel is requested before it has posted
+        mBinderService.cancelNotificationWithTag(PKG, PKG,
+                "testDelayCancelWhenEnqueuedHasNotPosted", 0, 0);
+
+        waitForIdle();
+
+        // THEN the cancel notification runnable is captured and associated with that record
+        ArrayMap<NotificationRecord,
+                ArrayList<NotificationManagerService.CancelNotificationRunnable>> delayed =
+                        mService.mDelayedCancelations;
+        Set<NotificationRecord> keySet = delayed.keySet();
+        assertEquals(1, keySet.size());
+    }
+
+    @Test
+    public void testDelayedCancelsExecuteAfterPost() throws Exception {
+        // Don't allow PostNotificationRunnables to execute so we can set up problematic state
+        mService.trapEnqueuedNotifications = true;
+        // GIVEN an enqueued notification
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testDelayCancelWhenEnqueuedHasNotPosted", 0,
+                generateNotificationRecord(null).getNotification(), 0);
+        mSystemClock.advanceTime(1);
+        // WHEN a cancel is requested before it has posted
+        mBinderService.cancelNotificationWithTag(PKG, PKG,
+                "testDelayCancelWhenEnqueuedHasNotPosted", 0, 0);
+
+        waitForIdle();
+
+        // We're now in a state with an a notification awaiting PostNotificationRunnable to execute
+        // WHEN the PostNotificationRunnable is allowed to execute
+        mService.drainTrappedRunnableQueue();
+        waitForIdle();
+
+        // THEN the cancel executes and the notification is removed
+        StatusBarNotification[] notifs =
+                mBinderService.getActiveNotifications(PKG);
+        assertEquals(0, notifs.length);
+        assertEquals(0, mService.getNotificationRecordCount());
+    }
+
+    @Test
     public void testCancelNotificationsFromListenerImmediatelyAfterEnqueue() throws Exception {
         NotificationRecord r = generateNotificationRecord(null);
         final StatusBarNotification sbn = r.getSbn();
@@ -2529,8 +2659,9 @@
 
     @Test
     public void testHasCompanionDevice_noService() {
-        mService = new TestableNotificationManagerService(mContext,  mNotificationRecordLogger,
-                mNotificationInstanceIdSequence);
+        mService =
+                new TestableNotificationManagerService(mContext, mNotificationRecordLogger,
+                mSystemClock, mNotificationInstanceIdSequence);
 
         assertFalse(mService.hasCompanionDevice(mListener));
     }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index 3281c3f..ac2c619 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -103,12 +103,14 @@
     private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake(
             1 << 30);
     private List<UserInfo> mUsers;
+    private InjectableSystemClock mSystemClock = new FakeSystemClock();
 
     private static class TestableNotificationManagerService extends NotificationManagerService {
         TestableNotificationManagerService(Context context,
                 NotificationRecordLogger logger,
+                InjectableSystemClock systemClock,
                 InstanceIdSequence notificationInstanceIdSequence) {
-            super(context, logger, notificationInstanceIdSequence);
+            super(context, logger, systemClock, notificationInstanceIdSequence);
         }
 
         @Override
@@ -136,7 +138,7 @@
         when(mUm.getUsers()).thenReturn(mUsers);
 
         mService = new TestableNotificationManagerService(mContext, mNotificationRecordLogger,
-                mNotificationInstanceIdSequence);
+                mSystemClock, mNotificationInstanceIdSequence);
         mRoleObserver = mService.new RoleObserver(mRoleManager, mPm, mExecutor);
 
         try {