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 {