Merge "Fix back animation doesn't work when triggered a second time" into udc-dev
diff --git a/core/api/current.txt b/core/api/current.txt
index e10fbf2..288ab47 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -377,7 +377,7 @@
public static final class R.attr {
ctor public R.attr();
field public static final int absListViewStyle = 16842858; // 0x101006a
- field public static final int accessibilityDataSensitive;
+ field public static final int accessibilityDataSensitive = 16844407; // 0x1010677
field public static final int accessibilityEventTypes = 16843648; // 0x1010380
field public static final int accessibilityFeedbackType = 16843650; // 0x1010382
field public static final int accessibilityFlags = 16843652; // 0x1010384
@@ -445,12 +445,12 @@
field public static final int allowGameFpsOverride = 16844378; // 0x101065a
field public static final int allowNativeHeapPointerTagging = 16844306; // 0x1010612
field public static final int allowParallelSyncs = 16843570; // 0x1010332
- field public static final int allowSharedIsolatedProcess;
+ field public static final int allowSharedIsolatedProcess = 16844413; // 0x101067d
field public static final int allowSingleTap = 16843353; // 0x1010259
field public static final int allowTaskReparenting = 16843268; // 0x1010204
field public static final int allowUndo = 16843999; // 0x10104df
field public static final int allowUntrustedActivityEmbedding = 16844393; // 0x1010669
- field public static final int allowUpdateOwnership;
+ field public static final int allowUpdateOwnership = 16844416; // 0x1010680
field public static final int alpha = 16843551; // 0x101031f
field public static final int alphabeticModifiers = 16844110; // 0x101054e
field public static final int alphabeticShortcut = 16843235; // 0x10101e3
@@ -556,7 +556,7 @@
field public static final int canTakeScreenshot = 16844303; // 0x101060f
field public static final int candidatesTextStyleSpans = 16843312; // 0x1010230
field public static final int cantSaveState = 16844142; // 0x101056e
- field public static final int capability;
+ field public static final int capability = 16844423; // 0x1010687
field @Deprecated public static final int capitalize = 16843113; // 0x1010169
field public static final int category = 16843752; // 0x10103e8
field public static final int centerBright = 16842956; // 0x10100cc
@@ -741,7 +741,7 @@
field public static final int ellipsize = 16842923; // 0x10100ab
field public static final int ems = 16843096; // 0x1010158
field public static final int enableOnBackInvokedCallback = 16844396; // 0x101066c
- field public static final int enableTextStylingShortcuts;
+ field public static final int enableTextStylingShortcuts = 16844408; // 0x1010678
field public static final int enableVrMode = 16844069; // 0x1010525
field public static final int enabled = 16842766; // 0x101000e
field public static final int end = 16843996; // 0x10104dc
@@ -810,7 +810,7 @@
field public static final int focusableInTouchMode = 16842971; // 0x10100db
field public static final int focusedByDefault = 16844100; // 0x1010544
field @Deprecated public static final int focusedMonthDateColor = 16843587; // 0x1010343
- field public static final int focusedSearchResultHighlightColor;
+ field public static final int focusedSearchResultHighlightColor = 16844419; // 0x1010683
field public static final int font = 16844082; // 0x1010532
field public static final int fontFamily = 16843692; // 0x10103ac
field public static final int fontFeatureSettings = 16843959; // 0x10104b7
@@ -896,10 +896,10 @@
field public static final int hand_secondTintMode = 16844349; // 0x101063d
field public static final int handle = 16843354; // 0x101025a
field public static final int handleProfiling = 16842786; // 0x1010022
- field public static final int handwritingBoundsOffsetBottom;
- field public static final int handwritingBoundsOffsetLeft;
- field public static final int handwritingBoundsOffsetRight;
- field public static final int handwritingBoundsOffsetTop;
+ field public static final int handwritingBoundsOffsetBottom = 16844406; // 0x1010676
+ field public static final int handwritingBoundsOffsetLeft = 16844403; // 0x1010673
+ field public static final int handwritingBoundsOffsetRight = 16844405; // 0x1010675
+ field public static final int handwritingBoundsOffsetTop = 16844404; // 0x1010674
field public static final int hapticFeedbackEnabled = 16843358; // 0x101025e
field public static final int hardwareAccelerated = 16843475; // 0x10102d3
field public static final int hasCode = 16842764; // 0x101000c
@@ -986,7 +986,7 @@
field public static final int isAlwaysSyncable = 16843571; // 0x1010333
field public static final int isAsciiCapable = 16843753; // 0x10103e9
field public static final int isAuxiliary = 16843647; // 0x101037f
- field public static final int isCredential;
+ field public static final int isCredential = 16844417; // 0x1010681
field public static final int isDefault = 16843297; // 0x1010221
field public static final int isFeatureSplit = 16844123; // 0x101055b
field public static final int isGame = 16843764; // 0x10103f4
@@ -1021,8 +1021,8 @@
field @Deprecated public static final int keyTextSize = 16843316; // 0x1010234
field @Deprecated public static final int keyWidth = 16843325; // 0x101023d
field public static final int keyboardLayout = 16843691; // 0x10103ab
- field public static final int keyboardLayoutType;
- field public static final int keyboardLocale;
+ field public static final int keyboardLayoutType = 16844415; // 0x101067f
+ field public static final int keyboardLocale = 16844414; // 0x101067e
field @Deprecated public static final int keyboardMode = 16843341; // 0x101024d
field public static final int keyboardNavigationCluster = 16844096; // 0x1010540
field public static final int keycode = 16842949; // 0x10100c5
@@ -1263,8 +1263,8 @@
field public static final int persistentDrawingCache = 16842990; // 0x10100ee
field public static final int persistentWhenFeatureAvailable = 16844131; // 0x1010563
field @Deprecated public static final int phoneNumber = 16843111; // 0x1010167
- field public static final int physicalKeyboardHintLanguageTag;
- field public static final int physicalKeyboardHintLayoutType;
+ field public static final int physicalKeyboardHintLanguageTag = 16844411; // 0x101067b
+ field public static final int physicalKeyboardHintLayoutType = 16844412; // 0x101067c
field public static final int pivotX = 16843189; // 0x10101b5
field public static final int pivotY = 16843190; // 0x10101b6
field public static final int pointerIcon = 16844041; // 0x1010509
@@ -1354,7 +1354,7 @@
field public static final int requireDeviceUnlock = 16843756; // 0x10103ec
field public static final int required = 16843406; // 0x101028e
field public static final int requiredAccountType = 16843734; // 0x10103d6
- field public static final int requiredDisplayCategory;
+ field public static final int requiredDisplayCategory = 16844409; // 0x1010679
field public static final int requiredFeature = 16844116; // 0x1010554
field public static final int requiredForAllUsers = 16843728; // 0x10103d0
field public static final int requiredNotFeature = 16844117; // 0x1010555
@@ -1422,7 +1422,7 @@
field public static final int searchHintIcon = 16843988; // 0x10104d4
field public static final int searchIcon = 16843907; // 0x1010483
field public static final int searchMode = 16843221; // 0x10101d5
- field public static final int searchResultHighlightColor;
+ field public static final int searchResultHighlightColor = 16844418; // 0x1010682
field public static final int searchSettingsDescription = 16843402; // 0x101028a
field public static final int searchSuggestAuthority = 16843222; // 0x10101d6
field public static final int searchSuggestIntentAction = 16843225; // 0x10101d9
@@ -1449,7 +1449,7 @@
field public static final int sessionService = 16843837; // 0x101043d
field public static final int settingsActivity = 16843301; // 0x1010225
field public static final int settingsSliceUri = 16844179; // 0x1010593
- field public static final int settingsSubtitle;
+ field public static final int settingsSubtitle = 16844422; // 0x1010686
field public static final int setupActivity = 16843766; // 0x10103f6
field public static final int shadowColor = 16843105; // 0x1010161
field public static final int shadowDx = 16843106; // 0x1010162
@@ -1555,7 +1555,7 @@
field public static final int strokeLineJoin = 16843788; // 0x101040c
field public static final int strokeMiterLimit = 16843789; // 0x101040d
field public static final int strokeWidth = 16843783; // 0x1010407
- field public static final int stylusHandwritingSettingsActivity;
+ field public static final int stylusHandwritingSettingsActivity = 16844420; // 0x1010684
field public static final int subMenuArrow = 16844019; // 0x10104f3
field public static final int submitBackground = 16843912; // 0x1010488
field public static final int subtitle = 16843473; // 0x10102d1
@@ -1861,7 +1861,7 @@
field public static final int windowMinWidthMajor = 16843606; // 0x1010356
field public static final int windowMinWidthMinor = 16843607; // 0x1010357
field public static final int windowNoDisplay = 16843294; // 0x101021e
- field public static final int windowNoMoveAnimation;
+ field public static final int windowNoMoveAnimation = 16844421; // 0x1010685
field public static final int windowNoTitle = 16842838; // 0x1010056
field @Deprecated public static final int windowOverscan = 16843727; // 0x10103cf
field public static final int windowReenterTransition = 16843951; // 0x10104af
@@ -1966,18 +1966,18 @@
field public static final int system_accent3_700 = 17170522; // 0x106005a
field public static final int system_accent3_800 = 17170523; // 0x106005b
field public static final int system_accent3_900 = 17170524; // 0x106005c
- field public static final int system_background_dark;
- field public static final int system_background_light;
- field public static final int system_control_activated_dark;
- field public static final int system_control_activated_light;
- field public static final int system_control_highlight_dark;
- field public static final int system_control_highlight_light;
- field public static final int system_control_normal_dark;
- field public static final int system_control_normal_light;
- field public static final int system_error_container_dark;
- field public static final int system_error_container_light;
- field public static final int system_error_dark;
- field public static final int system_error_light;
+ field public static final int system_background_dark = 17170581; // 0x1060095
+ field public static final int system_background_light = 17170538; // 0x106006a
+ field public static final int system_control_activated_dark = 17170599; // 0x10600a7
+ field public static final int system_control_activated_light = 17170556; // 0x106007c
+ field public static final int system_control_highlight_dark = 17170601; // 0x10600a9
+ field public static final int system_control_highlight_light = 17170558; // 0x106007e
+ field public static final int system_control_normal_dark = 17170600; // 0x10600a8
+ field public static final int system_control_normal_light = 17170557; // 0x106007d
+ field public static final int system_error_container_dark = 17170597; // 0x10600a5
+ field public static final int system_error_container_light = 17170554; // 0x106007a
+ field public static final int system_error_dark = 17170595; // 0x10600a3
+ field public static final int system_error_light = 17170552; // 0x1060078
field public static final int system_neutral1_0 = 17170461; // 0x106001d
field public static final int system_neutral1_10 = 17170462; // 0x106001e
field public static final int system_neutral1_100 = 17170464; // 0x1060020
@@ -2004,94 +2004,94 @@
field public static final int system_neutral2_700 = 17170483; // 0x1060033
field public static final int system_neutral2_800 = 17170484; // 0x1060034
field public static final int system_neutral2_900 = 17170485; // 0x1060035
- field public static final int system_on_background_dark;
- field public static final int system_on_background_light;
- field public static final int system_on_error_container_dark;
- field public static final int system_on_error_container_light;
- field public static final int system_on_error_dark;
- field public static final int system_on_error_light;
- field public static final int system_on_primary_container_dark;
- field public static final int system_on_primary_container_light;
- field public static final int system_on_primary_dark;
- field public static final int system_on_primary_fixed;
- field public static final int system_on_primary_fixed_variant;
- field public static final int system_on_primary_light;
- field public static final int system_on_secondary_container_dark;
- field public static final int system_on_secondary_container_light;
- field public static final int system_on_secondary_dark;
- field public static final int system_on_secondary_fixed;
- field public static final int system_on_secondary_fixed_variant;
- field public static final int system_on_secondary_light;
- field public static final int system_on_surface_dark;
- field public static final int system_on_surface_light;
- field public static final int system_on_surface_variant_dark;
- field public static final int system_on_surface_variant_light;
- field public static final int system_on_tertiary_container_dark;
- field public static final int system_on_tertiary_container_light;
- field public static final int system_on_tertiary_dark;
- field public static final int system_on_tertiary_fixed;
- field public static final int system_on_tertiary_fixed_variant;
- field public static final int system_on_tertiary_light;
- field public static final int system_outline_dark;
- field public static final int system_outline_light;
- field public static final int system_outline_variant_dark;
- field public static final int system_outline_variant_light;
- field public static final int system_palette_key_color_neutral_dark;
- field public static final int system_palette_key_color_neutral_light;
- field public static final int system_palette_key_color_neutral_variant_dark;
- field public static final int system_palette_key_color_neutral_variant_light;
- field public static final int system_palette_key_color_primary_dark;
- field public static final int system_palette_key_color_primary_light;
- field public static final int system_palette_key_color_secondary_dark;
- field public static final int system_palette_key_color_secondary_light;
- field public static final int system_palette_key_color_tertiary_dark;
- field public static final int system_palette_key_color_tertiary_light;
- field public static final int system_primary_container_dark;
- field public static final int system_primary_container_light;
- field public static final int system_primary_dark;
- field public static final int system_primary_fixed;
- field public static final int system_primary_fixed_dim;
- field public static final int system_primary_light;
- field public static final int system_secondary_container_dark;
- field public static final int system_secondary_container_light;
- field public static final int system_secondary_dark;
- field public static final int system_secondary_fixed;
- field public static final int system_secondary_fixed_dim;
- field public static final int system_secondary_light;
- field public static final int system_surface_bright_dark;
- field public static final int system_surface_bright_light;
- field public static final int system_surface_container_dark;
- field public static final int system_surface_container_high_dark;
- field public static final int system_surface_container_high_light;
- field public static final int system_surface_container_highest_dark;
- field public static final int system_surface_container_highest_light;
- field public static final int system_surface_container_light;
- field public static final int system_surface_container_low_dark;
- field public static final int system_surface_container_low_light;
- field public static final int system_surface_container_lowest_dark;
- field public static final int system_surface_container_lowest_light;
- field public static final int system_surface_dark;
- field public static final int system_surface_dim_dark;
- field public static final int system_surface_dim_light;
- field public static final int system_surface_light;
- field public static final int system_surface_variant_dark;
- field public static final int system_surface_variant_light;
- field public static final int system_tertiary_container_dark;
- field public static final int system_tertiary_container_light;
- field public static final int system_tertiary_dark;
- field public static final int system_tertiary_fixed;
- field public static final int system_tertiary_fixed_dim;
- field public static final int system_tertiary_light;
- field public static final int system_text_hint_inverse_dark;
- field public static final int system_text_hint_inverse_light;
- field public static final int system_text_primary_inverse_dark;
- field public static final int system_text_primary_inverse_disable_only_dark;
- field public static final int system_text_primary_inverse_disable_only_light;
- field public static final int system_text_primary_inverse_light;
- field public static final int system_text_secondary_and_tertiary_inverse_dark;
- field public static final int system_text_secondary_and_tertiary_inverse_disabled_dark;
- field public static final int system_text_secondary_and_tertiary_inverse_disabled_light;
- field public static final int system_text_secondary_and_tertiary_inverse_light;
+ field public static final int system_on_background_dark = 17170582; // 0x1060096
+ field public static final int system_on_background_light = 17170539; // 0x106006b
+ field public static final int system_on_error_container_dark = 17170598; // 0x10600a6
+ field public static final int system_on_error_container_light = 17170555; // 0x106007b
+ field public static final int system_on_error_dark = 17170596; // 0x10600a4
+ field public static final int system_on_error_light = 17170553; // 0x1060079
+ field public static final int system_on_primary_container_dark = 17170570; // 0x106008a
+ field public static final int system_on_primary_container_light = 17170527; // 0x106005f
+ field public static final int system_on_primary_dark = 17170572; // 0x106008c
+ field public static final int system_on_primary_fixed = 17170614; // 0x10600b6
+ field public static final int system_on_primary_fixed_variant = 17170615; // 0x10600b7
+ field public static final int system_on_primary_light = 17170529; // 0x1060061
+ field public static final int system_on_secondary_container_dark = 17170574; // 0x106008e
+ field public static final int system_on_secondary_container_light = 17170531; // 0x1060063
+ field public static final int system_on_secondary_dark = 17170576; // 0x1060090
+ field public static final int system_on_secondary_fixed = 17170618; // 0x10600ba
+ field public static final int system_on_secondary_fixed_variant = 17170619; // 0x10600bb
+ field public static final int system_on_secondary_light = 17170533; // 0x1060065
+ field public static final int system_on_surface_dark = 17170584; // 0x1060098
+ field public static final int system_on_surface_light = 17170541; // 0x106006d
+ field public static final int system_on_surface_variant_dark = 17170593; // 0x10600a1
+ field public static final int system_on_surface_variant_light = 17170550; // 0x1060076
+ field public static final int system_on_tertiary_container_dark = 17170578; // 0x1060092
+ field public static final int system_on_tertiary_container_light = 17170535; // 0x1060067
+ field public static final int system_on_tertiary_dark = 17170580; // 0x1060094
+ field public static final int system_on_tertiary_fixed = 17170622; // 0x10600be
+ field public static final int system_on_tertiary_fixed_variant = 17170623; // 0x10600bf
+ field public static final int system_on_tertiary_light = 17170537; // 0x1060069
+ field public static final int system_outline_dark = 17170594; // 0x10600a2
+ field public static final int system_outline_light = 17170551; // 0x1060077
+ field public static final int system_outline_variant_dark = 17170625; // 0x10600c1
+ field public static final int system_outline_variant_light = 17170624; // 0x10600c0
+ field public static final int system_palette_key_color_neutral_dark = 17170610; // 0x10600b2
+ field public static final int system_palette_key_color_neutral_light = 17170567; // 0x1060087
+ field public static final int system_palette_key_color_neutral_variant_dark = 17170611; // 0x10600b3
+ field public static final int system_palette_key_color_neutral_variant_light = 17170568; // 0x1060088
+ field public static final int system_palette_key_color_primary_dark = 17170607; // 0x10600af
+ field public static final int system_palette_key_color_primary_light = 17170564; // 0x1060084
+ field public static final int system_palette_key_color_secondary_dark = 17170608; // 0x10600b0
+ field public static final int system_palette_key_color_secondary_light = 17170565; // 0x1060085
+ field public static final int system_palette_key_color_tertiary_dark = 17170609; // 0x10600b1
+ field public static final int system_palette_key_color_tertiary_light = 17170566; // 0x1060086
+ field public static final int system_primary_container_dark = 17170569; // 0x1060089
+ field public static final int system_primary_container_light = 17170526; // 0x106005e
+ field public static final int system_primary_dark = 17170571; // 0x106008b
+ field public static final int system_primary_fixed = 17170612; // 0x10600b4
+ field public static final int system_primary_fixed_dim = 17170613; // 0x10600b5
+ field public static final int system_primary_light = 17170528; // 0x1060060
+ field public static final int system_secondary_container_dark = 17170573; // 0x106008d
+ field public static final int system_secondary_container_light = 17170530; // 0x1060062
+ field public static final int system_secondary_dark = 17170575; // 0x106008f
+ field public static final int system_secondary_fixed = 17170616; // 0x10600b8
+ field public static final int system_secondary_fixed_dim = 17170617; // 0x10600b9
+ field public static final int system_secondary_light = 17170532; // 0x1060064
+ field public static final int system_surface_bright_dark = 17170590; // 0x106009e
+ field public static final int system_surface_bright_light = 17170547; // 0x1060073
+ field public static final int system_surface_container_dark = 17170587; // 0x106009b
+ field public static final int system_surface_container_high_dark = 17170588; // 0x106009c
+ field public static final int system_surface_container_high_light = 17170545; // 0x1060071
+ field public static final int system_surface_container_highest_dark = 17170589; // 0x106009d
+ field public static final int system_surface_container_highest_light = 17170546; // 0x1060072
+ field public static final int system_surface_container_light = 17170544; // 0x1060070
+ field public static final int system_surface_container_low_dark = 17170585; // 0x1060099
+ field public static final int system_surface_container_low_light = 17170542; // 0x106006e
+ field public static final int system_surface_container_lowest_dark = 17170586; // 0x106009a
+ field public static final int system_surface_container_lowest_light = 17170543; // 0x106006f
+ field public static final int system_surface_dark = 17170583; // 0x1060097
+ field public static final int system_surface_dim_dark = 17170591; // 0x106009f
+ field public static final int system_surface_dim_light = 17170548; // 0x1060074
+ field public static final int system_surface_light = 17170540; // 0x106006c
+ field public static final int system_surface_variant_dark = 17170592; // 0x10600a0
+ field public static final int system_surface_variant_light = 17170549; // 0x1060075
+ field public static final int system_tertiary_container_dark = 17170577; // 0x1060091
+ field public static final int system_tertiary_container_light = 17170534; // 0x1060066
+ field public static final int system_tertiary_dark = 17170579; // 0x1060093
+ field public static final int system_tertiary_fixed = 17170620; // 0x10600bc
+ field public static final int system_tertiary_fixed_dim = 17170621; // 0x10600bd
+ field public static final int system_tertiary_light = 17170536; // 0x1060068
+ field public static final int system_text_hint_inverse_dark = 17170606; // 0x10600ae
+ field public static final int system_text_hint_inverse_light = 17170563; // 0x1060083
+ field public static final int system_text_primary_inverse_dark = 17170602; // 0x10600aa
+ field public static final int system_text_primary_inverse_disable_only_dark = 17170604; // 0x10600ac
+ field public static final int system_text_primary_inverse_disable_only_light = 17170561; // 0x1060081
+ field public static final int system_text_primary_inverse_light = 17170559; // 0x106007f
+ field public static final int system_text_secondary_and_tertiary_inverse_dark = 17170603; // 0x10600ab
+ field public static final int system_text_secondary_and_tertiary_inverse_disabled_dark = 17170605; // 0x10600ad
+ field public static final int system_text_secondary_and_tertiary_inverse_disabled_light = 17170562; // 0x1060082
+ field public static final int system_text_secondary_and_tertiary_inverse_light = 17170560; // 0x1060080
field public static final int tab_indicator_text = 17170441; // 0x1060009
field @Deprecated public static final int tertiary_text_dark = 17170448; // 0x1060010
field @Deprecated public static final int tertiary_text_light = 17170449; // 0x1060011
@@ -2310,7 +2310,7 @@
field public static final int accessibilityActionPageUp = 16908358; // 0x1020046
field public static final int accessibilityActionPressAndHold = 16908362; // 0x102004a
field public static final int accessibilityActionScrollDown = 16908346; // 0x102003a
- field public static final int accessibilityActionScrollInDirection;
+ field public static final int accessibilityActionScrollInDirection = 16908382; // 0x102005e
field public static final int accessibilityActionScrollLeft = 16908345; // 0x1020039
field public static final int accessibilityActionScrollRight = 16908347; // 0x102003b
field public static final int accessibilityActionScrollToPosition = 16908343; // 0x1020037
@@ -2331,7 +2331,7 @@
field public static final int addToDictionary = 16908330; // 0x102002a
field public static final int autofill = 16908355; // 0x1020043
field public static final int background = 16908288; // 0x1020000
- field public static final int bold;
+ field public static final int bold = 16908379; // 0x102005b
field public static final int button1 = 16908313; // 0x1020019
field public static final int button2 = 16908314; // 0x102001a
field public static final int button3 = 16908315; // 0x102001b
@@ -2357,7 +2357,7 @@
field public static final int inputExtractAccessories = 16908378; // 0x102005a
field public static final int inputExtractAction = 16908377; // 0x1020059
field public static final int inputExtractEditText = 16908325; // 0x1020025
- field public static final int italic;
+ field public static final int italic = 16908380; // 0x102005c
field @Deprecated public static final int keyboardView = 16908326; // 0x1020026
field public static final int list = 16908298; // 0x102000a
field public static final int list_container = 16908351; // 0x102003f
@@ -2389,7 +2389,7 @@
field public static final int textAssist = 16908353; // 0x1020041
field public static final int title = 16908310; // 0x1020016
field public static final int toggle = 16908311; // 0x1020017
- field public static final int underline;
+ field public static final int underline = 16908381; // 0x102005d
field public static final int undo = 16908338; // 0x1020032
field public static final int widget_frame = 16908312; // 0x1020018
}
@@ -32652,7 +32652,7 @@
field public static final int S = 31; // 0x1f
field public static final int S_V2 = 32; // 0x20
field public static final int TIRAMISU = 33; // 0x21
- field public static final int UPSIDE_DOWN_CAKE = 10000; // 0x2710
+ field public static final int UPSIDE_DOWN_CAKE = 34; // 0x22
}
public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index fbc69e3..ace7d59 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -410,14 +410,14 @@
field public static final int sdkVersion = 16844304; // 0x1010610
field public static final int supportsAmbientMode = 16844173; // 0x101058d
field public static final int userRestriction = 16844164; // 0x1010584
- field public static final int visualQueryDetectionService;
+ field public static final int visualQueryDetectionService = 16844410; // 0x101067a
}
public static final class R.bool {
- field public static final int config_enableDefaultNotes;
- field public static final int config_enableDefaultNotesForWorkProfile;
+ field public static final int config_enableDefaultNotes = 17891338; // 0x111000a
+ field public static final int config_enableDefaultNotesForWorkProfile = 17891339; // 0x111000b
field public static final int config_enableQrCodeScannerOnLockScreen = 17891336; // 0x1110008
- field public static final int config_safetyProtectionEnabled;
+ field public static final int config_safetyProtectionEnabled = 17891337; // 0x1110009
field public static final int config_sendPackageName = 17891328; // 0x1110000
field public static final int config_showDefaultAssistant = 17891329; // 0x1110001
field public static final int config_showDefaultEmergency = 17891330; // 0x1110002
@@ -430,7 +430,7 @@
public static final class R.dimen {
field public static final int config_restrictedIconSize = 17104903; // 0x1050007
- field public static final int config_viewConfigurationHandwritingGestureLineMargin;
+ field public static final int config_viewConfigurationHandwritingGestureLineMargin = 17104906; // 0x105000a
}
public static final class R.drawable {
@@ -452,7 +452,7 @@
field public static final int config_defaultCallRedirection = 17039397; // 0x1040025
field public static final int config_defaultCallScreening = 17039398; // 0x1040026
field public static final int config_defaultDialer = 17039395; // 0x1040023
- field public static final int config_defaultNotes;
+ field public static final int config_defaultNotes = 17039429; // 0x1040045
field public static final int config_defaultSms = 17039396; // 0x1040024
field public static final int config_devicePolicyManagement = 17039421; // 0x104003d
field public static final int config_feedbackIntentExtraKey = 17039391; // 0x104001f
@@ -468,10 +468,10 @@
field public static final int config_systemAutomotiveCalendarSyncManager = 17039423; // 0x104003f
field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
field public static final int config_systemAutomotiveProjection = 17039401; // 0x1040029
- field public static final int config_systemCallStreaming;
+ field public static final int config_systemCallStreaming = 17039431; // 0x1040047
field public static final int config_systemCompanionDeviceProvider = 17039417; // 0x1040039
field public static final int config_systemContacts = 17039403; // 0x104002b
- field public static final int config_systemFinancedDeviceController;
+ field public static final int config_systemFinancedDeviceController = 17039430; // 0x1040046
field public static final int config_systemGallery = 17039399; // 0x1040027
field public static final int config_systemNotificationIntelligence = 17039413; // 0x1040035
field public static final int config_systemSettingsIntelligence = 17039426; // 0x1040042
@@ -483,7 +483,7 @@
field public static final int config_systemUi = 17039418; // 0x104003a
field public static final int config_systemUiIntelligence = 17039410; // 0x1040032
field public static final int config_systemVisualIntelligence = 17039415; // 0x1040037
- field public static final int config_systemWearHealthService;
+ field public static final int config_systemWearHealthService = 17039428; // 0x1040044
field public static final int config_systemWellbeing = 17039408; // 0x1040030
field public static final int config_systemWifiCoexManager = 17039407; // 0x104002f
field public static final int safety_protection_display_text = 17039425; // 0x1040041
@@ -10117,7 +10117,7 @@
public final class NetworkProviderInfo implements android.os.Parcelable {
method public int describeContents();
method @IntRange(from=0, to=100) public int getBatteryPercentage();
- method @IntRange(from=0, to=3) public int getConnectionStrength();
+ method @IntRange(from=0, to=4) public int getConnectionStrength();
method @NonNull public String getDeviceName();
method public int getDeviceType();
method @NonNull public android.os.Bundle getExtras();
@@ -10136,7 +10136,7 @@
ctor public NetworkProviderInfo.Builder(@NonNull String, @NonNull String);
method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo build();
method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryPercentage(@IntRange(from=0, to=100) int);
- method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=3) int);
+ method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=4) int);
method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceName(@NonNull String);
method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceType(int);
method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setExtras(@NonNull android.os.Bundle);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 2dfda51..7d81a95 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1651,6 +1651,11 @@
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.KeyphraseMetadata> CREATOR;
}
+ public class SoundTrigger {
+ field public static final int MODEL_PARAM_INVALID = -1; // 0xffffffff
+ field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0
+ }
+
public static final class SoundTrigger.KeyphraseRecognitionExtra implements android.os.Parcelable {
ctor public SoundTrigger.KeyphraseRecognitionExtra(int, int, int);
}
@@ -1663,6 +1668,19 @@
ctor public SoundTrigger.ModuleProperties(int, @NonNull String, @NonNull String, @NonNull String, int, @NonNull String, int, int, int, int, boolean, int, boolean, int, boolean, int);
}
+ public static final class SoundTrigger.RecognitionConfig implements android.os.Parcelable {
+ ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[], int);
+ ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[]);
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.RecognitionConfig> CREATOR;
+ field public final boolean allowMultipleTriggers;
+ field public final int audioCapabilities;
+ field public final boolean captureRequested;
+ field @NonNull public final byte[] data;
+ field @NonNull public final android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[] keyphrases;
+ }
+
public static class SoundTrigger.RecognitionEvent {
ctor public SoundTrigger.RecognitionEvent(int, int, boolean, int, int, int, boolean, @NonNull android.media.AudioFormat, @Nullable byte[], long);
}
@@ -2000,6 +2018,57 @@
}
+package android.media.soundtrigger {
+
+ public final class SoundTriggerInstrumentation {
+ method public void setResourceContention(boolean);
+ method public void triggerOnResourcesAvailable();
+ method public void triggerRestart();
+ }
+
+ public static interface SoundTriggerInstrumentation.GlobalCallback {
+ method public default void onClientAttached();
+ method public default void onClientDetached();
+ method public default void onFrameworkDetached();
+ method public void onModelLoaded(@NonNull android.media.soundtrigger.SoundTriggerInstrumentation.ModelSession);
+ method public default void onPreempted();
+ method public default void onRestarted();
+ }
+
+ public static interface SoundTriggerInstrumentation.ModelCallback {
+ method public default void onModelUnloaded();
+ method public default void onParamSet(int, int);
+ method public void onRecognitionStarted(@NonNull android.media.soundtrigger.SoundTriggerInstrumentation.RecognitionSession);
+ }
+
+ public class SoundTriggerInstrumentation.ModelSession {
+ method public void clearModelCallback();
+ method @NonNull public java.util.List<android.hardware.soundtrigger.SoundTrigger.Keyphrase> getPhrases();
+ method @NonNull public android.media.soundtrigger.SoundTriggerManager.Model getSoundModel();
+ method public boolean isKeyphrase();
+ method public void setModelCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.ModelCallback);
+ method public void triggerUnloadModel();
+ }
+
+ public static interface SoundTriggerInstrumentation.RecognitionCallback {
+ method public void onRecognitionStopped();
+ }
+
+ public class SoundTriggerInstrumentation.RecognitionSession {
+ method public void clearRecognitionCallback();
+ method public int getAudioSession();
+ method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig getRecognitionConfig();
+ method public void setRecognitionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.RecognitionCallback);
+ method public void triggerAbortRecognition();
+ method public void triggerRecognitionEvent(@NonNull byte[], @Nullable java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>);
+ }
+
+ public final class SoundTriggerManager {
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public static android.media.soundtrigger.SoundTriggerInstrumentation attachInstrumentation(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.GlobalCallback);
+ }
+
+}
+
package android.media.tv {
public final class TvInputManager {
@@ -2024,6 +2093,14 @@
}
+package android.media.voice {
+
+ public final class KeyphraseModelManager {
+ method @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public void setModelDatabaseForTestEnabled(boolean);
+ }
+
+}
+
package android.net {
public class NetworkPolicyManager {
@@ -2944,6 +3021,7 @@
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(@NonNull String, @NonNull java.util.Locale, @NonNull android.hardware.soundtrigger.SoundTrigger.ModuleProperties, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.AlwaysOnHotwordDetector.Callback);
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(@NonNull String, @NonNull java.util.Locale, @Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull android.hardware.soundtrigger.SoundTrigger.ModuleProperties, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.AlwaysOnHotwordDetector.Callback);
method @NonNull public final java.util.List<android.hardware.soundtrigger.SoundTrigger.ModuleProperties> listModuleProperties();
+ method public final void setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean);
}
public static class VoiceInteractionSession.ActivityId {
@@ -3351,8 +3429,14 @@
method @NonNull public android.hardware.input.InputDeviceIdentifier getIdentifier();
}
+ public abstract class InputEvent implements android.os.Parcelable {
+ method public abstract int getDisplayId();
+ method public abstract void setDisplayId(int);
+ }
+
public class KeyEvent extends android.view.InputEvent implements android.os.Parcelable {
method public static String actionToString(int);
+ method public final int getDisplayId();
method public final void setDisplayId(int);
field public static final int FLAG_IS_ACCESSIBILITY_EVENT = 2048; // 0x800
field public static final int LAST_KEYCODE = 316; // 0x13c
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 0293bb5..95e446d 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -343,7 +343,170 @@
*/
public abstract boolean hasRunningActivity(int uid, @Nullable String packageName);
- public abstract void updateOomAdj();
+ /**
+ * Oom Adj Reason: none - internal use only, do not use it.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_NONE = 0;
+
+ /**
+ * Oom Adj Reason: activity changes.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_ACTIVITY = 1;
+
+ /**
+ * Oom Adj Reason: finishing a broadcast receiver.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_FINISH_RECEIVER = 2;
+
+ /**
+ * Oom Adj Reason: starting a broadcast receiver.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_START_RECEIVER = 3;
+
+ /**
+ * Oom Adj Reason: binding to a service.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_BIND_SERVICE = 4;
+
+ /**
+ * Oom Adj Reason: unbinding from a service.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_UNBIND_SERVICE = 5;
+
+ /**
+ * Oom Adj Reason: starting a service.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_START_SERVICE = 6;
+
+ /**
+ * Oom Adj Reason: connecting to a content provider.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_GET_PROVIDER = 7;
+
+ /**
+ * Oom Adj Reason: disconnecting from a content provider.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_REMOVE_PROVIDER = 8;
+
+ /**
+ * Oom Adj Reason: UI visibility changes.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_UI_VISIBILITY = 9;
+
+ /**
+ * Oom Adj Reason: device power allowlist changes.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_ALLOWLIST = 10;
+
+ /**
+ * Oom Adj Reason: starting a process.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_PROCESS_BEGIN = 11;
+
+ /**
+ * Oom Adj Reason: ending a process.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_PROCESS_END = 12;
+
+ /**
+ * Oom Adj Reason: short FGS timeout.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_SHORT_FGS_TIMEOUT = 13;
+
+ /**
+ * Oom Adj Reason: system initialization.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_SYSTEM_INIT = 14;
+
+ /**
+ * Oom Adj Reason: backup/restore.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_BACKUP = 15;
+
+ /**
+ * Oom Adj Reason: instrumented by the SHELL.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_SHELL = 16;
+
+ /**
+ * Oom Adj Reason: task stack is being removed.
+ */
+ public static final int OOM_ADJ_REASON_REMOVE_TASK = 17;
+
+ /**
+ * Oom Adj Reason: uid idle.
+ */
+ public static final int OOM_ADJ_REASON_UID_IDLE = 18;
+
+ /**
+ * Oom Adj Reason: stop service.
+ */
+ public static final int OOM_ADJ_REASON_STOP_SERVICE = 19;
+
+ /**
+ * Oom Adj Reason: executing service.
+ */
+ public static final int OOM_ADJ_REASON_EXECUTING_SERVICE = 20;
+
+ /**
+ * Oom Adj Reason: background restriction changes.
+ */
+ public static final int OOM_ADJ_REASON_RESTRICTION_CHANGE = 21;
+
+ /**
+ * Oom Adj Reason: A package or its component is disabled.
+ */
+ public static final int OOM_ADJ_REASON_COMPONENT_DISABLED = 22;
+
+ @IntDef(prefix = {"OOM_ADJ_REASON_"}, value = {
+ OOM_ADJ_REASON_NONE,
+ OOM_ADJ_REASON_ACTIVITY,
+ OOM_ADJ_REASON_FINISH_RECEIVER,
+ OOM_ADJ_REASON_START_RECEIVER,
+ OOM_ADJ_REASON_BIND_SERVICE,
+ OOM_ADJ_REASON_UNBIND_SERVICE,
+ OOM_ADJ_REASON_START_SERVICE,
+ OOM_ADJ_REASON_GET_PROVIDER,
+ OOM_ADJ_REASON_REMOVE_PROVIDER,
+ OOM_ADJ_REASON_UI_VISIBILITY,
+ OOM_ADJ_REASON_ALLOWLIST,
+ OOM_ADJ_REASON_PROCESS_BEGIN,
+ OOM_ADJ_REASON_PROCESS_END,
+ OOM_ADJ_REASON_SHORT_FGS_TIMEOUT,
+ OOM_ADJ_REASON_SYSTEM_INIT,
+ OOM_ADJ_REASON_BACKUP,
+ OOM_ADJ_REASON_SHELL,
+ OOM_ADJ_REASON_REMOVE_TASK,
+ OOM_ADJ_REASON_UID_IDLE,
+ OOM_ADJ_REASON_STOP_SERVICE,
+ OOM_ADJ_REASON_EXECUTING_SERVICE,
+ OOM_ADJ_REASON_RESTRICTION_CHANGE,
+ OOM_ADJ_REASON_COMPONENT_DISABLED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface OomAdjReason {}
+
+ /**
+ * Request to update oom adj.
+ */
+ public abstract void updateOomAdj(@OomAdjReason int oomAdjReason);
public abstract void updateCpuStats();
/**
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index b48a8fb..1dddf06 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1461,9 +1461,25 @@
*/
public static final int OP_USE_FULL_SCREEN_INTENT = AppProtoEnums.APP_OP_USE_FULL_SCREEN_INTENT;
+ /**
+ * Hides camera indicator for sandboxed detection apps that directly access the service.
+ *
+ * @hide
+ */
+ public static final int OP_CAMERA_SANDBOXED =
+ AppProtoEnums.APP_OP_CAMERA_SANDBOXED;
+
+ /**
+ * Hides microphone indicator for sandboxed detection apps that directly access the service.
+ *
+ * @hide
+ */
+ public static final int OP_RECORD_AUDIO_SANDBOXED =
+ AppProtoEnums.APP_OP_RECORD_AUDIO_SANDBOXED;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 134;
+ public static final int _NUM_OP = 136;
/**
* All app ops represented as strings.
@@ -1605,6 +1621,8 @@
OPSTR_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
OPSTR_BODY_SENSORS_WRIST_TEMPERATURE,
OPSTR_USE_FULL_SCREEN_INTENT,
+ OPSTR_CAMERA_SANDBOXED,
+ OPSTR_RECORD_AUDIO_SANDBOXED
})
public @interface AppOpString {}
@@ -2013,6 +2031,20 @@
public static final String OPSTR_COARSE_LOCATION_SOURCE = "android:coarse_location_source";
/**
+ * Camera is being recorded in sandboxed detection process.
+ *
+ * @hide
+ */
+ public static final String OPSTR_CAMERA_SANDBOXED = "android:camera_sandboxed";
+
+ /**
+ * Audio is being recorded in sandboxed detection process.
+ *
+ * @hide
+ */
+ public static final String OPSTR_RECORD_AUDIO_SANDBOXED = "android:record_audio_sandboxed";
+
+ /**
* Allow apps to create the requests to manage the media files without user confirmation.
*
* @see android.Manifest.permission#MANAGE_MEDIA
@@ -2738,7 +2770,11 @@
.setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
new AppOpInfo.Builder(OP_USE_FULL_SCREEN_INTENT, OPSTR_USE_FULL_SCREEN_INTENT,
"USE_FULL_SCREEN_INTENT").setPermission(Manifest.permission.USE_FULL_SCREEN_INTENT)
- .build()
+ .build(),
+ new AppOpInfo.Builder(OP_CAMERA_SANDBOXED, OPSTR_CAMERA_SANDBOXED,
+ "CAMERA_SANDBOXED").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_RECORD_AUDIO_SANDBOXED, OPSTR_RECORD_AUDIO_SANDBOXED,
+ "RECORD_AUDIO_SANDBOXED").setDefaultMode(AppOpsManager.MODE_ALLOWED).build()
};
// The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/credentials/ui/CreateCredentialProviderData.java b/core/java/android/credentials/ui/CreateCredentialProviderData.java
index 852934a..629d578 100644
--- a/core/java/android/credentials/ui/CreateCredentialProviderData.java
+++ b/core/java/android/credentials/ui/CreateCredentialProviderData.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
+import android.content.pm.ParceledListSlice;
import android.os.Parcel;
import android.os.Parcelable;
@@ -35,7 +36,7 @@
@TestApi
public final class CreateCredentialProviderData extends ProviderData implements Parcelable {
@NonNull
- private final List<Entry> mSaveEntries;
+ private final ParceledListSlice<Entry> mSaveEntries;
@Nullable
private final Entry mRemoteEntry;
@@ -43,13 +44,13 @@
@NonNull String providerFlattenedComponentName, @NonNull List<Entry> saveEntries,
@Nullable Entry remoteEntry) {
super(providerFlattenedComponentName);
- mSaveEntries = saveEntries;
+ mSaveEntries = new ParceledListSlice<>(saveEntries);
mRemoteEntry = remoteEntry;
}
@NonNull
public List<Entry> getSaveEntries() {
- return mSaveEntries;
+ return mSaveEntries.getList();
}
@Nullable
@@ -60,9 +61,7 @@
private CreateCredentialProviderData(@NonNull Parcel in) {
super(in);
- List<Entry> credentialEntries = new ArrayList<>();
- in.readTypedList(credentialEntries, Entry.CREATOR);
- mSaveEntries = credentialEntries;
+ mSaveEntries = in.readParcelable(null, android.content.pm.ParceledListSlice.class);
AnnotationValidations.validate(NonNull.class, null, mSaveEntries);
Entry remoteEntry = in.readTypedObject(Entry.CREATOR);
@@ -72,7 +71,7 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
- dest.writeTypedList(mSaveEntries);
+ dest.writeParcelable(mSaveEntries, flags);
dest.writeTypedObject(mRemoteEntry, flags);
}
diff --git a/core/java/android/credentials/ui/GetCredentialProviderData.java b/core/java/android/credentials/ui/GetCredentialProviderData.java
index e4688a84..773dee9 100644
--- a/core/java/android/credentials/ui/GetCredentialProviderData.java
+++ b/core/java/android/credentials/ui/GetCredentialProviderData.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
+import android.content.pm.ParceledListSlice;
import android.os.Parcel;
import android.os.Parcelable;
@@ -35,11 +36,11 @@
@TestApi
public final class GetCredentialProviderData extends ProviderData implements Parcelable {
@NonNull
- private final List<Entry> mCredentialEntries;
+ private final ParceledListSlice<Entry> mCredentialEntries;
@NonNull
- private final List<Entry> mActionChips;
+ private final ParceledListSlice<Entry> mActionChips;
@NonNull
- private final List<AuthenticationEntry> mAuthenticationEntries;
+ private final ParceledListSlice<AuthenticationEntry> mAuthenticationEntries;
@Nullable
private final Entry mRemoteEntry;
@@ -49,25 +50,25 @@
@NonNull List<AuthenticationEntry> authenticationEntries,
@Nullable Entry remoteEntry) {
super(providerFlattenedComponentName);
- mCredentialEntries = credentialEntries;
- mActionChips = actionChips;
- mAuthenticationEntries = authenticationEntries;
+ mCredentialEntries = new ParceledListSlice<>(credentialEntries);
+ mActionChips = new ParceledListSlice<>(actionChips);
+ mAuthenticationEntries = new ParceledListSlice<>(authenticationEntries);
mRemoteEntry = remoteEntry;
}
@NonNull
public List<Entry> getCredentialEntries() {
- return mCredentialEntries;
+ return mCredentialEntries.getList();
}
@NonNull
public List<Entry> getActionChips() {
- return mActionChips;
+ return mActionChips.getList();
}
@NonNull
public List<AuthenticationEntry> getAuthenticationEntries() {
- return mAuthenticationEntries;
+ return mAuthenticationEntries.getList();
}
@Nullable
@@ -77,20 +78,16 @@
private GetCredentialProviderData(@NonNull Parcel in) {
super(in);
-
- List<Entry> credentialEntries = new ArrayList<>();
- in.readTypedList(credentialEntries, Entry.CREATOR);
- mCredentialEntries = credentialEntries;
+ mCredentialEntries = in.readParcelable(null,
+ android.content.pm.ParceledListSlice.class);
AnnotationValidations.validate(NonNull.class, null, mCredentialEntries);
- List<Entry> actionChips = new ArrayList<>();
- in.readTypedList(actionChips, Entry.CREATOR);
- mActionChips = actionChips;
+ mActionChips = in.readParcelable(null,
+ android.content.pm.ParceledListSlice.class);
AnnotationValidations.validate(NonNull.class, null, mActionChips);
- List<AuthenticationEntry> authenticationEntries = new ArrayList<>();
- in.readTypedList(authenticationEntries, AuthenticationEntry.CREATOR);
- mAuthenticationEntries = authenticationEntries;
+ mAuthenticationEntries = in.readParcelable(null,
+ android.content.pm.ParceledListSlice.class);
AnnotationValidations.validate(NonNull.class, null, mAuthenticationEntries);
Entry remoteEntry = in.readTypedObject(Entry.CREATOR);
@@ -100,9 +97,9 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
- dest.writeTypedList(mCredentialEntries);
- dest.writeTypedList(mActionChips);
- dest.writeTypedList(mAuthenticationEntries);
+ dest.writeParcelable(mCredentialEntries, flags);
+ dest.writeParcelable(mActionChips, flags);
+ dest.writeParcelable(mAuthenticationEntries, flags);
dest.writeTypedObject(mRemoteEntry, flags);
}
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index fa678fc..2e40f60 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -142,6 +142,7 @@
private PromptInfo mPromptInfo;
private ButtonInfo mNegativeButtonInfo;
private Context mContext;
+ private IAuthService mService;
/**
* Creates a builder for a {@link BiometricPrompt} dialog.
@@ -212,6 +213,18 @@
}
/**
+ * @param service
+ * @return This builder.
+ * @hide
+ */
+ @RequiresPermission(TEST_BIOMETRIC)
+ @NonNull
+ public Builder setService(@NonNull IAuthService service) {
+ mService = service;
+ return this;
+ }
+
+ /**
* Sets an optional title, subtitle, and/or description that will override other text when
* the user is authenticating with PIN/pattern/password. Currently for internal use only.
* @return This builder.
@@ -472,7 +485,9 @@
throw new IllegalArgumentException("Can't have both negative button behavior"
+ " and device credential enabled");
}
- return new BiometricPrompt(mContext, mPromptInfo, mNegativeButtonInfo);
+ mService = (mService == null) ? IAuthService.Stub.asInterface(
+ ServiceManager.getService(Context.AUTH_SERVICE)) : mService;
+ return new BiometricPrompt(mContext, mPromptInfo, mNegativeButtonInfo, mService);
}
}
@@ -521,7 +536,6 @@
public void onAuthenticationFailed() {
mExecutor.execute(() -> {
mAuthenticationCallback.onAuthenticationFailed();
- mIsPromptShowing = false;
});
}
@@ -604,12 +618,12 @@
private boolean mIsPromptShowing;
- private BiometricPrompt(Context context, PromptInfo promptInfo, ButtonInfo negativeButtonInfo) {
+ private BiometricPrompt(Context context, PromptInfo promptInfo, ButtonInfo negativeButtonInfo,
+ IAuthService service) {
mContext = context;
mPromptInfo = promptInfo;
mNegativeButtonInfo = negativeButtonInfo;
- mService = IAuthService.Stub.asInterface(
- ServiceManager.getService(Context.AUTH_SERVICE));
+ mService = service;
mIsPromptShowing = false;
}
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index fa16e16..6d43ddf 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -1051,6 +1051,29 @@
return "ModelParamRange [start=" + mStart + ", end=" + mEnd + "]";
}
}
+ /**
+ * SoundTrigger model parameter types.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "MODEL_PARAM" }, value = {
+ MODEL_PARAM_INVALID,
+ MODEL_PARAM_THRESHOLD_FACTOR
+ })
+ public @interface ModelParamTypes {}
+
+ /**
+ * See {@link ModelParams.INVALID}
+ * @hide
+ */
+ @TestApi
+ public static final int MODEL_PARAM_INVALID = ModelParams.INVALID;
+ /**
+ * See {@link ModelParams.THRESHOLD_FACTOR}
+ * @hide
+ */
+ @TestApi
+ public static final int MODEL_PARAM_THRESHOLD_FACTOR = ModelParams.THRESHOLD_FACTOR;
/**
* Modes for key phrase recognition
@@ -1450,7 +1473,8 @@
*
* @hide
*/
- public static class RecognitionConfig implements Parcelable {
+ @TestApi
+ public static final class RecognitionConfig implements Parcelable {
/** True if the DSP should capture the trigger sound and make it available for further
* capture. */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -1464,6 +1488,7 @@
* options for each keyphrase. */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@NonNull
+ @SuppressLint("ArrayReturn")
public final KeyphraseRecognitionExtra keyphrases[];
/** Opaque data for use by system applications who know about voice engine internals,
* typically during enrollment. */
@@ -1479,8 +1504,8 @@
public final int audioCapabilities;
public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
- @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data,
- int audioCapabilities) {
+ @SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases,
+ @Nullable byte[] data, int audioCapabilities) {
this.captureRequested = captureRequested;
this.allowMultipleTriggers = allowMultipleTriggers;
this.keyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0];
@@ -1490,7 +1515,8 @@
@UnsupportedAppUsage
public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
- @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
+ @SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases,
+ @Nullable byte[] data) {
this(captureRequested, allowMultipleTriggers, keyphrases, data, 0);
}
@@ -1517,7 +1543,7 @@
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeByte((byte) (captureRequested ? 1 : 0));
dest.writeByte((byte) (allowMultipleTriggers ? 1 : 0));
dest.writeTypedArray(keyphrases, flags);
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 244632a..9f9c222 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1222,7 +1222,7 @@
/**
* Upside Down Cake.
*/
- public static final int UPSIDE_DOWN_CAKE = CUR_DEVELOPMENT;
+ public static final int UPSIDE_DOWN_CAKE = 34;
}
/** The type of build, like "user" or "eng". */
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index d2f9ff0..59b945c 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -2051,6 +2051,14 @@
* <P>Type: TEXT</P>
*/
public static final String ADDRESS = "address";
+
+ /**
+ * The subscription to which the message belongs to. Its value will be less than 0
+ * if the sub id cannot be determined.
+ * <p>Type: INTEGER (long) </p>
+ * @hide
+ */
+ public static final String SUBSCRIPTION_ID = "sub_id";
}
/**
@@ -2119,6 +2127,14 @@
* <P>Type: INTEGER (boolean)</P>
*/
public static final String ARCHIVED = "archived";
+
+ /**
+ * The subscription to which the message belongs to. Its value will be less than 0
+ * if the sub id cannot be determined.
+ * <p>Type: INTEGER (long) </p>
+ * @hide
+ */
+ public static final String SUBSCRIPTION_ID = "sub_id";
}
/**
@@ -2477,6 +2493,14 @@
public static final String CHARSET = "charset";
/**
+ * The subscription to which the message belongs to. Its value will be less than 0
+ * if the sub id cannot be determined.
+ * <p>Type: INTEGER (long) </p>
+ * @hide
+ */
+ public static final String SUBSCRIPTION_ID = "sub_id";
+
+ /**
* Generates a Addr {@link Uri} for message, used to perform Addr table operation
* for mms.
*
@@ -2597,6 +2621,14 @@
public static final String TEXT = "text";
/**
+ * The subscription to which the message belongs to. Its value will be less than 0
+ * if the sub id cannot be determined.
+ * <p>Type: INTEGER (long) </p>
+ * @hide
+ */
+ public static final String SUBSCRIPTION_ID = "sub_id";
+
+ /**
* Generates a Part {@link Uri} for message, used to perform Part table operation
* for mms.
*
@@ -2635,6 +2667,14 @@
* <P>Type: INTEGER (long)</P>
*/
public static final String SENT_TIME = "sent_time";
+
+ /**
+ * The subscription to which the message belongs to. Its value will be less than 0
+ * if the sub id cannot be determined.
+ * <p>Type: INTEGER (long) </p>
+ * @hide
+ */
+ public static final String SUBSCRIPTION_ID = "sub_id";
}
/**
@@ -2868,6 +2908,14 @@
* <P>Type: TEXT</P>
*/
public static final String INDEXED_TEXT = "index_text";
+
+ /**
+ * The subscription to which the message belongs to. Its value will be less than 0
+ * if the sub id cannot be determined.
+ * <p>Type: INTEGER (long) </p>
+ * @hide
+ */
+ public static final String SUBSCRIPTION_ID = "sub_id";
}
}
diff --git a/core/java/android/service/credentials/BeginCreateCredentialResponse.java b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
index cd53cb6..df93433 100644
--- a/core/java/android/service/credentials/BeginCreateCredentialResponse.java
+++ b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.content.pm.ParceledListSlice;
import android.os.Parcel;
import android.os.Parcelable;
@@ -33,7 +34,7 @@
* Response to a {@link BeginCreateCredentialRequest}.
*/
public final class BeginCreateCredentialResponse implements Parcelable {
- private final @NonNull List<CreateEntry> mCreateEntries;
+ private final @NonNull ParceledListSlice<CreateEntry> mCreateEntries;
private final @Nullable RemoteEntry mRemoteCreateEntry;
/**
@@ -41,19 +42,19 @@
* to return.
*/
public BeginCreateCredentialResponse() {
- this(/*createEntries=*/new ArrayList<>(), /*remoteCreateEntry=*/null);
+ this(/*createEntries=*/new ParceledListSlice<>(new ArrayList<>()),
+ /*remoteCreateEntry=*/null);
}
private BeginCreateCredentialResponse(@NonNull Parcel in) {
- List<CreateEntry> createEntries = new ArrayList<>();
- in.readTypedList(createEntries, CreateEntry.CREATOR);
- mCreateEntries = createEntries;
+ mCreateEntries = in.readParcelable(
+ null, android.content.pm.ParceledListSlice.class);
mRemoteCreateEntry = in.readTypedObject(RemoteEntry.CREATOR);
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeTypedList(mCreateEntries);
+ dest.writeParcelable(mCreateEntries, flags);
dest.writeTypedObject(mRemoteCreateEntry, flags);
}
@@ -76,7 +77,7 @@
};
/* package-private */ BeginCreateCredentialResponse(
- @NonNull List<CreateEntry> createEntries,
+ @NonNull ParceledListSlice<CreateEntry> createEntries,
@Nullable RemoteEntry remoteCreateEntry) {
this.mCreateEntries = createEntries;
com.android.internal.util.AnnotationValidations.validate(
@@ -86,7 +87,7 @@
/** Returns the list of create entries to be displayed on the UI. */
public @NonNull List<CreateEntry> getCreateEntries() {
- return mCreateEntries;
+ return mCreateEntries.getList();
}
/** Returns the remote create entry to be displayed on the UI. */
@@ -159,7 +160,9 @@
* Builds a new instance of {@link BeginCreateCredentialResponse}.
*/
public @NonNull BeginCreateCredentialResponse build() {
- return new BeginCreateCredentialResponse(mCreateEntries, mRemoteCreateEntry);
+ return new BeginCreateCredentialResponse(
+ new ParceledListSlice<>(mCreateEntries),
+ mRemoteCreateEntry);
}
}
}
diff --git a/core/java/android/service/credentials/BeginGetCredentialResponse.java b/core/java/android/service/credentials/BeginGetCredentialResponse.java
index e25b686..5ed06ac 100644
--- a/core/java/android/service/credentials/BeginGetCredentialResponse.java
+++ b/core/java/android/service/credentials/BeginGetCredentialResponse.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.content.pm.ParceledListSlice;
import android.os.Parcel;
import android.os.Parcelable;
@@ -35,13 +36,13 @@
*/
public final class BeginGetCredentialResponse implements Parcelable {
/** List of credential entries to be displayed on the UI. */
- private final @NonNull List<CredentialEntry> mCredentialEntries;
+ private final @NonNull ParceledListSlice<CredentialEntry> mCredentialEntries;
/** List of authentication entries to be displayed on the UI. */
- private final @NonNull List<Action> mAuthenticationEntries;
+ private final @NonNull ParceledListSlice<Action> mAuthenticationEntries;
/** List of provider actions to be displayed on the UI. */
- private final @NonNull List<Action> mActions;
+ private final @NonNull ParceledListSlice<Action> mActions;
/** Remote credential entry to get the response from a different device. */
private final @Nullable RemoteEntry mRemoteCredentialEntry;
@@ -51,31 +52,30 @@
* or {@link Action} to return.
*/
public BeginGetCredentialResponse() {
- this(/*credentialEntries=*/new ArrayList<>(),
- /*authenticationActions=*/new ArrayList<>(),
- /*actions=*/new ArrayList<>(),
+ this(/*credentialEntries=*/new ParceledListSlice<>(new ArrayList<>()),
+ /*authenticationEntries=*/new ParceledListSlice<>(new ArrayList<>()),
+ /*actions=*/new ParceledListSlice<>(new ArrayList<>()),
/*remoteCredentialEntry=*/null);
}
- private BeginGetCredentialResponse(@NonNull List<CredentialEntry> credentialEntries,
- @NonNull List<Action> authenticationEntries, @NonNull List<Action> actions,
+ private BeginGetCredentialResponse(
+ @NonNull ParceledListSlice<CredentialEntry> credentialEntries,
+ @NonNull ParceledListSlice<Action> authenticationEntries,
+ @NonNull ParceledListSlice<Action> actions,
@Nullable RemoteEntry remoteCredentialEntry) {
- mCredentialEntries = new ArrayList<>(credentialEntries);
- mAuthenticationEntries = new ArrayList<>(authenticationEntries);
- mActions = new ArrayList<>(actions);
+ mCredentialEntries = credentialEntries;
+ mAuthenticationEntries = authenticationEntries;
+ mActions = actions;
mRemoteCredentialEntry = remoteCredentialEntry;
}
private BeginGetCredentialResponse(@NonNull Parcel in) {
- List<CredentialEntry> credentialEntries = new ArrayList<>();
- in.readTypedList(credentialEntries, CredentialEntry.CREATOR);
- mCredentialEntries = credentialEntries;
- List<Action> authenticationEntries = new ArrayList<>();
- in.readTypedList(authenticationEntries, Action.CREATOR);
- mAuthenticationEntries = authenticationEntries;
- List<Action> actions = new ArrayList<>();
- in.readTypedList(actions, Action.CREATOR);
- mActions = actions;
+ mCredentialEntries = in.readParcelable(
+ null, android.content.pm.ParceledListSlice.class);
+ mAuthenticationEntries = in.readParcelable(
+ null, android.content.pm.ParceledListSlice.class);
+ mActions = in.readParcelable(
+ null, android.content.pm.ParceledListSlice.class);
mRemoteCredentialEntry = in.readTypedObject(RemoteEntry.CREATOR);
}
@@ -99,9 +99,9 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeTypedList(mCredentialEntries, flags);
- dest.writeTypedList(mAuthenticationEntries, flags);
- dest.writeTypedList(mActions, flags);
+ dest.writeParcelable(mCredentialEntries, flags);
+ dest.writeParcelable(mAuthenticationEntries, flags);
+ dest.writeParcelable(mActions, flags);
dest.writeTypedObject(mRemoteCredentialEntry, flags);
}
@@ -109,21 +109,22 @@
* Returns the list of credential entries to be displayed on the UI.
*/
public @NonNull List<CredentialEntry> getCredentialEntries() {
- return mCredentialEntries;
+ return mCredentialEntries.getList();
}
/**
* Returns the list of authentication entries to be displayed on the UI.
*/
public @NonNull List<Action> getAuthenticationActions() {
- return mAuthenticationEntries;
+ return mAuthenticationEntries.getList();
}
/**
* Returns the list of actions to be displayed on the UI.
*/
public @NonNull List<Action> getActions() {
- return mActions;
+
+ return mActions.getList();
}
/**
@@ -268,8 +269,11 @@
* Builds a {@link BeginGetCredentialResponse} instance.
*/
public @NonNull BeginGetCredentialResponse build() {
- return new BeginGetCredentialResponse(mCredentialEntries, mAuthenticationEntries,
- mActions, mRemoteCredentialEntry);
+ return new BeginGetCredentialResponse(
+ new ParceledListSlice<>(mCredentialEntries),
+ new ParceledListSlice<>(mAuthenticationEntries),
+ new ParceledListSlice<>(mActions),
+ mRemoteCredentialEntry);
}
}
}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 402da28..828c062 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -45,7 +45,6 @@
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.PluralsMessageFormatter;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -59,7 +58,6 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
@@ -310,86 +308,6 @@
return buffer.toString();
}
- public Diff diff(ZenModeConfig to) {
- final Diff d = new Diff();
- if (to == null) {
- return d.addLine("config", "delete");
- }
- if (user != to.user) {
- d.addLine("user", user, to.user);
- }
- if (allowAlarms != to.allowAlarms) {
- d.addLine("allowAlarms", allowAlarms, to.allowAlarms);
- }
- if (allowMedia != to.allowMedia) {
- d.addLine("allowMedia", allowMedia, to.allowMedia);
- }
- if (allowSystem != to.allowSystem) {
- d.addLine("allowSystem", allowSystem, to.allowSystem);
- }
- if (allowCalls != to.allowCalls) {
- d.addLine("allowCalls", allowCalls, to.allowCalls);
- }
- if (allowReminders != to.allowReminders) {
- d.addLine("allowReminders", allowReminders, to.allowReminders);
- }
- if (allowEvents != to.allowEvents) {
- d.addLine("allowEvents", allowEvents, to.allowEvents);
- }
- if (allowRepeatCallers != to.allowRepeatCallers) {
- d.addLine("allowRepeatCallers", allowRepeatCallers, to.allowRepeatCallers);
- }
- if (allowMessages != to.allowMessages) {
- d.addLine("allowMessages", allowMessages, to.allowMessages);
- }
- if (allowCallsFrom != to.allowCallsFrom) {
- d.addLine("allowCallsFrom", allowCallsFrom, to.allowCallsFrom);
- }
- if (allowMessagesFrom != to.allowMessagesFrom) {
- d.addLine("allowMessagesFrom", allowMessagesFrom, to.allowMessagesFrom);
- }
- if (suppressedVisualEffects != to.suppressedVisualEffects) {
- d.addLine("suppressedVisualEffects", suppressedVisualEffects,
- to.suppressedVisualEffects);
- }
- final ArraySet<String> allRules = new ArraySet<>();
- addKeys(allRules, automaticRules);
- addKeys(allRules, to.automaticRules);
- final int N = allRules.size();
- for (int i = 0; i < N; i++) {
- final String rule = allRules.valueAt(i);
- final ZenRule fromRule = automaticRules != null ? automaticRules.get(rule) : null;
- final ZenRule toRule = to.automaticRules != null ? to.automaticRules.get(rule) : null;
- ZenRule.appendDiff(d, "automaticRule[" + rule + "]", fromRule, toRule);
- }
- ZenRule.appendDiff(d, "manualRule", manualRule, to.manualRule);
-
- if (areChannelsBypassingDnd != to.areChannelsBypassingDnd) {
- d.addLine("areChannelsBypassingDnd", areChannelsBypassingDnd,
- to.areChannelsBypassingDnd);
- }
- return d;
- }
-
- public static Diff diff(ZenModeConfig from, ZenModeConfig to) {
- if (from == null) {
- final Diff d = new Diff();
- if (to != null) {
- d.addLine("config", "insert");
- }
- return d;
- }
- return from.diff(to);
- }
-
- private static <T> void addKeys(ArraySet<T> set, ArrayMap<T, ?> map) {
- if (map != null) {
- for (int i = 0; i < map.size(); i++) {
- set.add(map.keyAt(i));
- }
- }
- }
-
public boolean isValid() {
if (!isValidManualRule(manualRule)) return false;
final int N = automaticRules.size();
@@ -1922,66 +1840,6 @@
proto.end(token);
}
- private static void appendDiff(Diff d, String item, ZenRule from, ZenRule to) {
- if (d == null) return;
- if (from == null) {
- if (to != null) {
- d.addLine(item, "insert");
- }
- return;
- }
- from.appendDiff(d, item, to);
- }
-
- private void appendDiff(Diff d, String item, ZenRule to) {
- if (to == null) {
- d.addLine(item, "delete");
- return;
- }
- if (enabled != to.enabled) {
- d.addLine(item, "enabled", enabled, to.enabled);
- }
- if (snoozing != to.snoozing) {
- d.addLine(item, "snoozing", snoozing, to.snoozing);
- }
- if (!Objects.equals(name, to.name)) {
- d.addLine(item, "name", name, to.name);
- }
- if (zenMode != to.zenMode) {
- d.addLine(item, "zenMode", zenMode, to.zenMode);
- }
- if (!Objects.equals(conditionId, to.conditionId)) {
- d.addLine(item, "conditionId", conditionId, to.conditionId);
- }
- if (!Objects.equals(condition, to.condition)) {
- d.addLine(item, "condition", condition, to.condition);
- }
- if (!Objects.equals(component, to.component)) {
- d.addLine(item, "component", component, to.component);
- }
- if (!Objects.equals(configurationActivity, to.configurationActivity)) {
- d.addLine(item, "configActivity", configurationActivity, to.configurationActivity);
- }
- if (!Objects.equals(id, to.id)) {
- d.addLine(item, "id", id, to.id);
- }
- if (creationTime != to.creationTime) {
- d.addLine(item, "creationTime", creationTime, to.creationTime);
- }
- if (!Objects.equals(enabler, to.enabler)) {
- d.addLine(item, "enabler", enabler, to.enabler);
- }
- if (!Objects.equals(zenPolicy, to.zenPolicy)) {
- d.addLine(item, "zenPolicy", zenPolicy, to.zenPolicy);
- }
- if (modified != to.modified) {
- d.addLine(item, "modified", modified, to.modified);
- }
- if (!Objects.equals(pkg, to.pkg)) {
- d.addLine(item, "pkg", pkg, to.pkg);
- }
- }
-
@Override
public boolean equals(@Nullable Object o) {
if (!(o instanceof ZenRule)) return false;
@@ -2040,40 +1898,6 @@
};
}
- public static class Diff {
- private final ArrayList<String> lines = new ArrayList<>();
-
- @Override
- public String toString() {
- final StringBuilder sb = new StringBuilder("Diff[");
- final int N = lines.size();
- for (int i = 0; i < N; i++) {
- if (i > 0) {
- sb.append(",\n");
- }
- sb.append(lines.get(i));
- }
- return sb.append(']').toString();
- }
-
- private Diff addLine(String item, String action) {
- lines.add(item + ":" + action);
- return this;
- }
-
- public Diff addLine(String item, String subitem, Object from, Object to) {
- return addLine(item + "." + subitem, from, to);
- }
-
- public Diff addLine(String item, Object from, Object to) {
- return addLine(item, from + "->" + to);
- }
-
- public boolean isEmpty() {
- return lines.isEmpty();
- }
- }
-
/**
* Determines whether dnd behavior should mute all ringer-controlled sounds
* This includes notification, ringer and system sounds
diff --git a/core/java/android/service/notification/ZenModeDiff.java b/core/java/android/service/notification/ZenModeDiff.java
new file mode 100644
index 0000000..c7b89eb
--- /dev/null
+++ b/core/java/android/service/notification/ZenModeDiff.java
@@ -0,0 +1,542 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.notification;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * ZenModeDiff is a utility class meant to encapsulate the diff between ZenModeConfigs and their
+ * subcomponents (automatic and manual ZenRules).
+ * @hide
+ */
+public class ZenModeDiff {
+ /**
+ * Enum representing whether the existence of a config or rule has changed (added or removed,
+ * or "none" meaning there is no change, which may either mean both null, or there exists a
+ * diff in fields rather than add/remove).
+ */
+ @IntDef(value = {
+ NONE,
+ ADDED,
+ REMOVED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ExistenceChange{}
+
+ public static final int NONE = 0;
+ public static final int ADDED = 1;
+ public static final int REMOVED = 2;
+
+ /**
+ * Diff class representing an individual field diff.
+ * @param <T> The type of the field.
+ */
+ public static class FieldDiff<T> {
+ private final T mFrom;
+ private final T mTo;
+
+ /**
+ * Constructor to create a FieldDiff object with the given values.
+ * @param from from (old) value
+ * @param to to (new) value
+ */
+ public FieldDiff(@Nullable T from, @Nullable T to) {
+ mFrom = from;
+ mTo = to;
+ }
+
+ /**
+ * Get the "from" value
+ */
+ public T from() {
+ return mFrom;
+ }
+
+ /**
+ * Get the "to" value
+ */
+ public T to() {
+ return mTo;
+ }
+
+ /**
+ * Get the string representation of this field diff, in the form of "from->to".
+ */
+ @Override
+ public String toString() {
+ return mFrom + "->" + mTo;
+ }
+
+ /**
+ * Returns whether this represents an actual diff.
+ */
+ public boolean hasDiff() {
+ // note that Objects.equals handles null values gracefully.
+ return !Objects.equals(mFrom, mTo);
+ }
+ }
+
+ /**
+ * Base diff class that contains info about whether something was added, and a set of named
+ * fields that changed.
+ * Extend for diffs of specific types of objects.
+ */
+ private abstract static class BaseDiff {
+ // Whether the diff was added or removed
+ @ExistenceChange private int mExists = NONE;
+
+ // Map from field name to diffs for any standalone fields in the object.
+ private ArrayMap<String, FieldDiff> mFields = new ArrayMap<>();
+
+ // Functions for actually diffing objects and string representations have to be implemented
+ // by subclasses.
+
+ /**
+ * Return whether this diff represents any changes.
+ */
+ public abstract boolean hasDiff();
+
+ /**
+ * Return a string representation of the diff.
+ */
+ public abstract String toString();
+
+ /**
+ * Constructor that takes the two objects meant to be compared. This constructor sets
+ * whether there is an existence change (added or removed).
+ * @param from previous Object
+ * @param to new Object
+ */
+ BaseDiff(Object from, Object to) {
+ if (from == null) {
+ if (to != null) {
+ mExists = ADDED;
+ }
+ // If both are null, there isn't an existence change; callers/inheritors must handle
+ // the both null case.
+ } else if (to == null) {
+ // in this case, we know that from != null
+ mExists = REMOVED;
+ }
+
+ // Subclasses should implement the actual diffing functionality in their own
+ // constructors.
+ }
+
+ /**
+ * Add a diff for a specific field to the map.
+ * @param name field name
+ * @param diff FieldDiff object representing the diff
+ */
+ final void addField(String name, FieldDiff diff) {
+ mFields.put(name, diff);
+ }
+
+ /**
+ * Returns whether this diff represents a config being newly added.
+ */
+ public final boolean wasAdded() {
+ return mExists == ADDED;
+ }
+
+ /**
+ * Returns whether this diff represents a config being removed.
+ */
+ public final boolean wasRemoved() {
+ return mExists == REMOVED;
+ }
+
+ /**
+ * Returns whether this diff represents an object being either added or removed.
+ */
+ public final boolean hasExistenceChange() {
+ return mExists != NONE;
+ }
+
+ /**
+ * Returns whether there are any individual field diffs.
+ */
+ public final boolean hasFieldDiffs() {
+ return mFields.size() > 0;
+ }
+
+ /**
+ * Returns the diff for the specific named field if it exists
+ */
+ public final FieldDiff getDiffForField(String name) {
+ return mFields.getOrDefault(name, null);
+ }
+
+ /**
+ * Get the set of all field names with some diff.
+ */
+ public final Set<String> fieldNamesWithDiff() {
+ return mFields.keySet();
+ }
+ }
+
+ /**
+ * Diff class representing a diff between two ZenModeConfigs.
+ */
+ public static class ConfigDiff extends BaseDiff {
+ // Rules. Automatic rule map is keyed by the rule name.
+ private final ArrayMap<String, RuleDiff> mAutomaticRulesDiff = new ArrayMap<>();
+ private RuleDiff mManualRuleDiff;
+
+ // Helpers for string generation
+ private static final String ALLOW_CALLS_FROM_FIELD = "allowCallsFrom";
+ private static final String ALLOW_MESSAGES_FROM_FIELD = "allowMessagesFrom";
+ private static final String ALLOW_CONVERSATIONS_FROM_FIELD = "allowConversationsFrom";
+ private static final Set<String> PEOPLE_TYPE_FIELDS =
+ Set.of(ALLOW_CALLS_FROM_FIELD, ALLOW_MESSAGES_FROM_FIELD);
+
+ /**
+ * Create a diff that contains diffs between the "from" and "to" ZenModeConfigs.
+ *
+ * @param from previous ZenModeConfig
+ * @param to new ZenModeConfig
+ */
+ public ConfigDiff(ZenModeConfig from, ZenModeConfig to) {
+ super(from, to);
+ // If both are null skip
+ if (from == null && to == null) {
+ return;
+ }
+ if (hasExistenceChange()) {
+ // either added or removed; return here. otherwise (they're not both null) there's
+ // field diffs.
+ return;
+ }
+
+ // Now we compare all the fields, knowing there's a diff and that neither is null
+ if (from.user != to.user) {
+ addField("user", new FieldDiff<>(from.user, to.user));
+ }
+ if (from.allowAlarms != to.allowAlarms) {
+ addField("allowAlarms", new FieldDiff<>(from.allowAlarms, to.allowAlarms));
+ }
+ if (from.allowMedia != to.allowMedia) {
+ addField("allowMedia", new FieldDiff<>(from.allowMedia, to.allowMedia));
+ }
+ if (from.allowSystem != to.allowSystem) {
+ addField("allowSystem", new FieldDiff<>(from.allowSystem, to.allowSystem));
+ }
+ if (from.allowCalls != to.allowCalls) {
+ addField("allowCalls", new FieldDiff<>(from.allowCalls, to.allowCalls));
+ }
+ if (from.allowReminders != to.allowReminders) {
+ addField("allowReminders",
+ new FieldDiff<>(from.allowReminders, to.allowReminders));
+ }
+ if (from.allowEvents != to.allowEvents) {
+ addField("allowEvents", new FieldDiff<>(from.allowEvents, to.allowEvents));
+ }
+ if (from.allowRepeatCallers != to.allowRepeatCallers) {
+ addField("allowRepeatCallers",
+ new FieldDiff<>(from.allowRepeatCallers, to.allowRepeatCallers));
+ }
+ if (from.allowMessages != to.allowMessages) {
+ addField("allowMessages",
+ new FieldDiff<>(from.allowMessages, to.allowMessages));
+ }
+ if (from.allowConversations != to.allowConversations) {
+ addField("allowConversations",
+ new FieldDiff<>(from.allowConversations, to.allowConversations));
+ }
+ if (from.allowCallsFrom != to.allowCallsFrom) {
+ addField("allowCallsFrom",
+ new FieldDiff<>(from.allowCallsFrom, to.allowCallsFrom));
+ }
+ if (from.allowMessagesFrom != to.allowMessagesFrom) {
+ addField("allowMessagesFrom",
+ new FieldDiff<>(from.allowMessagesFrom, to.allowMessagesFrom));
+ }
+ if (from.allowConversationsFrom != to.allowConversationsFrom) {
+ addField("allowConversationsFrom",
+ new FieldDiff<>(from.allowConversationsFrom, to.allowConversationsFrom));
+ }
+ if (from.suppressedVisualEffects != to.suppressedVisualEffects) {
+ addField("suppressedVisualEffects",
+ new FieldDiff<>(from.suppressedVisualEffects, to.suppressedVisualEffects));
+ }
+ if (from.areChannelsBypassingDnd != to.areChannelsBypassingDnd) {
+ addField("areChannelsBypassingDnd",
+ new FieldDiff<>(from.areChannelsBypassingDnd, to.areChannelsBypassingDnd));
+ }
+
+ // Compare automatic and manual rules
+ final ArraySet<String> allRules = new ArraySet<>();
+ addKeys(allRules, from.automaticRules);
+ addKeys(allRules, to.automaticRules);
+ final int num = allRules.size();
+ for (int i = 0; i < num; i++) {
+ final String rule = allRules.valueAt(i);
+ final ZenModeConfig.ZenRule
+ fromRule = from.automaticRules != null ? from.automaticRules.get(rule)
+ : null;
+ final ZenModeConfig.ZenRule
+ toRule = to.automaticRules != null ? to.automaticRules.get(rule) : null;
+ RuleDiff ruleDiff = new RuleDiff(fromRule, toRule);
+ if (ruleDiff.hasDiff()) {
+ mAutomaticRulesDiff.put(rule, ruleDiff);
+ }
+ }
+ // If there's no diff this may turn out to be null, but that's also fine
+ RuleDiff manualRuleDiff = new RuleDiff(from.manualRule, to.manualRule);
+ if (manualRuleDiff.hasDiff()) {
+ mManualRuleDiff = manualRuleDiff;
+ }
+ }
+
+ private static <T> void addKeys(ArraySet<T> set, ArrayMap<T, ?> map) {
+ if (map != null) {
+ for (int i = 0; i < map.size(); i++) {
+ set.add(map.keyAt(i));
+ }
+ }
+ }
+
+ /**
+ * Returns whether this diff object contains any diffs in any field.
+ */
+ @Override
+ public boolean hasDiff() {
+ return hasExistenceChange()
+ || hasFieldDiffs()
+ || mManualRuleDiff != null
+ || mAutomaticRulesDiff.size() > 0;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("Diff[");
+ if (!hasDiff()) {
+ sb.append("no changes");
+ }
+
+ // If added or deleted, then that's just the end of it
+ if (hasExistenceChange()) {
+ if (wasAdded()) {
+ sb.append("added");
+ } else if (wasRemoved()) {
+ sb.append("removed");
+ }
+ }
+
+ // Handle top-level field change
+ boolean first = true;
+ for (String key : fieldNamesWithDiff()) {
+ FieldDiff diff = getDiffForField(key);
+ if (diff == null) {
+ // this shouldn't happen, but
+ continue;
+ }
+ if (first) {
+ first = false;
+ } else {
+ sb.append(",\n");
+ }
+
+ // Some special handling for people- and conversation-type fields for readability
+ if (PEOPLE_TYPE_FIELDS.contains(key)) {
+ sb.append(key);
+ sb.append(":");
+ sb.append(ZenModeConfig.sourceToString((int) diff.from()));
+ sb.append("->");
+ sb.append(ZenModeConfig.sourceToString((int) diff.to()));
+ } else if (key.equals(ALLOW_CONVERSATIONS_FROM_FIELD)) {
+ sb.append(key);
+ sb.append(":");
+ sb.append(ZenPolicy.conversationTypeToString((int) diff.from()));
+ sb.append("->");
+ sb.append(ZenPolicy.conversationTypeToString((int) diff.to()));
+ } else {
+ sb.append(key);
+ sb.append(":");
+ sb.append(diff);
+ }
+ }
+
+ // manual rule
+ if (mManualRuleDiff != null && mManualRuleDiff.hasDiff()) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append(",\n");
+ }
+ sb.append("manualRule:");
+ sb.append(mManualRuleDiff);
+ }
+
+ // automatic rules
+ for (String rule : mAutomaticRulesDiff.keySet()) {
+ RuleDiff diff = mAutomaticRulesDiff.get(rule);
+ if (diff != null && diff.hasDiff()) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append(",\n");
+ }
+ sb.append("automaticRule[");
+ sb.append(rule);
+ sb.append("]:");
+ sb.append(diff);
+ }
+ }
+
+ return sb.append(']').toString();
+ }
+
+ /**
+ * Get the diff in manual rule, if it exists.
+ */
+ public RuleDiff getManualRuleDiff() {
+ return mManualRuleDiff;
+ }
+
+ /**
+ * Get the full map of automatic rule diffs, or null if there are no diffs.
+ */
+ public ArrayMap<String, RuleDiff> getAllAutomaticRuleDiffs() {
+ return (mAutomaticRulesDiff.size() > 0) ? mAutomaticRulesDiff : null;
+ }
+ }
+
+ /**
+ * Diff class representing a change between two ZenRules.
+ */
+ public static class RuleDiff extends BaseDiff {
+ /**
+ * Create a RuleDiff representing the difference between two ZenRule objects.
+ * @param from previous ZenRule
+ * @param to new ZenRule
+ * @return The diff between the two given ZenRules
+ */
+ public RuleDiff(ZenModeConfig.ZenRule from, ZenModeConfig.ZenRule to) {
+ super(from, to);
+ // Short-circuit the both-null case
+ if (from == null && to == null) {
+ return;
+ }
+ // Return if the diff was added or removed
+ if (hasExistenceChange()) {
+ return;
+ }
+
+ if (from.enabled != to.enabled) {
+ addField("enabled", new FieldDiff<>(from.enabled, to.enabled));
+ }
+ if (from.snoozing != to.snoozing) {
+ addField("snoozing", new FieldDiff<>(from.snoozing, to.snoozing));
+ }
+ if (!Objects.equals(from.name, to.name)) {
+ addField("name", new FieldDiff<>(from.name, to.name));
+ }
+ if (from.zenMode != to.zenMode) {
+ addField("zenMode", new FieldDiff<>(from.zenMode, to.zenMode));
+ }
+ if (!Objects.equals(from.conditionId, to.conditionId)) {
+ addField("conditionId", new FieldDiff<>(from.conditionId, to.conditionId));
+ }
+ if (!Objects.equals(from.condition, to.condition)) {
+ addField("condition", new FieldDiff<>(from.condition, to.condition));
+ }
+ if (!Objects.equals(from.component, to.component)) {
+ addField("component", new FieldDiff<>(from.component, to.component));
+ }
+ if (!Objects.equals(from.configurationActivity, to.configurationActivity)) {
+ addField("configurationActivity", new FieldDiff<>(
+ from.configurationActivity, to.configurationActivity));
+ }
+ if (!Objects.equals(from.id, to.id)) {
+ addField("id", new FieldDiff<>(from.id, to.id));
+ }
+ if (from.creationTime != to.creationTime) {
+ addField("creationTime",
+ new FieldDiff<>(from.creationTime, to.creationTime));
+ }
+ if (!Objects.equals(from.enabler, to.enabler)) {
+ addField("enabler", new FieldDiff<>(from.enabler, to.enabler));
+ }
+ if (!Objects.equals(from.zenPolicy, to.zenPolicy)) {
+ addField("zenPolicy", new FieldDiff<>(from.zenPolicy, to.zenPolicy));
+ }
+ if (from.modified != to.modified) {
+ addField("modified", new FieldDiff<>(from.modified, to.modified));
+ }
+ if (!Objects.equals(from.pkg, to.pkg)) {
+ addField("pkg", new FieldDiff<>(from.pkg, to.pkg));
+ }
+ }
+
+ /**
+ * Returns whether this object represents an actual diff.
+ */
+ @Override
+ public boolean hasDiff() {
+ return hasExistenceChange() || hasFieldDiffs();
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("ZenRuleDiff{");
+ // If there's no diff, probably we haven't actually let this object continue existing
+ // but might as well handle this case.
+ if (!hasDiff()) {
+ sb.append("no changes");
+ }
+
+ // If added or deleted, then that's just the end of it
+ if (hasExistenceChange()) {
+ if (wasAdded()) {
+ sb.append("added");
+ } else if (wasRemoved()) {
+ sb.append("removed");
+ }
+ }
+
+ // Go through all of the individual fields
+ boolean first = true;
+ for (String key : fieldNamesWithDiff()) {
+ FieldDiff diff = getDiffForField(key);
+ if (diff == null) {
+ // this shouldn't happen, but
+ continue;
+ }
+ if (first) {
+ first = false;
+ } else {
+ sb.append(", ");
+ }
+
+ sb.append(key);
+ sb.append(":");
+ sb.append(diff);
+ }
+
+ return sb.append("}").toString();
+ }
+ }
+}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index fcc64b0..68cce4a 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -50,6 +50,7 @@
import android.util.ArraySet;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IVoiceActionCheckCallback;
import com.android.internal.app.IVoiceInteractionManagerService;
@@ -200,6 +201,9 @@
private final Set<HotwordDetector> mActiveDetectors = new ArraySet<>();
+ // True if any of the createAOHD methods should use the test ST module.
+ @GuardedBy("mLock")
+ private boolean mTestModuleForAlwaysOnHotwordDetectorEnabled = false;
private void onDetectorRemoteException(@NonNull IBinder token, int detectorType) {
Log.d(TAG, "onDetectorRemoteException for " + HotwordDetector.detectorTypeToString(
@@ -512,14 +516,14 @@
Objects.requireNonNull(callback);
return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
/* supportHotwordDetectionService= */ false, /* options= */ null,
- /* sharedMemory= */ null, /* moduleProperties */ null, executor, callback);
+ /* sharedMemory= */ null, /* moduleProperties= */ null, executor, callback);
}
/**
* Same as {@link createAlwaysOnHotwordDetector(String, Locale, Executor,
* AlwaysOnHotwordDetector.Callback)}, but allow explicit selection of the underlying ST
* module to attach to.
- * Use {@link listModuleProperties} to get available modules to attach to.
+ * Use {@link #listModuleProperties()} to get available modules to attach to.
* @hide
*/
@TestApi
@@ -645,14 +649,14 @@
Objects.requireNonNull(callback);
return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
/* supportHotwordDetectionService= */ true, options, sharedMemory,
- /* moduleProperties */ null, executor, callback);
+ /* moduleProperties= */ null, executor, callback);
}
/**
* Same as {@link createAlwaysOnHotwordDetector(String, Locale,
* PersistableBundle, SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)},
* but allow explicit selection of the underlying ST module to attach to.
- * Use {@link listModuleProperties} to get available modules to attach to.
+ * Use {@link #listModuleProperties()} to get available modules to attach to.
* @hide
*/
@TestApi
@@ -717,6 +721,10 @@
try {
dspDetector.registerOnDestroyListener(this::onHotwordDetectorDestroyed);
+ // Check if we are currently overridden, and should use the test module.
+ if (mTestModuleForAlwaysOnHotwordDetectorEnabled) {
+ moduleProperties = getTestModuleProperties();
+ }
// If moduleProperties is null, the default STModule is used.
dspDetector.initialize(options, sharedMemory, moduleProperties);
} catch (Exception e) {
@@ -990,6 +998,44 @@
return mKeyphraseEnrollmentInfo;
}
+
+ /**
+ * Configure {@link createAlwaysOnHotwordDetector(String, Locale,
+ * SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)}
+ * and similar overloads to utilize the test SoundTrigger module instead of the
+ * actual DSP module.
+ * @param isEnabled - {@code true} if subsequently created {@link AlwaysOnHotwordDetector}
+ * objects should attach to a test module. {@code false} if subsequently created
+ * {@link AlwaysOnHotwordDetector} should attach to the actual DSP module.
+ * @hide
+ */
+ @TestApi
+ public final void setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean isEnabled) {
+ synchronized (mLock) {
+ mTestModuleForAlwaysOnHotwordDetectorEnabled = isEnabled;
+ }
+ }
+
+ /**
+ * Get the {@link SoundTrigger.ModuleProperties} representing the fake
+ * STHAL to attach to via {@link createAlwaysOnHotwordDetector(String, Locale,
+ * SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)} and
+ * similar overloads for test purposes.
+ * @return ModuleProperties to use for test purposes.
+ */
+ private final @NonNull SoundTrigger.ModuleProperties getTestModuleProperties() {
+ var moduleProps = listModuleProperties()
+ .stream()
+ .filter((SoundTrigger.ModuleProperties prop)
+ -> prop.getSupportedModelArch().equals(SoundTrigger.FAKE_HAL_ARCH))
+ .findFirst()
+ .orElse(null);
+ if (moduleProps == null) {
+ throw new IllegalStateException("Fake ST HAL should always be available");
+ }
+ return moduleProps;
+ }
+
/**
* Checks if a given keyphrase and locale are supported to create an
* {@link AlwaysOnHotwordDetector}.
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index c329508..d87198a0 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -128,6 +128,12 @@
*/
public static final String SETTINGS_ENABLE_SPA_PHASE2 = "settings_enable_spa_phase2";
+ /**
+ * Enable the SPA metrics writing.
+ * @hide
+ */
+ public static final String SETTINGS_ENABLE_SPA_METRICS = "settings_enable_spa_metrics";
+
/** Flag to enable/disable adb log metrics
* @hide
*/
@@ -226,6 +232,7 @@
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "true");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA_PHASE2, "false");
+ DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA_METRICS, "false");
DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "true");
DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
diff --git a/core/java/android/view/InputEvent.java b/core/java/android/view/InputEvent.java
index 0b4adae..a8e68b71 100644
--- a/core/java/android/view/InputEvent.java
+++ b/core/java/android/view/InputEvent.java
@@ -16,6 +16,7 @@
package android.view;
+import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
@@ -100,6 +101,7 @@
* @return The display id associated with the event.
* @hide
*/
+ @TestApi
public abstract int getDisplayId();
/**
@@ -107,6 +109,7 @@
* @param displayId
* @hide
*/
+ @TestApi
public abstract void setDisplayId(int displayId);
/**
* Copies the event.
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index b6d9400..858da55 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -2100,6 +2100,7 @@
}
/** @hide */
+ @TestApi
@Override
public final int getDisplayId() {
return mDisplayId;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2f5cd54..055b5cb 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -6946,6 +6946,7 @@
return;
}
final boolean needsStylusPointerIcon = event.isStylusPointer()
+ && event.isHoverEvent()
&& mInputManager.isStylusPointerIconEnabled();
if (needsStylusPointerIcon || event.isFromSource(InputDevice.SOURCE_MOUSE)) {
if (event.getActionMasked() == MotionEvent.ACTION_HOVER_ENTER
diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl
index ab7f602..ed751cb 100644
--- a/core/java/com/android/internal/app/ISoundTriggerService.aidl
+++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl
@@ -16,8 +16,9 @@
package com.android.internal.app;
-import android.media.permission.Identity;
import android.hardware.soundtrigger.SoundTrigger;
+import android.media.permission.Identity;
+import android.media.soundtrigger_middleware.ISoundTriggerInjection;
import com.android.internal.app.ISoundTriggerSession;
/**
@@ -74,4 +75,8 @@
*/
List<SoundTrigger.ModuleProperties> listModuleProperties(in Identity originatorIdentity);
+ /**
+ * Attach an HAL injection interface.
+ */
+ void attachInjection(ISoundTriggerInjection injection);
}
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 6b40d98..24d5afc 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -96,6 +96,21 @@
* @RequiresPermission Manifest.permission.MANAGE_VOICE_KEYPHRASES
*/
int deleteKeyphraseSoundModel(int keyphraseId, in String bcp47Locale);
+
+ /**
+ * Override the persistent enrolled model database with an in-memory
+ * fake for testing purposes.
+ *
+ * @param enabled - {@code true} to enable the test database. {@code false} to enable
+ * the real, persistent database.
+ * @param token - IBinder used to register a death listener to clean-up the override
+ * if tests do not clean up gracefully.
+ */
+ @EnforcePermission("MANAGE_VOICE_KEYPHRASES")
+ @JavaPassthrough(annotation= "@android.annotation.RequiresPermission(" +
+ "android.Manifest.permission.MANAGE_VOICE_KEYPHRASES)")
+ void setModelDatabaseForTestEnabled(boolean enabled, IBinder token);
+
/**
* Indicates if there's a keyphrase sound model available for the given keyphrase ID and the
* user ID of the caller.
@@ -106,6 +121,7 @@
* @param bcp47Locale The BCP47 language tag for the keyphrase's locale.
*/
boolean isEnrolledForKeyphrase(int keyphraseId, String bcp47Locale);
+
/**
* Generates KeyphraseMetadata for an enrolled sound model based on keyphrase string, locale,
* and the user ID of the caller.
diff --git a/core/java/com/android/internal/util/DumpUtils.java b/core/java/com/android/internal/util/DumpUtils.java
index f6d80a5..8fe2b9c 100644
--- a/core/java/com/android/internal/util/DumpUtils.java
+++ b/core/java/com/android/internal/util/DumpUtils.java
@@ -25,6 +25,7 @@
import android.os.Handler;
import android.text.TextUtils;
import android.util.Slog;
+import android.util.SparseArray;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -312,5 +313,85 @@
|| cn.flattenToString().toLowerCase().contains(filterString.toLowerCase());
};
}
-}
+ /**
+ * Lambda used to dump a key (and its index) while iterating though a collection.
+ */
+ public interface KeyDumper {
+
+ /** Dumps the index and key.*/
+ void dump(int index, int key);
+ }
+
+ /**
+ * Lambda used to dump a value while iterating though a collection.
+ *
+ * @param <T> type of the value.
+ */
+ public interface ValueDumper<T> {
+
+ /** Dumps the value.*/
+ void dump(T value);
+ }
+
+ /**
+ * Dumps a sparse array.
+ */
+ public static void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<?> array,
+ String name) {
+ dumpSparseArray(pw, prefix, array, name, /* keyDumper= */ null, /* valueDumper= */ null);
+ }
+
+ /**
+ * Dumps the values of a sparse array.
+ */
+ public static <T> void dumpSparseArrayValues(PrintWriter pw, String prefix,
+ SparseArray<T> array, String name) {
+ dumpSparseArray(pw, prefix, array, name, (i, k) -> {
+ pw.printf("%s%s", prefix, prefix);
+ }, /* valueDumper= */ null);
+ }
+
+ /**
+ * Dumps a sparse array, customizing each line.
+ */
+ public static <T> void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<T> array,
+ String name, @Nullable KeyDumper keyDumper, @Nullable ValueDumper<T> valueDumper) {
+ int size = array.size();
+ if (size == 0) {
+ pw.print(prefix);
+ pw.print("No ");
+ pw.print(name);
+ pw.println("s");
+ return;
+ }
+ pw.print(prefix);
+ pw.print(size);
+ pw.print(' ');
+ pw.print(name);
+ pw.println("(s):");
+
+ String prefix2 = prefix + prefix;
+ for (int i = 0; i < size; i++) {
+ int key = array.keyAt(i);
+ T value = array.valueAt(i);
+ if (keyDumper != null) {
+ keyDumper.dump(i, key);
+ } else {
+ pw.print(prefix2);
+ pw.print(i);
+ pw.print(": ");
+ pw.print(key);
+ pw.print("->");
+ }
+ if (value == null) {
+ pw.print("(null)");
+ } else if (valueDumper != null) {
+ valueDumper.dump(value);
+ } else {
+ pw.print(value);
+ }
+ pw.println();
+ }
+ }
+}
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index ed612a0..025a57d 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -1035,6 +1035,7 @@
optional int32 uid = 1;
repeated .android.app.ApplicationExitInfoProto app_exit_info = 2;
+ repeated .android.app.ApplicationExitInfoProto app_recoverable_crash = 3;
}
repeated User users = 2;
}
diff --git a/core/res/res/values/public-final.xml b/core/res/res/values/public-final.xml
index 85325fe..daa0f553 100644
--- a/core/res/res/values/public-final.xml
+++ b/core/res/res/values/public-final.xml
@@ -3402,4 +3402,343 @@
<!-- @hide @SystemApi -->
<public type="bool" name="config_enableQrCodeScannerOnLockScreen" id="0x01110008" />
+ <!-- ===============================================================
+ Resources added in version U of the platform
+
+ NOTE: After this version of the platform is forked, changes cannot be made to the root
+ branch's groups for that release. Only merge changes to the forked platform branch.
+ =============================================================== -->
+ <eat-comment/>
+
+ <staging-public-group-final type="attr" first-id="0x01ce0000">
+ <public name="handwritingBoundsOffsetLeft" />
+ <public name="handwritingBoundsOffsetTop" />
+ <public name="handwritingBoundsOffsetRight" />
+ <public name="handwritingBoundsOffsetBottom" />
+ <public name="accessibilityDataSensitive" />
+ <public name="enableTextStylingShortcuts" />
+ <public name="requiredDisplayCategory"/>
+ <public name="removed_maxConcurrentSessionsCount" />
+ <public name="visualQueryDetectionService" />
+ <public name="physicalKeyboardHintLanguageTag" />
+ <public name="physicalKeyboardHintLayoutType" />
+ <public name="allowSharedIsolatedProcess" />
+ <public name="keyboardLocale" />
+ <public name="keyboardLayoutType" />
+ <public name="allowUpdateOwnership" />
+ <public name="isCredential"/>
+ <public name="searchResultHighlightColor" />
+ <public name="focusedSearchResultHighlightColor" />
+ <public name="stylusHandwritingSettingsActivity" />
+ <public name="windowNoMoveAnimation" />
+ <public name="settingsSubtitle" />
+ <public name="capability" />
+ </staging-public-group-final>
+
+ <public type="attr" name="handwritingBoundsOffsetLeft" id="0x01010673" />
+ <public type="attr" name="handwritingBoundsOffsetTop" id="0x01010674" />
+ <public type="attr" name="handwritingBoundsOffsetRight" id="0x01010675" />
+ <public type="attr" name="handwritingBoundsOffsetBottom" id="0x01010676" />
+ <public type="attr" name="accessibilityDataSensitive" id="0x01010677" />
+ <public type="attr" name="enableTextStylingShortcuts" id="0x01010678" />
+ <public type="attr" name="requiredDisplayCategory" id="0x01010679" />
+ <public type="attr" name="visualQueryDetectionService" id="0x0101067a" />
+ <public type="attr" name="physicalKeyboardHintLanguageTag" id="0x0101067b" />
+ <public type="attr" name="physicalKeyboardHintLayoutType" id="0x0101067c" />
+ <public type="attr" name="allowSharedIsolatedProcess" id="0x0101067d" />
+ <public type="attr" name="keyboardLocale" id="0x0101067e" />
+ <public type="attr" name="keyboardLayoutType" id="0x0101067f" />
+ <public type="attr" name="allowUpdateOwnership" id="0x01010680" />
+ <public type="attr" name="isCredential" id="0x01010681" />
+ <public type="attr" name="searchResultHighlightColor" id="0x01010682" />
+ <public type="attr" name="focusedSearchResultHighlightColor" id="0x01010683" />
+ <public type="attr" name="stylusHandwritingSettingsActivity" id="0x01010684" />
+ <public type="attr" name="windowNoMoveAnimation" id="0x01010685" />
+ <public type="attr" name="settingsSubtitle" id="0x01010686" />
+ <public type="attr" name="capability" id="0x01010687" />
+
+ <staging-public-group-final type="id" first-id="0x01cd0000">
+ <public name="bold" />
+ <public name="italic" />
+ <public name="underline" />
+ <public name="accessibilityActionScrollInDirection" />
+ </staging-public-group-final>
+
+ <public type="id" name="bold" id="0x0102005b" />
+ <public type="id" name="italic" id="0x0102005c" />
+ <public type="id" name="underline" id="0x0102005d" />
+ <public type="id" name="accessibilityActionScrollInDirection" id="0x0102005e" />
+
+ <staging-public-group-final type="string" first-id="0x01cb0000">
+ <!-- @hide @SystemApi -->
+ <public name="config_systemWearHealthService" />
+ <!-- @hide @SystemApi -->
+ <public name="config_defaultNotes" />
+ <!-- @hide @SystemApi -->
+ <public name="config_systemFinancedDeviceController" />
+ <!-- @hide @SystemApi -->
+ <public name="config_systemCallStreaming" />
+ </staging-public-group-final>
+
+ <!-- @hide @SystemApi -->
+ <public type="string" name="config_systemWearHealthService" id="0x01040044" />
+ <!-- @hide @SystemApi -->
+ <public type="string" name="config_defaultNotes" id="0x01040045" />
+ <!-- @hide @SystemApi -->
+ <public type="string" name="config_systemFinancedDeviceController" id="0x01040046" />
+ <!-- @hide @SystemApi -->
+ <public type="string" name="config_systemCallStreaming" id="0x01040047" />
+
+ <staging-public-group-final type="dimen" first-id="0x01ca0000">
+ <!-- @hide @SystemApi -->
+ <public name="config_viewConfigurationHandwritingGestureLineMargin" />
+ </staging-public-group-final>
+
+ <!-- @hide @SystemApi -->
+ <public type="dimen" name="config_viewConfigurationHandwritingGestureLineMargin" id="0x0105000a" />
+
+ <staging-public-group-final type="color" first-id="0x01c90000">
+ <public name="system_primary_container_light" />
+ <public name="system_on_primary_container_light" />
+ <public name="system_primary_light" />
+ <public name="system_on_primary_light" />
+ <public name="system_secondary_container_light" />
+ <public name="system_on_secondary_container_light" />
+ <public name="system_secondary_light" />
+ <public name="system_on_secondary_light" />
+ <public name="system_tertiary_container_light" />
+ <public name="system_on_tertiary_container_light" />
+ <public name="system_tertiary_light" />
+ <public name="system_on_tertiary_light" />
+ <public name="system_background_light" />
+ <public name="system_on_background_light" />
+ <public name="system_surface_light" />
+ <public name="system_on_surface_light" />
+ <public name="system_surface_container_low_light" />
+ <public name="system_surface_container_lowest_light" />
+ <public name="system_surface_container_light" />
+ <public name="system_surface_container_high_light" />
+ <public name="system_surface_container_highest_light" />
+ <public name="system_surface_bright_light" />
+ <public name="system_surface_dim_light" />
+ <public name="system_surface_variant_light" />
+ <public name="system_on_surface_variant_light" />
+ <public name="system_outline_light" />
+ <public name="system_error_light" />
+ <public name="system_on_error_light" />
+ <public name="system_error_container_light" />
+ <public name="system_on_error_container_light" />
+ <public name="removed_system_primary_fixed_light" />
+ <public name="removed_system_primary_fixed_dim_light" />
+ <public name="removed_system_on_primary_fixed_light" />
+ <public name="removed_system_on_primary_fixed_variant_light" />
+ <public name="removed_system_secondary_fixed_light" />
+ <public name="removed_system_secondary_fixed_dim_light" />
+ <public name="removed_system_on_secondary_fixed_light" />
+ <public name="removed_system_on_secondary_fixed_variant_light" />
+ <public name="removed_system_tertiary_fixed_light" />
+ <public name="removed_system_tertiary_fixed_dim_light" />
+ <public name="removed_system_on_tertiary_fixed_light" />
+ <public name="removed_system_on_tertiary_fixed_variant_light" />
+ <public name="system_control_activated_light" />
+ <public name="system_control_normal_light" />
+ <public name="system_control_highlight_light" />
+ <public name="system_text_primary_inverse_light" />
+ <public name="system_text_secondary_and_tertiary_inverse_light" />
+ <public name="system_text_primary_inverse_disable_only_light" />
+ <public name="system_text_secondary_and_tertiary_inverse_disabled_light" />
+ <public name="system_text_hint_inverse_light" />
+ <public name="system_palette_key_color_primary_light" />
+ <public name="system_palette_key_color_secondary_light" />
+ <public name="system_palette_key_color_tertiary_light" />
+ <public name="system_palette_key_color_neutral_light" />
+ <public name="system_palette_key_color_neutral_variant_light" />
+ <public name="system_primary_container_dark"/>
+ <public name="system_on_primary_container_dark"/>
+ <public name="system_primary_dark"/>
+ <public name="system_on_primary_dark"/>
+ <public name="system_secondary_container_dark"/>
+ <public name="system_on_secondary_container_dark"/>
+ <public name="system_secondary_dark"/>
+ <public name="system_on_secondary_dark"/>
+ <public name="system_tertiary_container_dark"/>
+ <public name="system_on_tertiary_container_dark"/>
+ <public name="system_tertiary_dark"/>
+ <public name="system_on_tertiary_dark"/>
+ <public name="system_background_dark"/>
+ <public name="system_on_background_dark"/>
+ <public name="system_surface_dark"/>
+ <public name="system_on_surface_dark"/>
+ <public name="system_surface_container_low_dark"/>
+ <public name="system_surface_container_lowest_dark"/>
+ <public name="system_surface_container_dark"/>
+ <public name="system_surface_container_high_dark"/>
+ <public name="system_surface_container_highest_dark"/>
+ <public name="system_surface_bright_dark"/>
+ <public name="system_surface_dim_dark"/>
+ <public name="system_surface_variant_dark"/>
+ <public name="system_on_surface_variant_dark"/>
+ <public name="system_outline_dark"/>
+ <public name="system_error_dark"/>
+ <public name="system_on_error_dark"/>
+ <public name="system_error_container_dark"/>
+ <public name="system_on_error_container_dark"/>
+ <public name="removed_system_primary_fixed_dark"/>
+ <public name="removed_system_primary_fixed_dim_dark"/>
+ <public name="removed_system_on_primary_fixed_dark"/>
+ <public name="removed_system_on_primary_fixed_variant_dark"/>
+ <public name="removed_system_secondary_fixed_dark"/>
+ <public name="removed_system_secondary_fixed_dim_dark"/>
+ <public name="removed_system_on_secondary_fixed_dark"/>
+ <public name="removed_system_on_secondary_fixed_variant_dark"/>
+ <public name="removed_system_tertiary_fixed_dark"/>
+ <public name="removed_system_tertiary_fixed_dim_dark"/>
+ <public name="removed_system_on_tertiary_fixed_dark"/>
+ <public name="removed_system_on_tertiary_fixed_variant_dark"/>
+ <public name="system_control_activated_dark"/>
+ <public name="system_control_normal_dark"/>
+ <public name="system_control_highlight_dark"/>
+ <public name="system_text_primary_inverse_dark"/>
+ <public name="system_text_secondary_and_tertiary_inverse_dark"/>
+ <public name="system_text_primary_inverse_disable_only_dark"/>
+ <public name="system_text_secondary_and_tertiary_inverse_disabled_dark"/>
+ <public name="system_text_hint_inverse_dark"/>
+ <public name="system_palette_key_color_primary_dark"/>
+ <public name="system_palette_key_color_secondary_dark"/>
+ <public name="system_palette_key_color_tertiary_dark"/>
+ <public name="system_palette_key_color_neutral_dark"/>
+ <public name="system_palette_key_color_neutral_variant_dark"/>
+ <public name="system_primary_fixed" />
+ <public name="system_primary_fixed_dim" />
+ <public name="system_on_primary_fixed" />
+ <public name="system_on_primary_fixed_variant" />
+ <public name="system_secondary_fixed" />
+ <public name="system_secondary_fixed_dim" />
+ <public name="system_on_secondary_fixed" />
+ <public name="system_on_secondary_fixed_variant" />
+ <public name="system_tertiary_fixed" />
+ <public name="system_tertiary_fixed_dim" />
+ <public name="system_on_tertiary_fixed" />
+ <public name="system_on_tertiary_fixed_variant" />
+ <public name="system_outline_variant_light" />
+ <public name="system_outline_variant_dark" />
+ </staging-public-group-final>
+
+ <public type="color" name="system_primary_container_light" id="0x0106005e" />
+ <public type="color" name="system_on_primary_container_light" id="0x0106005f" />
+ <public type="color" name="system_primary_light" id="0x01060060" />
+ <public type="color" name="system_on_primary_light" id="0x01060061" />
+ <public type="color" name="system_secondary_container_light" id="0x01060062" />
+ <public type="color" name="system_on_secondary_container_light" id="0x01060063" />
+ <public type="color" name="system_secondary_light" id="0x01060064" />
+ <public type="color" name="system_on_secondary_light" id="0x01060065" />
+ <public type="color" name="system_tertiary_container_light" id="0x01060066" />
+ <public type="color" name="system_on_tertiary_container_light" id="0x01060067" />
+ <public type="color" name="system_tertiary_light" id="0x01060068" />
+ <public type="color" name="system_on_tertiary_light" id="0x01060069" />
+ <public type="color" name="system_background_light" id="0x0106006a" />
+ <public type="color" name="system_on_background_light" id="0x0106006b" />
+ <public type="color" name="system_surface_light" id="0x0106006c" />
+ <public type="color" name="system_on_surface_light" id="0x0106006d" />
+ <public type="color" name="system_surface_container_low_light" id="0x0106006e" />
+ <public type="color" name="system_surface_container_lowest_light" id="0x0106006f" />
+ <public type="color" name="system_surface_container_light" id="0x01060070" />
+ <public type="color" name="system_surface_container_high_light" id="0x01060071" />
+ <public type="color" name="system_surface_container_highest_light" id="0x01060072" />
+ <public type="color" name="system_surface_bright_light" id="0x01060073" />
+ <public type="color" name="system_surface_dim_light" id="0x01060074" />
+ <public type="color" name="system_surface_variant_light" id="0x01060075" />
+ <public type="color" name="system_on_surface_variant_light" id="0x01060076" />
+ <public type="color" name="system_outline_light" id="0x01060077" />
+ <public type="color" name="system_error_light" id="0x01060078" />
+ <public type="color" name="system_on_error_light" id="0x01060079" />
+ <public type="color" name="system_error_container_light" id="0x0106007a" />
+ <public type="color" name="system_on_error_container_light" id="0x0106007b" />
+ <public type="color" name="system_control_activated_light" id="0x0106007c" />
+ <public type="color" name="system_control_normal_light" id="0x0106007d" />
+ <public type="color" name="system_control_highlight_light" id="0x0106007e" />
+ <public type="color" name="system_text_primary_inverse_light" id="0x0106007f" />
+ <public type="color" name="system_text_secondary_and_tertiary_inverse_light" id="0x01060080" />
+ <public type="color" name="system_text_primary_inverse_disable_only_light" id="0x01060081" />
+ <public type="color" name="system_text_secondary_and_tertiary_inverse_disabled_light" id="0x01060082" />
+ <public type="color" name="system_text_hint_inverse_light" id="0x01060083" />
+ <public type="color" name="system_palette_key_color_primary_light" id="0x01060084" />
+ <public type="color" name="system_palette_key_color_secondary_light" id="0x01060085" />
+ <public type="color" name="system_palette_key_color_tertiary_light" id="0x01060086" />
+ <public type="color" name="system_palette_key_color_neutral_light" id="0x01060087" />
+ <public type="color" name="system_palette_key_color_neutral_variant_light" id="0x01060088" />
+ <public type="color" name="system_primary_container_dark" id="0x01060089" />
+ <public type="color" name="system_on_primary_container_dark" id="0x0106008a" />
+ <public type="color" name="system_primary_dark" id="0x0106008b" />
+ <public type="color" name="system_on_primary_dark" id="0x0106008c" />
+ <public type="color" name="system_secondary_container_dark" id="0x0106008d" />
+ <public type="color" name="system_on_secondary_container_dark" id="0x0106008e" />
+ <public type="color" name="system_secondary_dark" id="0x0106008f" />
+ <public type="color" name="system_on_secondary_dark" id="0x01060090" />
+ <public type="color" name="system_tertiary_container_dark" id="0x01060091" />
+ <public type="color" name="system_on_tertiary_container_dark" id="0x01060092" />
+ <public type="color" name="system_tertiary_dark" id="0x01060093" />
+ <public type="color" name="system_on_tertiary_dark" id="0x01060094" />
+ <public type="color" name="system_background_dark" id="0x01060095" />
+ <public type="color" name="system_on_background_dark" id="0x01060096" />
+ <public type="color" name="system_surface_dark" id="0x01060097" />
+ <public type="color" name="system_on_surface_dark" id="0x01060098" />
+ <public type="color" name="system_surface_container_low_dark" id="0x01060099" />
+ <public type="color" name="system_surface_container_lowest_dark" id="0x0106009a" />
+ <public type="color" name="system_surface_container_dark" id="0x0106009b" />
+ <public type="color" name="system_surface_container_high_dark" id="0x0106009c" />
+ <public type="color" name="system_surface_container_highest_dark" id="0x0106009d" />
+ <public type="color" name="system_surface_bright_dark" id="0x0106009e" />
+ <public type="color" name="system_surface_dim_dark" id="0x0106009f" />
+ <public type="color" name="system_surface_variant_dark" id="0x010600a0" />
+ <public type="color" name="system_on_surface_variant_dark" id="0x010600a1" />
+ <public type="color" name="system_outline_dark" id="0x010600a2" />
+ <public type="color" name="system_error_dark" id="0x010600a3" />
+ <public type="color" name="system_on_error_dark" id="0x010600a4" />
+ <public type="color" name="system_error_container_dark" id="0x010600a5" />
+ <public type="color" name="system_on_error_container_dark" id="0x010600a6" />
+ <public type="color" name="system_control_activated_dark" id="0x010600a7" />
+ <public type="color" name="system_control_normal_dark" id="0x010600a8" />
+ <public type="color" name="system_control_highlight_dark" id="0x010600a9" />
+ <public type="color" name="system_text_primary_inverse_dark" id="0x010600aa" />
+ <public type="color" name="system_text_secondary_and_tertiary_inverse_dark" id="0x010600ab" />
+ <public type="color" name="system_text_primary_inverse_disable_only_dark" id="0x010600ac" />
+ <public type="color" name="system_text_secondary_and_tertiary_inverse_disabled_dark" id="0x010600ad" />
+ <public type="color" name="system_text_hint_inverse_dark" id="0x010600ae" />
+ <public type="color" name="system_palette_key_color_primary_dark" id="0x010600af" />
+ <public type="color" name="system_palette_key_color_secondary_dark" id="0x010600b0" />
+ <public type="color" name="system_palette_key_color_tertiary_dark" id="0x010600b1" />
+ <public type="color" name="system_palette_key_color_neutral_dark" id="0x010600b2" />
+ <public type="color" name="system_palette_key_color_neutral_variant_dark" id="0x010600b3" />
+ <public type="color" name="system_primary_fixed" id="0x010600b4" />
+ <public type="color" name="system_primary_fixed_dim" id="0x010600b5" />
+ <public type="color" name="system_on_primary_fixed" id="0x010600b6" />
+ <public type="color" name="system_on_primary_fixed_variant" id="0x010600b7" />
+ <public type="color" name="system_secondary_fixed" id="0x010600b8" />
+ <public type="color" name="system_secondary_fixed_dim" id="0x010600b9" />
+ <public type="color" name="system_on_secondary_fixed" id="0x010600ba" />
+ <public type="color" name="system_on_secondary_fixed_variant" id="0x010600bb" />
+ <public type="color" name="system_tertiary_fixed" id="0x010600bc" />
+ <public type="color" name="system_tertiary_fixed_dim" id="0x010600bd" />
+ <public type="color" name="system_on_tertiary_fixed" id="0x010600be" />
+ <public type="color" name="system_on_tertiary_fixed_variant" id="0x010600bf" />
+ <public type="color" name="system_outline_variant_light" id="0x010600c0" />
+ <public type="color" name="system_outline_variant_dark" id="0x010600c1" />
+
+ <staging-public-group-final type="bool" first-id="0x01be0000">
+ <!-- @hide @SystemApi -->
+ <public name="config_safetyProtectionEnabled" />
+ <!-- @hide @SystemApi -->
+ <public name="config_enableDefaultNotes" />
+ <!-- @hide @SystemApi -->
+ <public name="config_enableDefaultNotesForWorkProfile" />
+ </staging-public-group-final>
+
+ <!-- @hide @SystemApi -->
+ <public type="bool" name="config_safetyProtectionEnabled" id="0x01110009" />
+ <!-- @hide @SystemApi -->
+ <public type="bool" name="config_enableDefaultNotes" id="0x0111000a" />
+ <!-- @hide @SystemApi -->
+ <public type="bool" name="config_enableDefaultNotesForWorkProfile" id="0x0111000b" />
+
</resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 9cbf3b6..49a1940 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -102,231 +102,65 @@
<resources>
<!-- ===============================================================
- Resources added in version U of the platform
+ Resources added in version NEXT of the platform
NOTE: After this version of the platform is forked, changes cannot be made to the root
branch's groups for that release. Only merge changes to the forked platform branch.
=============================================================== -->
<eat-comment/>
- <staging-public-group type="attr" first-id="0x01ce0000">
- <public name="handwritingBoundsOffsetLeft" />
- <public name="handwritingBoundsOffsetTop" />
- <public name="handwritingBoundsOffsetRight" />
- <public name="handwritingBoundsOffsetBottom" />
- <public name="accessibilityDataSensitive" />
- <public name="enableTextStylingShortcuts" />
- <public name="requiredDisplayCategory"/>
- <public name="removed_maxConcurrentSessionsCount" />
- <public name="visualQueryDetectionService" />
- <public name="physicalKeyboardHintLanguageTag" />
- <public name="physicalKeyboardHintLayoutType" />
- <public name="allowSharedIsolatedProcess" />
- <public name="keyboardLocale" />
- <public name="keyboardLayoutType" />
- <public name="allowUpdateOwnership" />
- <public name="isCredential"/>
- <public name="searchResultHighlightColor" />
- <public name="focusedSearchResultHighlightColor" />
- <public name="stylusHandwritingSettingsActivity" />
- <public name="windowNoMoveAnimation" />
- <public name="settingsSubtitle" />
- <public name="capability" />
+ <staging-public-group type="attr" first-id="0x01bd0000">
</staging-public-group>
- <staging-public-group type="id" first-id="0x01cd0000">
- <public name="bold" />
- <public name="italic" />
- <public name="underline" />
- <public name="accessibilityActionScrollInDirection" />
+ <staging-public-group type="id" first-id="0x01bc0000">
</staging-public-group>
- <staging-public-group type="style" first-id="0x01cc0000">
+ <staging-public-group type="style" first-id="0x01bb0000">
</staging-public-group>
- <staging-public-group type="string" first-id="0x01cb0000">
- <!-- @hide @SystemApi -->
- <public name="config_systemWearHealthService" />
- <!-- @hide @SystemApi -->
- <public name="config_defaultNotes" />
- <!-- @hide @SystemApi -->
- <public name="config_systemFinancedDeviceController" />
- <!-- @hide @SystemApi -->
- <public name="config_systemCallStreaming" />
+ <staging-public-group type="string" first-id="0x01ba0000">
</staging-public-group>
- <staging-public-group type="dimen" first-id="0x01ca0000">
- <!-- @hide @SystemApi -->
- <public name="config_viewConfigurationHandwritingGestureLineMargin" />
+ <staging-public-group type="dimen" first-id="0x01b90000">
</staging-public-group>
- <staging-public-group type="color" first-id="0x01c90000">
- <public name="system_primary_container_light" />
- <public name="system_on_primary_container_light" />
- <public name="system_primary_light" />
- <public name="system_on_primary_light" />
- <public name="system_secondary_container_light" />
- <public name="system_on_secondary_container_light" />
- <public name="system_secondary_light" />
- <public name="system_on_secondary_light" />
- <public name="system_tertiary_container_light" />
- <public name="system_on_tertiary_container_light" />
- <public name="system_tertiary_light" />
- <public name="system_on_tertiary_light" />
- <public name="system_background_light" />
- <public name="system_on_background_light" />
- <public name="system_surface_light" />
- <public name="system_on_surface_light" />
- <public name="system_surface_container_low_light" />
- <public name="system_surface_container_lowest_light" />
- <public name="system_surface_container_light" />
- <public name="system_surface_container_high_light" />
- <public name="system_surface_container_highest_light" />
- <public name="system_surface_bright_light" />
- <public name="system_surface_dim_light" />
- <public name="system_surface_variant_light" />
- <public name="system_on_surface_variant_light" />
- <public name="system_outline_light" />
- <public name="system_error_light" />
- <public name="system_on_error_light" />
- <public name="system_error_container_light" />
- <public name="system_on_error_container_light" />
- <public name="removed_system_primary_fixed_light" />
- <public name="removed_system_primary_fixed_dim_light" />
- <public name="removed_system_on_primary_fixed_light" />
- <public name="removed_system_on_primary_fixed_variant_light" />
- <public name="removed_system_secondary_fixed_light" />
- <public name="removed_system_secondary_fixed_dim_light" />
- <public name="removed_system_on_secondary_fixed_light" />
- <public name="removed_system_on_secondary_fixed_variant_light" />
- <public name="removed_system_tertiary_fixed_light" />
- <public name="removed_system_tertiary_fixed_dim_light" />
- <public name="removed_system_on_tertiary_fixed_light" />
- <public name="removed_system_on_tertiary_fixed_variant_light" />
- <public name="system_control_activated_light" />
- <public name="system_control_normal_light" />
- <public name="system_control_highlight_light" />
- <public name="system_text_primary_inverse_light" />
- <public name="system_text_secondary_and_tertiary_inverse_light" />
- <public name="system_text_primary_inverse_disable_only_light" />
- <public name="system_text_secondary_and_tertiary_inverse_disabled_light" />
- <public name="system_text_hint_inverse_light" />
- <public name="system_palette_key_color_primary_light" />
- <public name="system_palette_key_color_secondary_light" />
- <public name="system_palette_key_color_tertiary_light" />
- <public name="system_palette_key_color_neutral_light" />
- <public name="system_palette_key_color_neutral_variant_light" />
- <public name="system_primary_container_dark"/>
- <public name="system_on_primary_container_dark"/>
- <public name="system_primary_dark"/>
- <public name="system_on_primary_dark"/>
- <public name="system_secondary_container_dark"/>
- <public name="system_on_secondary_container_dark"/>
- <public name="system_secondary_dark"/>
- <public name="system_on_secondary_dark"/>
- <public name="system_tertiary_container_dark"/>
- <public name="system_on_tertiary_container_dark"/>
- <public name="system_tertiary_dark"/>
- <public name="system_on_tertiary_dark"/>
- <public name="system_background_dark"/>
- <public name="system_on_background_dark"/>
- <public name="system_surface_dark"/>
- <public name="system_on_surface_dark"/>
- <public name="system_surface_container_low_dark"/>
- <public name="system_surface_container_lowest_dark"/>
- <public name="system_surface_container_dark"/>
- <public name="system_surface_container_high_dark"/>
- <public name="system_surface_container_highest_dark"/>
- <public name="system_surface_bright_dark"/>
- <public name="system_surface_dim_dark"/>
- <public name="system_surface_variant_dark"/>
- <public name="system_on_surface_variant_dark"/>
- <public name="system_outline_dark"/>
- <public name="system_error_dark"/>
- <public name="system_on_error_dark"/>
- <public name="system_error_container_dark"/>
- <public name="system_on_error_container_dark"/>
- <public name="removed_system_primary_fixed_dark"/>
- <public name="removed_system_primary_fixed_dim_dark"/>
- <public name="removed_system_on_primary_fixed_dark"/>
- <public name="removed_system_on_primary_fixed_variant_dark"/>
- <public name="removed_system_secondary_fixed_dark"/>
- <public name="removed_system_secondary_fixed_dim_dark"/>
- <public name="removed_system_on_secondary_fixed_dark"/>
- <public name="removed_system_on_secondary_fixed_variant_dark"/>
- <public name="removed_system_tertiary_fixed_dark"/>
- <public name="removed_system_tertiary_fixed_dim_dark"/>
- <public name="removed_system_on_tertiary_fixed_dark"/>
- <public name="removed_system_on_tertiary_fixed_variant_dark"/>
- <public name="system_control_activated_dark"/>
- <public name="system_control_normal_dark"/>
- <public name="system_control_highlight_dark"/>
- <public name="system_text_primary_inverse_dark"/>
- <public name="system_text_secondary_and_tertiary_inverse_dark"/>
- <public name="system_text_primary_inverse_disable_only_dark"/>
- <public name="system_text_secondary_and_tertiary_inverse_disabled_dark"/>
- <public name="system_text_hint_inverse_dark"/>
- <public name="system_palette_key_color_primary_dark"/>
- <public name="system_palette_key_color_secondary_dark"/>
- <public name="system_palette_key_color_tertiary_dark"/>
- <public name="system_palette_key_color_neutral_dark"/>
- <public name="system_palette_key_color_neutral_variant_dark"/>
- <public name="system_primary_fixed" />
- <public name="system_primary_fixed_dim" />
- <public name="system_on_primary_fixed" />
- <public name="system_on_primary_fixed_variant" />
- <public name="system_secondary_fixed" />
- <public name="system_secondary_fixed_dim" />
- <public name="system_on_secondary_fixed" />
- <public name="system_on_secondary_fixed_variant" />
- <public name="system_tertiary_fixed" />
- <public name="system_tertiary_fixed_dim" />
- <public name="system_on_tertiary_fixed" />
- <public name="system_on_tertiary_fixed_variant" />
- <public name="system_outline_variant_light" />
- <public name="system_outline_variant_dark" />
+ <staging-public-group type="color" first-id="0x01b80000">
</staging-public-group>
- <staging-public-group type="array" first-id="0x01c80000">
+ <staging-public-group type="array" first-id="0x01b70000">
</staging-public-group>
- <staging-public-group type="drawable" first-id="0x01c70000">
+ <staging-public-group type="drawable" first-id="0x01b60000">
</staging-public-group>
- <staging-public-group type="layout" first-id="0x01c60000">
+ <staging-public-group type="layout" first-id="0x01b50000">
</staging-public-group>
- <staging-public-group type="anim" first-id="0x01c50000">
+ <staging-public-group type="anim" first-id="0x01b40000">
</staging-public-group>
- <staging-public-group type="animator" first-id="0x01c40000">
+ <staging-public-group type="animator" first-id="0x01b30000">
</staging-public-group>
- <staging-public-group type="interpolator" first-id="0x01c30000">
+ <staging-public-group type="interpolator" first-id="0x01b20000">
</staging-public-group>
- <staging-public-group type="mipmap" first-id="0x01c20000">
+ <staging-public-group type="mipmap" first-id="0x01b10000">
</staging-public-group>
- <staging-public-group type="integer" first-id="0x01c10000">
+ <staging-public-group type="integer" first-id="0x01b00000">
</staging-public-group>
- <staging-public-group type="transition" first-id="0x01c00000">
+ <staging-public-group type="transition" first-id="0x01af0000">
</staging-public-group>
- <staging-public-group type="raw" first-id="0x01bf0000">
+ <staging-public-group type="raw" first-id="0x01ae0000">
</staging-public-group>
- <staging-public-group type="bool" first-id="0x01be0000">
- <!-- @hide @SystemApi -->
- <public name="config_safetyProtectionEnabled" />
- <!-- @hide @SystemApi -->
- <public name="config_enableDefaultNotes" />
- <!-- @hide @SystemApi -->
- <public name="config_enableDefaultNotesForWorkProfile" />
+ <staging-public-group type="bool" first-id="0x01ad0000">
</staging-public-group>
- <staging-public-group type="fraction" first-id="0x01bd0000">
+ <staging-public-group type="fraction" first-id="0x01ac0000">
</staging-public-group>
</resources>
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
index 980211f..c6bb07b 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
@@ -25,6 +25,7 @@
import android.app.Activity;
import android.compat.testing.PlatformCompatChangeRule;
import android.os.Bundle;
+import android.platform.test.annotations.IwTest;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.util.PollingCheck;
@@ -84,6 +85,7 @@
}
}
+ @IwTest(focusArea = "accessibility")
@Test
public void testFontsScaleNonLinearly() {
final ActivityScenario<TestActivity> scenario = rule.getScenario();
@@ -114,6 +116,7 @@
)));
}
+ @IwTest(focusArea = "accessibility")
@Test
public void testOnConfigurationChanged_doesNotCrash() {
final ActivityScenario<TestActivity> scenario = rule.getScenario();
@@ -127,6 +130,7 @@
});
}
+ @IwTest(focusArea = "accessibility")
@Test
public void testUpdateConfiguration_doesNotCrash() {
final ActivityScenario<TestActivity> scenario = rule.getScenario();
diff --git a/core/tests/coretests/src/android/content/res/TEST_MAPPING b/core/tests/coretests/src/android/content/res/TEST_MAPPING
index 4ea6e40..ab14950 100644
--- a/core/tests/coretests/src/android/content/res/TEST_MAPPING
+++ b/core/tests/coretests/src/android/content/res/TEST_MAPPING
@@ -39,5 +39,18 @@
}
]
}
+ ],
+ "ironwood-postsubmit": [
+ {
+ "name": "FrameworksCoreTests",
+ "options":[
+ {
+ "include-annotation": "android.platform.test.annotations.IwTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
]
}
diff --git a/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java
new file mode 100644
index 0000000..66f3bca
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.biometrics;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.junit.MockitoRule;
+
+import java.util.concurrent.Executor;
+
+
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class BiometricPromptTest {
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private IAuthService mService;
+ private BiometricPrompt mBiometricPrompt;
+
+ private CancellationSignal mCancellationSignal;
+
+ private final TestLooper mLooper = new TestLooper();
+ private final Handler mHandler = new Handler(mLooper.getLooper());
+ private final Executor mExecutor = mHandler::post;
+
+ @Before
+ public void setUp() throws RemoteException {
+ mBiometricPrompt = new BiometricPrompt.Builder(mContext)
+ .setUseDefaultSubtitle()
+ .setUseDefaultTitle()
+ .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG
+ | BiometricManager.Authenticators.DEVICE_CREDENTIAL)
+ .setService(mService)
+ .build();
+
+ mCancellationSignal = new CancellationSignal();
+ when(mService.authenticate(any(), anyLong(), anyInt(), any(), anyString(), any()))
+ .thenReturn(0L);
+ when(mContext.getPackageName()).thenReturn("BiometricPromptTest");
+ }
+
+ @Test
+ public void testCancellationAfterAuthenticationFailed() throws RemoteException {
+ ArgumentCaptor<IBiometricServiceReceiver> biometricServiceReceiverCaptor =
+ ArgumentCaptor.forClass(IBiometricServiceReceiver.class);
+ BiometricPrompt.AuthenticationCallback callback =
+ new BiometricPrompt.AuthenticationCallback() {
+ @Override
+ public void onAuthenticationError(int errorCode, CharSequence errString) {
+ super.onAuthenticationError(errorCode, errString);
+ }};
+ mBiometricPrompt.authenticate(mCancellationSignal, mExecutor, callback);
+ mLooper.dispatchAll();
+
+ verify(mService).authenticate(any(), anyLong(), anyInt(),
+ biometricServiceReceiverCaptor.capture(), anyString(), any());
+
+ biometricServiceReceiverCaptor.getValue().onAuthenticationFailed();
+ mLooper.dispatchAll();
+ mCancellationSignal.cancel();
+
+ verify(mService).cancelAuthentication(any(), anyString(), anyLong());
+ }
+}
diff --git a/core/tests/coretests/src/android/hardware/biometrics/OWNERS b/core/tests/coretests/src/android/hardware/biometrics/OWNERS
new file mode 100644
index 0000000..6a2192a
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/biometrics/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/biometrics/OWNERS
diff --git a/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
index 4716312..36c2a62 100644
--- a/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
@@ -23,16 +23,25 @@
import static com.android.internal.util.DumpUtils.isPlatformNonCriticalPackage;
import static com.android.internal.util.DumpUtils.isPlatformPackage;
+import static com.google.common.truth.Truth.assertWithMessage;
+
import android.content.ComponentName;
+import android.util.SparseArray;
import junit.framework.TestCase;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
/**
* Run with:
atest FrameworksCoreTests:DumpUtilsTest
*/
public class DumpUtilsTest extends TestCase {
+ private final StringWriter mStringWriter = new StringWriter();
+ private final PrintWriter mPrintWriter = new PrintWriter(mStringWriter);
+
private static ComponentName cn(String componentName) {
if (componentName == null) {
return null;
@@ -168,4 +177,144 @@
Integer.toHexString(System.identityHashCode(component))).test(
wcn("com.google/.abc")));
}
+
+ public void testDumpSparseArray_empty() {
+ SparseArray<String> array = new SparseArray<>();
+
+ DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ "...", array, "whatever");
+
+ String output = flushPrintWriter();
+
+ assertWithMessage("empty array dump").that(output).isEqualTo("...No whatevers\n");
+ }
+
+ public void testDumpSparseArray_oneElement() {
+ SparseArray<String> array = new SparseArray<>();
+ array.put(1, "uno");
+
+ DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number");
+
+ String output = flushPrintWriter();
+
+ assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+ + ".1 number(s):\n"
+ + "..0: 1->uno\n");
+ }
+
+ public void testDumpSparseArray_oneNullElement() {
+ SparseArray<String> array = new SparseArray<>();
+ array.put(1, null);
+
+ DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "NULL");
+
+ String output = flushPrintWriter();
+
+ assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+ + ".1 NULL(s):\n"
+ + "..0: 1->(null)\n");
+ }
+
+ public void testDumpSparseArray_multipleElements() {
+ SparseArray<String> array = new SparseArray<>();
+ array.put(1, "uno");
+ array.put(2, "duo");
+ array.put(42, null);
+
+ DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number");
+
+ String output = flushPrintWriter();
+
+ assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+ + ".3 number(s):\n"
+ + "..0: 1->uno\n"
+ + "..1: 2->duo\n"
+ + "..2: 42->(null)\n");
+ }
+
+ public void testDumpSparseArray_keyDumperOnly() {
+ SparseArray<String> array = new SparseArray<>();
+ array.put(1, "uno");
+ array.put(2, "duo");
+ array.put(42, null);
+
+ DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number",
+ (i, k) -> {
+ mPrintWriter.printf("_%d=%d_", i, k);
+ }, /* valueDumper= */ null);
+
+ String output = flushPrintWriter();
+
+ assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+ + ".3 number(s):\n"
+ + "_0=1_uno\n"
+ + "_1=2_duo\n"
+ + "_2=42_(null)\n");
+ }
+
+ public void testDumpSparseArray_valueDumperOnly() {
+ SparseArray<String> array = new SparseArray<>();
+ array.put(1, "uno");
+ array.put(2, "duo");
+ array.put(42, null);
+
+ DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number",
+ /* keyDumper= */ null,
+ s -> {
+ mPrintWriter.print(s.toUpperCase());
+ });
+
+ String output = flushPrintWriter();
+
+ assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+ + ".3 number(s):\n"
+ + "..0: 1->UNO\n"
+ + "..1: 2->DUO\n"
+ + "..2: 42->(null)\n");
+ }
+
+ public void testDumpSparseArray_keyAndValueDumpers() {
+ SparseArray<String> array = new SparseArray<>();
+ array.put(1, "uno");
+ array.put(2, "duo");
+ array.put(42, null);
+
+ DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number",
+ (i, k) -> {
+ mPrintWriter.printf("_%d=%d_", i, k);
+ },
+ s -> {
+ mPrintWriter.print(s.toUpperCase());
+ });
+
+ String output = flushPrintWriter();
+
+ assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+ + ".3 number(s):\n"
+ + "_0=1_UNO\n"
+ + "_1=2_DUO\n"
+ + "_2=42_(null)\n");
+ }
+
+ public void testDumpSparseArrayValues() {
+ SparseArray<String> array = new SparseArray<>();
+ array.put(1, "uno");
+ array.put(2, "duo");
+ array.put(42, null);
+
+ DumpUtils.dumpSparseArrayValues(mPrintWriter, /* prefix= */ ".", array, "number");
+
+ String output = flushPrintWriter();
+
+ assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+ + ".3 numbers:\n"
+ + "..uno\n"
+ + "..duo\n"
+ + "..(null)\n");
+ }
+
+ private String flushPrintWriter() {
+ mPrintWriter.flush();
+
+ return mStringWriter.toString();
+ }
}
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 25b074d..2307d60 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -401,8 +401,9 @@
/**
* This is called by methods that want to throw an exception if the bitmap
* has already been recycled.
+ * @hide
*/
- private void checkRecycled(String errorMessage) {
+ void checkRecycled(String errorMessage) {
if (mRecycled) {
throw new IllegalStateException(errorMessage);
}
diff --git a/graphics/java/android/graphics/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java
index 2f6dd46..5c06577 100644
--- a/graphics/java/android/graphics/BitmapShader.java
+++ b/graphics/java/android/graphics/BitmapShader.java
@@ -120,6 +120,7 @@
if (bitmap == null) {
throw new IllegalArgumentException("Bitmap must be non-null");
}
+ bitmap.checkRecycled("Cannot create BitmapShader for recycled bitmap");
mBitmap = bitmap;
mTileX = tileX;
mTileY = tileY;
@@ -188,6 +189,8 @@
/** @hide */
@Override
protected long createNativeInstance(long nativeMatrix, boolean filterFromPaint) {
+ mBitmap.checkRecycled("BitmapShader's bitmap has been recycled");
+
boolean enableLinearFilter = mFilterMode == FILTER_MODE_LINEAR;
if (mFilterMode == FILTER_MODE_DEFAULT) {
mFilterFromPaint = filterFromPaint;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index f70df83..8c98c77 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -30,14 +30,17 @@
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.Rect;
+import android.os.SystemClock;
import android.view.Surface;
import android.view.SurfaceControl;
import android.window.TaskSnapshot;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.transition.Transitions;
import java.lang.annotation.Retention;
@@ -61,6 +64,14 @@
@Retention(RetentionPolicy.SOURCE)
public @interface AnimationType {}
+ /**
+ * The alpha type is set for swiping to home. But the swiped task may not enter PiP. And if
+ * another task enters PiP by non-swipe ways, e.g. call API in foreground or switch to 3-button
+ * navigation, then the alpha type is unexpected. So use a timeout to avoid applying wrong
+ * animation style to an unrelated task.
+ */
+ private static final int ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS = 800;
+
public static final int TRANSITION_DIRECTION_NONE = 0;
public static final int TRANSITION_DIRECTION_SAME = 1;
public static final int TRANSITION_DIRECTION_TO_PIP = 2;
@@ -109,6 +120,9 @@
});
private PipTransitionAnimator mCurrentAnimator;
+ @AnimationType
+ private int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
+ private long mLastOneShotAlphaAnimationTime;
public PipAnimationController(PipSurfaceTransactionHelper helper) {
mSurfaceTransactionHelper = helper;
@@ -222,6 +236,37 @@
}
/**
+ * Sets the preferred enter animation type for one time. This is typically used to set the
+ * animation type to {@link PipAnimationController#ANIM_TYPE_ALPHA}.
+ * <p>
+ * For example, gesture navigation would first fade out the PiP activity, and the transition
+ * should be responsible to animate in (such as fade in) the PiP.
+ */
+ public void setOneShotEnterAnimationType(@AnimationType int animationType) {
+ mOneShotAnimationType = animationType;
+ if (animationType == ANIM_TYPE_ALPHA) {
+ mLastOneShotAlphaAnimationTime = SystemClock.uptimeMillis();
+ }
+ }
+
+ /** Returns the preferred animation type and consumes the one-shot type if needed. */
+ @AnimationType
+ public int takeOneShotEnterAnimationType() {
+ final int type = mOneShotAnimationType;
+ if (type == ANIM_TYPE_ALPHA) {
+ // Restore to default type.
+ mOneShotAnimationType = ANIM_TYPE_BOUNDS;
+ if (SystemClock.uptimeMillis() - mLastOneShotAlphaAnimationTime
+ > ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "Alpha animation is expired. Use bounds animation.");
+ return ANIM_TYPE_BOUNDS;
+ }
+ }
+ return type;
+ }
+
+ /**
* Additional callback interface for PiP animation
*/
public static class PipAnimationCallback {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index a0bd064..5670fe6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -62,7 +62,6 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.Log;
import android.view.Choreographer;
@@ -111,12 +110,6 @@
DisplayController.OnDisplaysChangedListener, ShellTaskOrganizer.FocusListener {
private static final String TAG = PipTaskOrganizer.class.getSimpleName();
private static final boolean DEBUG = false;
- /**
- * The alpha type is set for swiping to home. But the swiped task may not enter PiP. And if
- * another task enters PiP by non-swipe ways, e.g. call API in foreground or switch to 3-button
- * navigation, then the alpha type is unexpected.
- */
- private static final int ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS = 1000;
/**
* The fixed start delay in ms when fading out the content overlay from bounds animation.
@@ -301,8 +294,6 @@
private WindowContainerToken mToken;
private SurfaceControl mLeash;
protected PipTransitionState mPipTransitionState;
- private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
- private long mLastOneShotAlphaAnimationTime;
private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
protected PictureInPictureParams mPictureInPictureParams;
@@ -422,18 +413,6 @@
}
/**
- * Sets the preferred animation type for one time.
- * This is typically used to set the animation type to
- * {@link PipAnimationController#ANIM_TYPE_ALPHA}.
- */
- public void setOneShotAnimationType(@PipAnimationController.AnimationType int animationType) {
- mOneShotAnimationType = animationType;
- if (animationType == ANIM_TYPE_ALPHA) {
- mLastOneShotAlphaAnimationTime = SystemClock.uptimeMillis();
- }
- }
-
- /**
* Override if the PiP should always use a fade-in animation during PiP entry.
*
* @return true if the mOneShotAnimationType should always be
@@ -733,26 +712,17 @@
return;
}
- if (mOneShotAnimationType == ANIM_TYPE_ALPHA
- && SystemClock.uptimeMillis() - mLastOneShotAlphaAnimationTime
- > ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: Alpha animation is expired. Use bounds animation.", TAG);
- mOneShotAnimationType = ANIM_TYPE_BOUNDS;
- }
-
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
// For Shell transition, we will animate the window in PipTransition#startAnimation
// instead of #onTaskAppeared.
return;
}
- if (shouldAlwaysFadeIn()) {
- mOneShotAnimationType = ANIM_TYPE_ALPHA;
- }
-
+ final int animationType = shouldAlwaysFadeIn()
+ ? ANIM_TYPE_ALPHA
+ : mPipAnimationController.takeOneShotEnterAnimationType();
if (mWaitForFixedRotation) {
- onTaskAppearedWithFixedRotation();
+ onTaskAppearedWithFixedRotation(animationType);
return;
}
@@ -763,7 +733,7 @@
Objects.requireNonNull(destinationBounds, "Missing destination bounds");
final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
- if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
+ if (animationType == ANIM_TYPE_BOUNDS) {
if (!shouldAttachMenuEarly()) {
mPipMenuController.attach(mLeash);
}
@@ -773,16 +743,15 @@
sourceHintRect, TRANSITION_DIRECTION_TO_PIP, mEnterAnimationDuration,
null /* updateBoundsCallback */);
mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
- } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+ } else if (animationType == ANIM_TYPE_ALPHA) {
enterPipWithAlphaAnimation(destinationBounds, mEnterAnimationDuration);
- mOneShotAnimationType = ANIM_TYPE_BOUNDS;
} else {
- throw new RuntimeException("Unrecognized animation type: " + mOneShotAnimationType);
+ throw new RuntimeException("Unrecognized animation type: " + animationType);
}
}
- private void onTaskAppearedWithFixedRotation() {
- if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+ private void onTaskAppearedWithFixedRotation(int animationType) {
+ if (animationType == ANIM_TYPE_ALPHA) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: Defer entering PiP alpha animation, fixed rotation is ongoing", TAG);
// If deferred, hside the surface till fixed rotation is completed.
@@ -791,7 +760,6 @@
tx.setAlpha(mLeash, 0f);
tx.show(mLeash);
tx.apply();
- mOneShotAnimationType = ANIM_TYPE_BOUNDS;
return;
}
final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
@@ -1895,7 +1863,6 @@
+ " binder=" + (mToken != null ? mToken.asBinder() : null));
pw.println(innerPrefix + "mLeash=" + mLeash);
pw.println(innerPrefix + "mState=" + mPipTransitionState.getTransitionState());
- pw.println(innerPrefix + "mOneShotAnimationType=" + mOneShotAnimationType);
pw.println(innerPrefix + "mPictureInPictureParams=" + mPictureInPictureParams);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 4a76a50..b743140 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -87,7 +87,7 @@
private final int mEnterExitAnimationDuration;
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
private final Optional<SplitScreenController> mSplitScreenOptional;
- private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
+ private @PipAnimationController.AnimationType int mEnterAnimationType = ANIM_TYPE_BOUNDS;
private Transitions.TransitionFinishCallback mFinishCallback;
private SurfaceControl.Transaction mFinishTransaction;
private final Rect mExitDestinationBounds = new Rect();
@@ -133,20 +133,6 @@
}
@Override
- public void setIsFullAnimation(boolean isFullAnimation) {
- setOneShotAnimationType(isFullAnimation ? ANIM_TYPE_BOUNDS : ANIM_TYPE_ALPHA);
- }
-
- /**
- * Sets the preferred animation type for one time.
- * This is typically used to set the animation type to
- * {@link PipAnimationController#ANIM_TYPE_ALPHA}.
- */
- private void setOneShotAnimationType(@PipAnimationController.AnimationType int animationType) {
- mOneShotAnimationType = animationType;
- }
-
- @Override
public void startExitTransition(int type, WindowContainerTransaction out,
@Nullable Rect destinationBounds) {
if (destinationBounds != null) {
@@ -288,7 +274,10 @@
if (!requestHasPipEnter(request)) {
throw new IllegalStateException("Called PiP augmentRequest when request has no PiP");
}
- if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+ mEnterAnimationType = mPipOrganizer.shouldAlwaysFadeIn()
+ ? ANIM_TYPE_ALPHA
+ : mPipAnimationController.takeOneShotEnterAnimationType();
+ if (mEnterAnimationType == ANIM_TYPE_ALPHA) {
mRequestedEnterTransition = transition;
mRequestedEnterTask = request.getTriggerTask().token;
outWCT.setActivityWindowingMode(request.getTriggerTask().token,
@@ -308,7 +297,7 @@
@Override
public boolean handleRotateDisplay(int startRotation, int endRotation,
WindowContainerTransaction wct) {
- if (mRequestedEnterTransition != null && mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+ if (mRequestedEnterTransition != null && mEnterAnimationType == ANIM_TYPE_ALPHA) {
// A fade-in was requested but not-yet started. In this case, just recalculate the
// initial state under the new rotation.
int rotationDelta = deltaRotation(startRotation, endRotation);
@@ -760,7 +749,6 @@
if (taskInfo.pictureInPictureParams != null
&& taskInfo.pictureInPictureParams.isAutoEnterEnabled()
&& mPipTransitionState.getInSwipePipToHomeTransition()) {
- mOneShotAnimationType = ANIM_TYPE_BOUNDS;
final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay;
startTransaction.setMatrix(leash, Matrix.IDENTITY_MATRIX, new float[9])
.setPosition(leash, destinationBounds.left, destinationBounds.top)
@@ -796,17 +784,16 @@
startTransaction.setMatrix(leash, tmpTransform, new float[9]);
}
- if (mPipOrganizer.shouldAlwaysFadeIn()) {
- mOneShotAnimationType = ANIM_TYPE_ALPHA;
- }
-
- if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+ final int enterAnimationType = mEnterAnimationType;
+ if (enterAnimationType == ANIM_TYPE_ALPHA) {
+ // Restore to default type.
+ mEnterAnimationType = ANIM_TYPE_BOUNDS;
startTransaction.setAlpha(leash, 0f);
}
startTransaction.apply();
PipAnimationController.PipTransitionAnimator animator;
- if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
+ if (enterAnimationType == ANIM_TYPE_BOUNDS) {
animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds,
currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
0 /* startingAngle */, rotationDelta);
@@ -829,13 +816,11 @@
animator.setColorContentOverlay(mContext);
}
}
- } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+ } else if (enterAnimationType == ANIM_TYPE_ALPHA) {
animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds,
0f, 1f);
- mOneShotAnimationType = ANIM_TYPE_BOUNDS;
} else {
- throw new RuntimeException("Unrecognized animation type: "
- + mOneShotAnimationType);
+ throw new RuntimeException("Unrecognized animation type: " + enterAnimationType);
}
animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
.setPipAnimationCallback(mPipAnimationCallback)
@@ -897,7 +882,7 @@
.setWindowCrop(leash, endBounds.width(), endBounds.height());
}
}
- mSplitScreenOptional.get().finishEnterSplitScreen(startTransaction);
+ mSplitScreenOptional.get().finishEnterSplitScreen(finishTransaction);
startTransaction.apply();
mPipOrganizer.onExitPipFinished(taskInfo);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index f51e247..7979ce7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -105,15 +105,6 @@
}
/**
- * Called to inform the transition that the animation should start with the assumption that
- * PiP is not animating from its original bounds, but rather a continuation of another
- * animation. For example, gesture navigation would first fade out the PiP activity, and the
- * transition should be responsible to animate in (such as fade in) the PiP.
- */
- public void setIsFullAnimation(boolean isFullAnimation) {
- }
-
- /**
* Called when the Shell wants to start an exit Pip transition/animation.
*/
public void startExitTransition(int type, WindowContainerTransaction out,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 463ad77..b0bb14b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -968,12 +968,6 @@
mPipBoundsState.setShelfVisibility(visible, shelfHeight);
}
- private void setPinnedStackAnimationType(int animationType) {
- mPipTaskOrganizer.setOneShotAnimationType(animationType);
- mPipTransitionController.setIsFullAnimation(
- animationType == PipAnimationController.ANIM_TYPE_BOUNDS);
- }
-
@VisibleForTesting
void setPinnedStackAnimationListener(PipAnimationListener callback) {
mPinnedStackAnimationRecentsCallback = callback;
@@ -1337,7 +1331,8 @@
@Override
public void setPipAnimationTypeToAlpha() {
executeRemoteCallWithTaskPermission(mController, "setPipAnimationTypeToAlpha",
- (controller) -> controller.setPinnedStackAnimationType(ANIM_TYPE_ALPHA));
+ (controller) -> controller.mPipAnimationController.setOneShotEnterAnimationType(
+ ANIM_TYPE_ALPHA));
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index 81e118a..f819bee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -129,9 +129,10 @@
/**
* Start a pair of intents in one transition.
*/
- oneway void startIntents(in PendingIntent pendingIntent1, in Bundle options1,
- in PendingIntent pendingIntent2, in Bundle options2, int splitPosition,
- float splitRatio, in RemoteTransition remoteTransition, in InstanceId instanceId) = 19;
+ oneway void startIntents(in PendingIntent pendingIntent1, in ShortcutInfo shortcutInfo1,
+ in Bundle options1, in PendingIntent pendingIntent2, in ShortcutInfo shortcutInfo2,
+ in Bundle options2, int splitPosition, float splitRatio,
+ in RemoteTransition remoteTransition, in InstanceId instanceId) = 19;
/**
* Blocking call that notifies and gets additional split-screen targets when entering
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 7d5ab84..2cd16be 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -626,6 +626,35 @@
splitPosition, splitRatio, adapter, instanceId);
}
+ private void startIntents(PendingIntent pendingIntent1,
+ @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
+ PendingIntent pendingIntent2, @Nullable ShortcutInfo shortcutInfo2,
+ @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
+ @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ Intent fillInIntent1 = null;
+ Intent fillInIntent2 = null;
+ final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1);
+ final String packageName2 = SplitScreenUtils.getPackageName(pendingIntent2);
+ if (samePackage(packageName1, packageName2)) {
+ if (supportMultiInstancesSplit(packageName1)) {
+ fillInIntent1 = new Intent();
+ fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ fillInIntent2 = new Intent();
+ fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+ } else {
+ pendingIntent2 = null;
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Cancel entering split as not supporting multi-instances");
+ Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+ mStageCoordinator.startIntents(pendingIntent1, fillInIntent1, shortcutInfo1, options1,
+ pendingIntent2, fillInIntent2, shortcutInfo2, options2, splitPosition, splitRatio,
+ remoteTransition, instanceId);
+ }
+
@Override
public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options) {
@@ -1066,11 +1095,17 @@
}
@Override
- public void startIntents(PendingIntent pendingIntent1, @Nullable Bundle options1,
- PendingIntent pendingIntent2, @Nullable Bundle options2,
+ public void startIntents(PendingIntent pendingIntent1, @Nullable ShortcutInfo shortcutInfo1,
+ @Nullable Bundle options1, PendingIntent pendingIntent2,
+ @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2,
@SplitPosition int splitPosition, float splitRatio,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
- // TODO(b/259368992): To be implemented.
+ executeRemoteCallWithTaskPermission(mController, "startIntents",
+ (controller) ->
+ controller.startIntents(pendingIntent1, shortcutInfo1,
+ options1, pendingIntent2, shortcutInfo2, options2,
+ splitPosition, splitRatio, remoteTransition, instanceId)
+ );
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 22800ad..8b890bb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -22,6 +22,7 @@
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString;
@@ -155,8 +156,10 @@
}
boolean isRootOrSplitSideRoot = change.getParent() == null
|| topRoot.equals(change.getParent());
- // For enter or exit, we only want to animate the side roots but not the top-root.
- if (!isRootOrSplitSideRoot || topRoot.equals(change.getContainer())) {
+ boolean isDivider = change.getFlags() == FLAG_IS_DIVIDER_BAR;
+ // For enter or exit, we only want to animate side roots and the divider but not the
+ // top-root.
+ if (!isRootOrSplitSideRoot || topRoot.equals(change.getContainer()) || isDivider) {
continue;
}
@@ -165,6 +168,10 @@
t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top);
t.setWindowCrop(leash, change.getEndAbsBounds().width(),
change.getEndAbsBounds().height());
+ } else if (isDivider) {
+ t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top);
+ t.setLayer(leash, Integer.MAX_VALUE);
+ t.show(leash);
}
boolean isOpening = isOpeningTransition(info);
if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 04cb17c..ce5a2af6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -682,6 +682,46 @@
setEnterInstanceId(instanceId);
}
+ void startIntents(PendingIntent pendingIntent1, Intent fillInIntent1,
+ @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
+ PendingIntent pendingIntent2, Intent fillInIntent2,
+ @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio,
+ @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (!mMainStage.isActive()) {
+ // Build a request WCT that will launch both apps such that task 0 is on the main stage
+ // while task 1 is on the side stage.
+ mMainStage.activate(wct, false /* reparent */);
+ }
+
+ prepareEvictChildTasksIfSplitActive(wct);
+ mSplitLayout.setDivideRatio(splitRatio);
+ updateWindowBounds(mSplitLayout, wct);
+ wct.reorder(mRootTaskInfo.token, true);
+ setRootForceTranslucent(false, wct);
+
+ setSideStagePosition(splitPosition, wct);
+ options1 = options1 != null ? options1 : new Bundle();
+ addActivityOptions(options1, mSideStage);
+ if (shortcutInfo1 != null) {
+ wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1);
+ } else {
+ wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
+ }
+ options2 = options2 != null ? options2 : new Bundle();
+ addActivityOptions(options2, mMainStage);
+ if (shortcutInfo2 != null) {
+ wct.startShortcut(mContext.getPackageName(), shortcutInfo2, options2);
+ } else {
+ wct.sendPendingIntent(pendingIntent2, fillInIntent2, options2);
+ }
+
+ mSplitTransitions.startEnterTransition(
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null, null);
+ setEnterInstanceId(instanceId);
+ }
+
/** Starts a pair of tasks using legacy transition. */
void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
@@ -895,10 +935,6 @@
mSyncQueue.queue(wrapAsSplitRemoteAnimation(adapter), WindowManager.TRANSIT_OPEN, wct);
}
- mSyncQueue.runInSync(t -> {
- setDividerVisibility(true, t);
- });
-
setEnterInstanceId(instanceId);
}
@@ -937,6 +973,7 @@
@Override
public void onAnimationCancelled(boolean isKeyguardOccluded) {
onRemoteAnimationFinishedOrCancelled(evictWct);
+ setDividerVisibility(true, null);
try {
adapter.getRunner().onAnimationCancelled(isKeyguardOccluded);
} catch (RemoteException e) {
@@ -977,6 +1014,7 @@
t.setPosition(apps[i].leash, 0, 0);
}
}
+ setDividerVisibility(true, t);
t.apply();
IRemoteAnimationFinishedCallback wrapCallback =
@@ -1467,6 +1505,10 @@
void finishEnterSplitScreen(SurfaceControl.Transaction t) {
mSplitLayout.init();
setDividerVisibility(true, t);
+ // Ensure divider surface are re-parented back into the hierarchy at the end of the
+ // transition. See Transition#buildFinishTransaction for more detail.
+ t.reparent(mSplitLayout.getDividerLeash(), mRootTaskLeash);
+
updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
t.show(mRootTaskLeash);
setSplitsVisible(true);
@@ -1776,6 +1818,8 @@
setDividerVisibility(mainStageVisible, null);
}
+ // Set divider visibility flag and try to apply it, the param transaction is used to apply.
+ // See applyDividerVisibility for more detail.
private void setDividerVisibility(boolean visible, @Nullable SurfaceControl.Transaction t) {
if (visible == mDividerVisible) {
return;
@@ -1802,14 +1846,13 @@
return;
}
- if (t != null) {
- applyDividerVisibility(t);
- } else {
- mSyncQueue.runInSync(transaction -> applyDividerVisibility(transaction));
- }
+ applyDividerVisibility(t);
}
- private void applyDividerVisibility(SurfaceControl.Transaction t) {
+ // Apply divider visibility by current visibility flag. If param transaction is non-null, it
+ // will apply by that transaction, if it is null and visible, it will run a fade-in animation,
+ // otherwise hide immediately.
+ private void applyDividerVisibility(@Nullable SurfaceControl.Transaction t) {
final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
if (dividerLeash == null) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
@@ -1826,7 +1869,12 @@
mDividerFadeInAnimator.cancel();
}
- if (mDividerVisible) {
+ mSplitLayout.getRefDividerBounds(mTempRect1);
+ if (t != null) {
+ t.setVisibility(dividerLeash, mDividerVisible);
+ t.setLayer(dividerLeash, Integer.MAX_VALUE);
+ t.setPosition(dividerLeash, mTempRect1.left, mTempRect1.top);
+ } else if (mDividerVisible) {
final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
mDividerFadeInAnimator = ValueAnimator.ofFloat(0f, 1f);
mDividerFadeInAnimator.addUpdateListener(animation -> {
@@ -1866,7 +1914,10 @@
mDividerFadeInAnimator.start();
} else {
- t.hide(dividerLeash);
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ transaction.hide(dividerLeash);
+ transaction.apply();
+ mTransactionPool.release(transaction);
}
}
@@ -2450,7 +2501,7 @@
}
finishEnterSplitScreen(finishT);
- addDividerBarToTransition(info, finishT, true /* show */);
+ addDividerBarToTransition(info, true /* show */);
return true;
}
@@ -2593,7 +2644,7 @@
if (dismissTransition.mDismissTop == STAGE_TYPE_UNDEFINED) {
// TODO: Have a proper remote for this. Until then, though, reset state and use the
// normal animation stuff (which falls back to the normal launcher remote).
- t.hide(mSplitLayout.getDividerLeash());
+ setDividerVisibility(false, t);
mSplitLayout.release(t);
mSplitTransitions.mPendingDismiss = null;
return false;
@@ -2611,7 +2662,7 @@
});
}
- addDividerBarToTransition(info, finishT, false /* show */);
+ addDividerBarToTransition(info, false /* show */);
return true;
}
@@ -2652,11 +2703,11 @@
logExit(EXIT_REASON_UNKNOWN);
}
- private void addDividerBarToTransition(@NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction finishT, boolean show) {
+ private void addDividerBarToTransition(@NonNull TransitionInfo info, boolean show) {
final SurfaceControl leash = mSplitLayout.getDividerLeash();
final TransitionInfo.Change barChange = new TransitionInfo.Change(null /* token */, leash);
mSplitLayout.getRefDividerBounds(mTempRect1);
+ barChange.setParent(mRootTaskInfo.token);
barChange.setStartAbsBounds(mTempRect1);
barChange.setEndAbsBounds(mTempRect1);
barChange.setMode(show ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK);
@@ -2664,15 +2715,6 @@
// Technically this should be order-0, but this is running after layer assignment
// and it's a special case, so just add to end.
info.addChange(barChange);
-
- if (show) {
- finishT.setLayer(leash, Integer.MAX_VALUE);
- finishT.setPosition(leash, mTempRect1.left, mTempRect1.top);
- finishT.show(leash);
- // Ensure divider surface are re-parented back into the hierarchy at the end of the
- // transition. See Transition#buildFinishTransaction for more detail.
- finishT.reparent(leash, mRootTaskLeash);
- }
}
RemoteAnimationTarget getDividerBarLegacyTarget() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 060dc4e..dfde7e6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -110,19 +110,11 @@
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
final WindowContainerTransaction wct = new WindowContainerTransaction();
- final int outsetLeftId = R.dimen.freeform_resize_handle;
- final int outsetTopId = R.dimen.freeform_resize_handle;
- final int outsetRightId = R.dimen.freeform_resize_handle;
- final int outsetBottomId = R.dimen.freeform_resize_handle;
-
mRelayoutParams.reset();
mRelayoutParams.mRunningTaskInfo = taskInfo;
mRelayoutParams.mLayoutResId = R.layout.caption_window_decor;
mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
mRelayoutParams.mShadowRadiusId = shadowRadiusID;
- if (isDragResizeable) {
- mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId);
- }
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index f9c0e60..a004e37 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -208,11 +208,6 @@
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
final WindowContainerTransaction wct = new WindowContainerTransaction();
- final int outsetLeftId = R.dimen.freeform_resize_handle;
- final int outsetTopId = R.dimen.freeform_resize_handle;
- final int outsetRightId = R.dimen.freeform_resize_handle;
- final int outsetBottomId = R.dimen.freeform_resize_handle;
-
final int windowDecorLayoutId = getDesktopModeWindowDecorLayoutId(
taskInfo.getWindowingMode());
mRelayoutParams.reset();
@@ -220,9 +215,6 @@
mRelayoutParams.mLayoutResId = windowDecorLayoutId;
mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
mRelayoutParams.mShadowRadiusId = shadowRadiusID;
- if (isDragResizeable) {
- mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId);
- }
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
@@ -424,13 +416,12 @@
if (mRelayoutParams.mLayoutResId
== R.layout.desktop_mode_app_controls_window_decor) {
// Align the handle menu to the left of the caption.
- menuX = mRelayoutParams.mCaptionX - mResult.mDecorContainerOffsetX + mMarginMenuStart;
- menuY = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY + mMarginMenuTop;
+ menuX = mRelayoutParams.mCaptionX + mMarginMenuStart;
+ menuY = mRelayoutParams.mCaptionY + mMarginMenuTop;
} else {
// Position the handle menu at the center of the caption.
- menuX = mRelayoutParams.mCaptionX + (captionWidth / 2) - (mMenuWidth / 2)
- - mResult.mDecorContainerOffsetX;
- menuY = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY + mMarginMenuStart;
+ menuX = mRelayoutParams.mCaptionX + (captionWidth / 2) - (mMenuWidth / 2);
+ menuY = mRelayoutParams.mCaptionY + mMarginMenuStart;
}
// App Info pill setup.
@@ -497,23 +488,18 @@
final boolean pointInAppInfoPill = pointInView(
mHandleMenuAppInfoPill.mWindowViewHost.getView(),
- inputPoint.x - mHandleMenuAppInfoPillPosition.x - mResult.mDecorContainerOffsetX,
- inputPoint.y - mHandleMenuAppInfoPillPosition.y
- - mResult.mDecorContainerOffsetY);
+ inputPoint.x - mHandleMenuAppInfoPillPosition.x,
+ inputPoint.y - mHandleMenuAppInfoPillPosition.y);
boolean pointInWindowingPill = false;
if (mHandleMenuWindowingPill != null) {
pointInWindowingPill = pointInView(mHandleMenuWindowingPill.mWindowViewHost.getView(),
- inputPoint.x - mHandleMenuWindowingPillPosition.x
- - mResult.mDecorContainerOffsetX,
- inputPoint.y - mHandleMenuWindowingPillPosition.y
- - mResult.mDecorContainerOffsetY);
+ inputPoint.x - mHandleMenuWindowingPillPosition.x,
+ inputPoint.y - mHandleMenuWindowingPillPosition.y);
}
final boolean pointInMoreActionsPill = pointInView(
mHandleMenuMoreActionsPill.mWindowViewHost.getView(),
- inputPoint.x - mHandleMenuMoreActionsPillPosition.x
- - mResult.mDecorContainerOffsetX,
- inputPoint.y - mHandleMenuMoreActionsPillPosition.y
- - mResult.mDecorContainerOffsetY);
+ inputPoint.x - mHandleMenuMoreActionsPillPosition.x,
+ inputPoint.y - mHandleMenuMoreActionsPillPosition.y);
if (!pointInAppInfoPill && !pointInWindowingPill
&& !pointInMoreActionsPill && !pointInOpenMenuButton) {
closeHandleMenu();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 8cb575c..d5437c7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -64,8 +64,8 @@
private final TaskResizeInputEventReceiver mInputEventReceiver;
private final DragPositioningCallback mCallback;
- private int mWidth;
- private int mHeight;
+ private int mTaskWidth;
+ private int mTaskHeight;
private int mResizeHandleThickness;
private int mCornerSize;
@@ -128,78 +128,84 @@
* This is also used to update the touch regions of this handler every event dispatched here is
* a potential resize request.
*
- * @param width The width of the drag resize handler in pixels, including resize handle
- * thickness. That is task width + 2 * resize handle thickness.
- * @param height The height of the drag resize handler in pixels, including resize handle
- * thickness. That is task height + 2 * resize handle thickness.
+ * @param taskWidth The width of the task.
+ * @param taskHeight The height of the task.
* @param resizeHandleThickness The thickness of the resize handle in pixels.
* @param cornerSize The size of the resize handle centered in each corner.
* @param touchSlop The distance in pixels user has to drag with touch for it to register as
* a resize action.
*/
- void setGeometry(int width, int height, int resizeHandleThickness, int cornerSize,
+ void setGeometry(int taskWidth, int taskHeight, int resizeHandleThickness, int cornerSize,
int touchSlop) {
- if (mWidth == width && mHeight == height
+ if (mTaskWidth == taskWidth && mTaskHeight == taskHeight
&& mResizeHandleThickness == resizeHandleThickness
&& mCornerSize == cornerSize) {
return;
}
- mWidth = width;
- mHeight = height;
+ mTaskWidth = taskWidth;
+ mTaskHeight = taskHeight;
mResizeHandleThickness = resizeHandleThickness;
mCornerSize = cornerSize;
mDragDetector.setTouchSlop(touchSlop);
Region touchRegion = new Region();
- final Rect topInputBounds = new Rect(0, 0, mWidth, mResizeHandleThickness);
+ final Rect topInputBounds = new Rect(
+ -mResizeHandleThickness,
+ -mResizeHandleThickness,
+ mTaskWidth + mResizeHandleThickness,
+ 0);
touchRegion.union(topInputBounds);
- final Rect leftInputBounds = new Rect(0, mResizeHandleThickness,
- mResizeHandleThickness, mHeight - mResizeHandleThickness);
+ final Rect leftInputBounds = new Rect(
+ -mResizeHandleThickness,
+ 0,
+ 0,
+ mTaskHeight);
touchRegion.union(leftInputBounds);
final Rect rightInputBounds = new Rect(
- mWidth - mResizeHandleThickness, mResizeHandleThickness,
- mWidth, mHeight - mResizeHandleThickness);
+ mTaskWidth,
+ 0,
+ mTaskWidth + mResizeHandleThickness,
+ mTaskHeight);
touchRegion.union(rightInputBounds);
- final Rect bottomInputBounds = new Rect(0, mHeight - mResizeHandleThickness,
- mWidth, mHeight);
+ final Rect bottomInputBounds = new Rect(
+ -mResizeHandleThickness,
+ mTaskHeight,
+ mTaskWidth + mResizeHandleThickness,
+ mTaskHeight + mResizeHandleThickness);
touchRegion.union(bottomInputBounds);
// Set up touch areas in each corner.
int cornerRadius = mCornerSize / 2;
mLeftTopCornerBounds = new Rect(
- mResizeHandleThickness - cornerRadius,
- mResizeHandleThickness - cornerRadius,
- mResizeHandleThickness + cornerRadius,
- mResizeHandleThickness + cornerRadius
- );
+ -cornerRadius,
+ -cornerRadius,
+ cornerRadius,
+ cornerRadius);
touchRegion.union(mLeftTopCornerBounds);
mRightTopCornerBounds = new Rect(
- mWidth - mResizeHandleThickness - cornerRadius,
- mResizeHandleThickness - cornerRadius,
- mWidth - mResizeHandleThickness + cornerRadius,
- mResizeHandleThickness + cornerRadius
- );
+ mTaskWidth - cornerRadius,
+ -cornerRadius,
+ mTaskWidth + cornerRadius,
+ cornerRadius);
touchRegion.union(mRightTopCornerBounds);
mLeftBottomCornerBounds = new Rect(
- mResizeHandleThickness - cornerRadius,
- mHeight - mResizeHandleThickness - cornerRadius,
- mResizeHandleThickness + cornerRadius,
- mHeight - mResizeHandleThickness + cornerRadius
- );
+ -cornerRadius,
+ mTaskHeight - cornerRadius,
+ cornerRadius,
+ mTaskHeight + cornerRadius);
touchRegion.union(mLeftBottomCornerBounds);
mRightBottomCornerBounds = new Rect(
- mWidth - mResizeHandleThickness - cornerRadius,
- mHeight - mResizeHandleThickness - cornerRadius,
- mWidth - mResizeHandleThickness + cornerRadius,
- mHeight - mResizeHandleThickness + cornerRadius
- );
+ mTaskWidth - cornerRadius,
+ mTaskHeight - cornerRadius,
+ mTaskWidth + cornerRadius,
+ mTaskHeight + cornerRadius);
touchRegion.union(mRightBottomCornerBounds);
try {
@@ -358,16 +364,16 @@
@TaskPositioner.CtrlType
private int calculateResizeHandlesCtrlType(float x, float y) {
int ctrlType = 0;
- if (x < mResizeHandleThickness) {
+ if (x < 0) {
ctrlType |= TaskPositioner.CTRL_TYPE_LEFT;
}
- if (x > mWidth - mResizeHandleThickness) {
+ if (x > mTaskWidth) {
ctrlType |= TaskPositioner.CTRL_TYPE_RIGHT;
}
- if (y < mResizeHandleThickness) {
+ if (y < 0) {
ctrlType |= TaskPositioner.CTRL_TYPE_TOP;
}
- if (y > mHeight - mResizeHandleThickness) {
+ if (y > mTaskHeight) {
ctrlType |= TaskPositioner.CTRL_TYPE_BOTTOM;
}
return ctrlType;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 4ebd09f..bc5fd4d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -98,7 +98,6 @@
private final Binder mOwner = new Binder();
private final Rect mCaptionInsetsRect = new Rect();
- private final Rect mTaskSurfaceCrop = new Rect();
private final float[] mTmpColor = new float[3];
WindowDecoration(
@@ -218,21 +217,14 @@
final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
final Resources resources = mDecorWindowContext.getResources();
- outResult.mDecorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId);
- outResult.mDecorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId);
- outResult.mWidth = taskBounds.width()
- + loadDimensionPixelSize(resources, params.mOutsetRightId)
- - outResult.mDecorContainerOffsetX;
- outResult.mHeight = taskBounds.height()
- + loadDimensionPixelSize(resources, params.mOutsetBottomId)
- - outResult.mDecorContainerOffsetY;
- startT.setPosition(
- mDecorationContainerSurface,
- outResult.mDecorContainerOffsetX, outResult.mDecorContainerOffsetY)
- .setWindowCrop(mDecorationContainerSurface,
- outResult.mWidth, outResult.mHeight)
+ outResult.mWidth = taskBounds.width();
+ outResult.mHeight = taskBounds.height();
+ startT.setWindowCrop(mDecorationContainerSurface, outResult.mWidth, outResult.mHeight)
.show(mDecorationContainerSurface);
+ // TODO(b/270202228): This surface can be removed. Instead, use
+ // |mDecorationContainerSurface| to set the background now that it no longer has outsets
+ // and its crop is set to the task bounds.
// TaskBackgroundSurface
if (mTaskBackgroundSurface == null) {
final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
@@ -250,8 +242,7 @@
mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
- startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(),
- taskBounds.height())
+ startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(), taskBounds.height())
.setShadowRadius(mTaskBackgroundSurface, shadowRadius)
.setColor(mTaskBackgroundSurface, mTmpColor)
.show(mTaskBackgroundSurface);
@@ -269,11 +260,7 @@
final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
final int captionWidth = taskBounds.width();
- startT.setPosition(
- mCaptionContainerSurface,
- -outResult.mDecorContainerOffsetX + params.mCaptionX,
- -outResult.mDecorContainerOffsetY + params.mCaptionY)
- .setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
+ startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
.show(mCaptionContainerSurface);
if (mCaptionWindowManager == null) {
@@ -314,14 +301,9 @@
// Task surface itself
Point taskPosition = mTaskInfo.positionInParent;
- mTaskSurfaceCrop.set(
- outResult.mDecorContainerOffsetX,
- outResult.mDecorContainerOffsetY,
- outResult.mWidth + outResult.mDecorContainerOffsetX,
- outResult.mHeight + outResult.mDecorContainerOffsetY);
startT.show(mTaskSurface);
finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
- .setCrop(mTaskSurface, mTaskSurfaceCrop);
+ .setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
}
/**
@@ -447,37 +429,15 @@
int mCaptionWidthId;
int mShadowRadiusId;
- int mOutsetTopId;
- int mOutsetBottomId;
- int mOutsetLeftId;
- int mOutsetRightId;
-
int mCaptionX;
int mCaptionY;
- void setOutsets(int leftId, int topId, int rightId, int bottomId) {
- mOutsetLeftId = leftId;
- mOutsetTopId = topId;
- mOutsetRightId = rightId;
- mOutsetBottomId = bottomId;
- }
-
- void setCaptionPosition(int left, int top) {
- mCaptionX = left;
- mCaptionY = top;
- }
-
void reset() {
mLayoutResId = Resources.ID_NULL;
mCaptionHeightId = Resources.ID_NULL;
mCaptionWidthId = Resources.ID_NULL;
mShadowRadiusId = Resources.ID_NULL;
- mOutsetTopId = Resources.ID_NULL;
- mOutsetBottomId = Resources.ID_NULL;
- mOutsetLeftId = Resources.ID_NULL;
- mOutsetRightId = Resources.ID_NULL;
-
mCaptionX = 0;
mCaptionY = 0;
}
@@ -487,14 +447,10 @@
int mWidth;
int mHeight;
T mRootView;
- int mDecorContainerOffsetX;
- int mDecorContainerOffsetY;
void reset() {
mWidth = 0;
mHeight = 0;
- mDecorContainerOffsetX = 0;
- mDecorContainerOffsetY = 0;
mRootView = null;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 15bb10e..842c699 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -262,7 +262,8 @@
DisplayLayout layout = new DisplayLayout(info,
mContext.getResources(), true, true);
mPipDisplayLayoutState.setDisplayLayout(layout);
- mPipTaskOrganizer.setOneShotAnimationType(PipAnimationController.ANIM_TYPE_ALPHA);
+ doReturn(PipAnimationController.ANIM_TYPE_ALPHA).when(mMockPipAnimationController)
+ .takeOneShotEnterAnimationType();
mPipTaskOrganizer.setSurfaceControlTransactionFactory(
MockSurfaceControlHelper::createMockSurfaceControlTransaction);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index eda6fdc..e6219d1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -113,6 +113,7 @@
private SurfaceSession mSurfaceSession = new SurfaceSession();
private SurfaceControl mRootLeash;
+ private SurfaceControl mDividerLeash;
private ActivityManager.RunningTaskInfo mRootTask;
private StageCoordinator mStageCoordinator;
private Transitions mTransitions;
@@ -129,12 +130,14 @@
mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool,
mMainExecutor, Optional.empty()));
+ mDividerLeash = new SurfaceControl.Builder(mSurfaceSession).setName("fakeDivider").build();
when(mSplitLayout.getBounds1()).thenReturn(mBounds1);
when(mSplitLayout.getBounds2()).thenReturn(mBounds2);
when(mSplitLayout.getRootBounds()).thenReturn(mRootBounds);
when(mSplitLayout.isLandscape()).thenReturn(false);
when(mSplitLayout.applyTaskChanges(any(), any(), any())).thenReturn(true);
+ when(mSplitLayout.getDividerLeash()).thenReturn(mDividerLeash);
mRootTask = new TestRunningTaskInfoBuilder().build();
mRootLeash = new SurfaceControl.Builder(mSurfaceSession).setName("test").build();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index dfa3c10..e8147ff 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -159,14 +159,8 @@
.setVisible(false)
.build();
taskInfo.isFocused = false;
- // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
- // 64px.
+ // Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- mRelayoutParams.setOutsets(
- R.dimen.test_window_decor_left_outset,
- R.dimen.test_window_decor_top_outset,
- R.dimen.test_window_decor_right_outset,
- R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -213,14 +207,8 @@
.setVisible(true)
.build();
taskInfo.isFocused = true;
- // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
- // 64px.
+ // Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- mRelayoutParams.setOutsets(
- R.dimen.test_window_decor_left_outset,
- R.dimen.test_window_decor_top_outset,
- R.dimen.test_window_decor_right_outset,
- R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -229,8 +217,7 @@
verify(decorContainerSurfaceBuilder).setParent(taskSurface);
verify(decorContainerSurfaceBuilder).setContainerLayer();
verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true);
- verify(mMockSurfaceControlStartT).setPosition(decorContainerSurface, -20, -40);
- verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 380, 220);
+ verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 300, 100);
verify(taskBackgroundSurfaceBuilder).setParent(taskSurface);
verify(taskBackgroundSurfaceBuilder).setEffectLayer();
@@ -244,7 +231,6 @@
verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
verify(captionContainerSurfaceBuilder).setContainerLayer();
- verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
verify(mMockSurfaceControlStartT).show(captionContainerSurface);
@@ -268,12 +254,12 @@
verify(mMockSurfaceControlFinishT)
.setPosition(taskSurface, TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y);
verify(mMockSurfaceControlFinishT)
- .setCrop(taskSurface, new Rect(-20, -40, 360, 180));
+ .setWindowCrop(taskSurface, 300, 100);
verify(mMockSurfaceControlStartT)
.show(taskSurface);
- assertEquals(380, mRelayoutResult.mWidth);
- assertEquals(220, mRelayoutResult.mHeight);
+ assertEquals(300, mRelayoutResult.mWidth);
+ assertEquals(100, mRelayoutResult.mHeight);
}
@Test
@@ -309,14 +295,8 @@
.setVisible(true)
.build();
taskInfo.isFocused = true;
- // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
- // 64px.
+ // Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- mRelayoutParams.setOutsets(
- R.dimen.test_window_decor_left_outset,
- R.dimen.test_window_decor_top_outset,
- R.dimen.test_window_decor_right_outset,
- R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -419,11 +399,6 @@
.build();
taskInfo.isFocused = true;
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- mRelayoutParams.setOutsets(
- R.dimen.test_window_decor_left_outset,
- R.dimen.test_window_decor_top_outset,
- R.dimen.test_window_decor_right_outset,
- R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
windowDecor.relayout(taskInfo);
@@ -438,7 +413,7 @@
verify(additionalWindowSurfaceBuilder).setContainerLayer();
verify(additionalWindowSurfaceBuilder).setParent(decorContainerSurface);
verify(additionalWindowSurfaceBuilder).build();
- verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 20, 40);
+ verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 0, 0);
final int width = WindowDecoration.loadDimensionPixelSize(
mContext.getResources(), mCaptionMenuWidthId);
final int height = WindowDecoration.loadDimensionPixelSize(
@@ -496,11 +471,6 @@
.build();
taskInfo.isFocused = true;
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- mRelayoutParams.setOutsets(
- R.dimen.test_window_decor_left_outset,
- R.dimen.test_window_decor_top_outset,
- R.dimen.test_window_decor_right_outset,
- R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -508,7 +478,6 @@
verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
verify(captionContainerSurfaceBuilder).setContainerLayer();
- verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
// Width of the captionContainerSurface should match the width of TASK_BOUNDS
verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
verify(mMockSurfaceControlStartT).show(captionContainerSurface);
@@ -584,9 +553,7 @@
String name = "Test Window";
WindowDecoration.AdditionalWindow additionalWindow =
addWindow(R.layout.desktop_mode_window_decor_handle_menu_app_info_pill, name,
- mMockSurfaceControlAddWindowT,
- x - mRelayoutResult.mDecorContainerOffsetX,
- y - mRelayoutResult.mDecorContainerOffsetY,
+ mMockSurfaceControlAddWindowT, x, y,
width, height, shadowRadius, cornerRadius);
return additionalWindow;
}
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 2a8cb42..c4d3f5c 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -53,6 +53,8 @@
}
MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() {
+ bool wasSurfaceless = mEglManager.isCurrent(EGL_NO_SURFACE);
+
// In case the surface was destroyed (e.g. a previous trimMemory call) we
// need to recreate it here.
if (mHardwareBuffer) {
@@ -65,6 +67,37 @@
if (!mEglManager.makeCurrent(mEglSurface, &error)) {
return MakeCurrentResult::AlreadyCurrent;
}
+
+ // Make sure read/draw buffer state of default framebuffer is GL_BACK. Vendor implementations
+ // disagree on the draw/read buffer state if the default framebuffer transitions from a surface
+ // to EGL_NO_SURFACE and vice-versa. There was a related discussion within Khronos on this topic.
+ // See https://cvs.khronos.org/bugzilla/show_bug.cgi?id=13534.
+ // The discussion was not resolved with a clear consensus
+ if (error == 0 && wasSurfaceless && mEglSurface != EGL_NO_SURFACE) {
+ GLint curReadFB = 0;
+ GLint curDrawFB = 0;
+ glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &curReadFB);
+ glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &curDrawFB);
+
+ GLint buffer = GL_NONE;
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glGetIntegerv(GL_DRAW_BUFFER0, &buffer);
+ if (buffer == GL_NONE) {
+ const GLenum drawBuffer = GL_BACK;
+ glDrawBuffers(1, &drawBuffer);
+ }
+
+ glGetIntegerv(GL_READ_BUFFER, &buffer);
+ if (buffer == GL_NONE) {
+ glReadBuffer(GL_BACK);
+ }
+
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, curReadFB);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, curDrawFB);
+
+ GL_CHECKPOINT(LOW);
+ }
+
return error ? MakeCurrentResult::Failed : MakeCurrentResult::Succeeded;
}
diff --git a/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java b/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java
new file mode 100644
index 0000000..80bc5c0
--- /dev/null
+++ b/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java
@@ -0,0 +1,630 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.soundtrigger;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.TestApi;
+import android.hardware.soundtrigger.ConversionUtil;
+import android.hardware.soundtrigger.SoundTrigger;
+import android.media.soundtrigger_middleware.IAcknowledgeEvent;
+import android.media.soundtrigger_middleware.IInjectGlobalEvent;
+import android.media.soundtrigger_middleware.IInjectModelEvent;
+import android.media.soundtrigger_middleware.IInjectRecognitionEvent;
+import android.media.soundtrigger_middleware.ISoundTriggerInjection;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.ISoundTriggerService;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Used to inject/observe events when using a fake SoundTrigger HAL for test purposes.
+ * Created by {@link SoundTriggerManager#getInjection(Executor, GlobalCallback)}.
+ * Only one instance of this class is valid at any given time, old instances will be delivered
+ * {@link GlobalCallback#onPreempted()}.
+ * @hide
+ */
+@TestApi
+public final class SoundTriggerInstrumentation {
+
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private IInjectGlobalEvent mInjectGlobalEvent = null;
+
+ @GuardedBy("mLock")
+ private Map<IBinder, ModelSession> mModelSessionMap = new HashMap<>();
+ @GuardedBy("mLock")
+ private Map<IBinder, RecognitionSession> mRecognitionSessionMap = new HashMap<>();
+ @GuardedBy("mLock")
+ private IBinder mClientToken = null;
+
+ private final GlobalCallback mClientCallback;
+ private final Executor mGlobalCallbackExecutor;
+
+ /**
+ * Callback interface for un-sessioned events observed from the fake STHAL.
+ * Registered upon construction of {@link SoundTriggerInstrumentation}
+ * @hide
+ */
+ @TestApi
+ public interface GlobalCallback {
+ /**
+ * Called when the created {@link SoundTriggerInstrumentation} object is invalidated
+ * by another client creating an {@link SoundTriggerInstrumentation} to instrument the
+ * fake STHAL. Only one client may inject at a time.
+ * All sessions are invalidated, no further events will be received, and no
+ * injected events will be delivered.
+ */
+ default void onPreempted() {}
+ /**
+ * Called when the STHAL has been restarted by the framework, due to unexpected
+ * error conditions.
+ * Not called when {@link SoundTriggerInstrumentation#triggerRestart()} is injected.
+ */
+ default void onRestarted() {}
+ /**
+ * Called when the framework detaches from the fake HAL.
+ * This is not transmitted to real HALs, but it indicates that the
+ * framework has flushed its global state.
+ */
+ default void onFrameworkDetached() {}
+ /**
+ * Called when a client application attaches to the framework.
+ * This is not transmitted to real HALs, but it represents the state of
+ * the framework.
+ */
+ default void onClientAttached() {}
+ /**
+ * Called when a client application detaches from the framework.
+ * This is not transmitted to real HALs, but it represents the state of
+ * the framework.
+ */
+ default void onClientDetached() {}
+ /**
+ * Called when the fake HAL receives a model load from the framework.
+ * @param modelSession - A session which exposes additional injection
+ * functionality associated with the newly loaded
+ * model. See {@link ModelSession}.
+ */
+ void onModelLoaded(@NonNull ModelSession modelSession);
+ }
+
+ /**
+ * Callback for HAL events related to a loaded model. Register with
+ * {@link ModelSession#setModelCallback(Executor, ModelCallback)}
+ * Note, callbacks will not be delivered for events triggered by the injection.
+ * @hide
+ */
+ @TestApi
+ public interface ModelCallback {
+ /**
+ * Called when the model associated with the {@link ModelSession} this callback
+ * was registered for was unloaded by the framework.
+ */
+ default void onModelUnloaded() {}
+ /**
+ * Called when the model associated with the {@link ModelSession} this callback
+ * was registered for receives a set parameter call from the framework.
+ * @param param - Parameter being set.
+ * See {@link SoundTrigger.ModelParamTypes}
+ * @param value - Value the model parameter was set to.
+ */
+ default void onParamSet(@SoundTrigger.ModelParamTypes int param, int value) {}
+ /**
+ * Called when the model associated with the {@link ModelSession} this callback
+ * was registered for receives a recognition start request.
+ * @param recognitionSession - A session which exposes additional injection
+ * functionality associated with the newly started
+ * recognition. See {@link RecognitionSession}
+ */
+ void onRecognitionStarted(@NonNull RecognitionSession recognitionSession);
+ }
+
+ /**
+ * Callback for HAL events related to a started recognition. Register with
+ * {@link RecognitionSession#setRecognitionCallback(Executor, RecognitionCallback)}
+ * Note, callbacks will not be delivered for events triggered by the injection.
+ * @hide
+ */
+ @TestApi
+ public interface RecognitionCallback {
+ /**
+ * Called when the recognition associated with the {@link RecognitionSession} this
+ * callback was registered for was stopped by the framework.
+ */
+ void onRecognitionStopped();
+ }
+
+ /**
+ * Session associated with a loaded model in the fake STHAL.
+ * Can be used to query details about the loaded model, register a callback for future
+ * model events, or trigger HAL events associated with a loaded model.
+ * This session is invalid once the model is unloaded, caused by a
+ * {@link ModelSession#triggerUnloadModel()},
+ * the client unloading recognition, or if a {@link GlobalCallback#onRestarted()} is
+ * received.
+ * Further injections on an invalidated session will not be respected, and no future
+ * callbacks will be delivered.
+ * @hide
+ */
+ @TestApi
+ public class ModelSession {
+
+ /**
+ * Trigger the HAL to preemptively unload the model associated with this session.
+ * Typically occurs when a higher priority model is loaded which utilizes the same
+ * resources.
+ */
+ public void triggerUnloadModel() {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ try {
+ mInjectModelEvent.triggerUnloadModel();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mModelSessionMap.remove(mInjectModelEvent.asBinder());
+ }
+ }
+
+ /**
+ * Get the {@link SoundTriggerManager.Model} associated with this session.
+ * @return - The model associated with this session.
+ */
+ public @NonNull SoundTriggerManager.Model getSoundModel() {
+ return mModel;
+ }
+
+ /**
+ * Get the list of {@link SoundTrigger.Keyphrase} associated with this session.
+ * @return - The keyphrases associated with this session.
+ */
+ public @NonNull List<SoundTrigger.Keyphrase> getPhrases() {
+ if (mPhrases == null) {
+ return new ArrayList<>();
+ } else {
+ return new ArrayList<>(Arrays.asList(mPhrases));
+ }
+ }
+
+ /**
+ * Get whether this model is of keyphrase type.
+ * @return - true if the model is a keyphrase model, false otherwise
+ */
+ public boolean isKeyphrase() {
+ return (mPhrases != null);
+ }
+
+ /**
+ * Registers the model callback associated with this session. Events associated
+ * with this model session will be reported via this callback.
+ * See {@link ModelCallback}
+ * @param executor - Executor which the callback is dispatched on
+ * @param callback - Model callback for reporting model session events.
+ */
+ public void setModelCallback(@NonNull @CallbackExecutor Executor executor, @NonNull
+ ModelCallback callback) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ mModelCallback = Objects.requireNonNull(callback);
+ mModelExecutor = Objects.requireNonNull(executor);
+ }
+ }
+
+ /**
+ * Clear the model callback associated with this session, if any has been
+ * set by {@link #setModelCallback(Executor, ModelCallback)}.
+ */
+ public void clearModelCallback() {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ mModelCallback = null;
+ mModelExecutor = null;
+ }
+ }
+
+ private ModelSession(SoundModel model, Phrase[] phrases,
+ IInjectModelEvent injection) {
+ mModel = SoundTriggerManager.Model.create(UUID.fromString(model.uuid),
+ UUID.fromString(model.vendorUuid),
+ ConversionUtil.sharedMemoryToByteArray(model.data, model.dataSize));
+ if (phrases != null) {
+ mPhrases = new SoundTrigger.Keyphrase[phrases.length];
+ int i = 0;
+ for (var phrase : phrases) {
+ mPhrases[i++] = ConversionUtil.aidl2apiPhrase(phrase);
+ }
+ } else {
+ mPhrases = null;
+ }
+ mInjectModelEvent = injection;
+ }
+
+ private void wrap(Consumer<ModelCallback> consumer) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ if (mModelCallback != null && mModelExecutor != null) {
+ final ModelCallback callback = mModelCallback;
+ mModelExecutor.execute(() -> consumer.accept(callback));
+ }
+ }
+ }
+
+ private final SoundTriggerManager.Model mModel;
+ private final SoundTrigger.Keyphrase[] mPhrases;
+ private final IInjectModelEvent mInjectModelEvent;
+
+ @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+ private ModelCallback mModelCallback = null;
+ @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+ private Executor mModelExecutor = null;
+ }
+
+ /**
+ * Session associated with a recognition start in the fake STHAL.
+ * Can be used to get information about the started recognition, register a callback
+ * for future events associated with this recognition, and triggering
+ * recognition events or aborts.
+ * This session is invalid once the recognition is stopped, caused by a
+ * {@link RecognitionSession#triggerAbortRecognition()},
+ * {@link RecognitionSession#triggerRecognitionEvent(byte[], List)},
+ * the client stopping recognition, or any operation which invalidates the
+ * {@link ModelSession} which the session was created from.
+ * Further injections on an invalidated session will not be respected, and no future
+ * callbacks will be delivered.
+ * @hide
+ */
+ @TestApi
+ public class RecognitionSession {
+
+ /**
+ * Get an integer token representing the audio session associated with this
+ * recognition in the STHAL.
+ * @return - The session token.
+ */
+ public int getAudioSession() {
+ return mAudioSession;
+ }
+
+ /**
+ * Get the recognition config used to start this recognition.
+ * @return - The config passed to the HAL for startRecognition.
+ */
+ public @NonNull SoundTrigger.RecognitionConfig getRecognitionConfig() {
+ return mRecognitionConfig;
+ }
+
+ /**
+ * Trigger a recognition in the fake STHAL.
+ * @param data - The opaque data buffer included in the recognition event.
+ * @param phraseExtras - Keyphrase metadata included in the event. The
+ * event must include metadata for the keyphrase id
+ * associated with this model to be received by the
+ * client application.
+ */
+ public void triggerRecognitionEvent(@NonNull byte[] data, @Nullable
+ List<SoundTrigger.KeyphraseRecognitionExtra> phraseExtras) {
+ PhraseRecognitionExtra[] converted = null;
+ if (phraseExtras != null) {
+ converted = new PhraseRecognitionExtra[phraseExtras.size()];
+ int i = 0;
+ for (var phraseExtra : phraseExtras) {
+ converted[i++] = ConversionUtil.api2aidlPhraseRecognitionExtra(phraseExtra);
+ }
+ }
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ mRecognitionSessionMap.remove(mInjectRecognitionEvent.asBinder());
+ try {
+ mInjectRecognitionEvent.triggerRecognitionEvent(data, converted);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Trigger an abort recognition event in the fake HAL. This represents a
+ * preemptive ending of the recognition session by the HAL, despite no
+ * recognition detection. Typically occurs during contention for microphone
+ * usage, or if model limits are hit.
+ * See {@link SoundTriggerInstrumentation#setResourceContention(boolean)} to block
+ * subsequent downward calls for contention reasons.
+ */
+ public void triggerAbortRecognition() {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ mRecognitionSessionMap.remove(mInjectRecognitionEvent.asBinder());
+ try {
+ mInjectRecognitionEvent.triggerAbortRecognition();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Registers the recognition callback associated with this session. Events associated
+ * with this recognition session will be reported via this callback.
+ * See {@link RecognitionCallback}
+ * @param executor - Executor which the callback is dispatched on
+ * @param callback - Recognition callback for reporting recognition session events.
+ */
+ public void setRecognitionCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull RecognitionCallback callback) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ mRecognitionCallback = callback;
+ mRecognitionExecutor = executor;
+ }
+ }
+
+ /**
+ * Clear the recognition callback associated with this session, if any has been
+ * set by {@link #setRecognitionCallback(Executor, RecognitionCallback)}.
+ */
+ public void clearRecognitionCallback() {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ mRecognitionCallback = null;
+ mRecognitionExecutor = null;
+ }
+ }
+
+ private RecognitionSession(int audioSession,
+ RecognitionConfig recognitionConfig,
+ IInjectRecognitionEvent injectRecognitionEvent) {
+ mAudioSession = audioSession;
+ mRecognitionConfig = ConversionUtil.aidl2apiRecognitionConfig(recognitionConfig);
+ mInjectRecognitionEvent = injectRecognitionEvent;
+ }
+
+ private void wrap(Consumer<RecognitionCallback> consumer) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ if (mRecognitionCallback != null && mRecognitionExecutor != null) {
+ final RecognitionCallback callback = mRecognitionCallback;
+ mRecognitionExecutor.execute(() -> consumer.accept(callback));
+ }
+ }
+ }
+
+ private final int mAudioSession;
+ private final SoundTrigger.RecognitionConfig mRecognitionConfig;
+ private final IInjectRecognitionEvent mInjectRecognitionEvent;
+
+ @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+ private Executor mRecognitionExecutor = null;
+ @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+ private RecognitionCallback mRecognitionCallback = null;
+ }
+
+ // Implementation of injection interface passed to the HAL.
+ // This class will re-associate events received on this callback interface
+ // with sessions, to avoid staleness issues.
+ private class Injection extends ISoundTriggerInjection.Stub {
+ @Override
+ public void registerGlobalEventInjection(IInjectGlobalEvent globalInjection) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ mInjectGlobalEvent = globalInjection;
+ }
+ }
+
+ @Override
+ public void onSoundModelLoaded(SoundModel model, @Nullable Phrase[] phrases,
+ IInjectModelEvent modelInjection, IInjectGlobalEvent globalSession) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return;
+ ModelSession modelSession = new ModelSession(model, phrases, modelInjection);
+ mModelSessionMap.put(modelInjection.asBinder(), modelSession);
+ mGlobalCallbackExecutor.execute(() -> mClientCallback.onModelLoaded(modelSession));
+ }
+ }
+
+ @Override
+ public void onSoundModelUnloaded(IInjectModelEvent modelSession) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ ModelSession clientModelSession = mModelSessionMap.remove(modelSession.asBinder());
+ if (clientModelSession == null) return;
+ clientModelSession.wrap((ModelCallback cb) -> cb.onModelUnloaded());
+ }
+ }
+
+ @Override
+ public void onRecognitionStarted(int audioSessionHandle, RecognitionConfig config,
+ IInjectRecognitionEvent recognitionInjection, IInjectModelEvent modelSession) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ ModelSession clientModelSession = mModelSessionMap.get(modelSession.asBinder());
+ if (clientModelSession == null) return;
+ RecognitionSession recogSession = new RecognitionSession(
+ audioSessionHandle, config, recognitionInjection);
+ mRecognitionSessionMap.put(recognitionInjection.asBinder(), recogSession);
+ clientModelSession.wrap((ModelCallback cb) ->
+ cb.onRecognitionStarted(recogSession));
+ }
+ }
+
+ @Override
+ public void onRecognitionStopped(IInjectRecognitionEvent recognitionSession) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ RecognitionSession clientRecognitionSession =
+ mRecognitionSessionMap.remove(recognitionSession.asBinder());
+ if (clientRecognitionSession == null) return;
+ clientRecognitionSession.wrap((RecognitionCallback cb)
+ -> cb.onRecognitionStopped());
+ }
+ }
+
+ @Override
+ public void onParamSet(int modelParam, int value, IInjectModelEvent modelSession) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ ModelSession clientModelSession = mModelSessionMap.get(modelSession.asBinder());
+ if (clientModelSession == null) return;
+ clientModelSession.wrap((ModelCallback cb) -> cb.onParamSet(modelParam, value));
+ }
+ }
+
+
+ @Override
+ public void onRestarted(IInjectGlobalEvent globalSession) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return;
+ mRecognitionSessionMap.clear();
+ mModelSessionMap.clear();
+ mGlobalCallbackExecutor.execute(() -> mClientCallback.onRestarted());
+ }
+ }
+
+ @Override
+ public void onFrameworkDetached(IInjectGlobalEvent globalSession) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return;
+ mGlobalCallbackExecutor.execute(() -> mClientCallback.onFrameworkDetached());
+ }
+ }
+
+ @Override
+ public void onClientAttached(IBinder token, IInjectGlobalEvent globalSession) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return;
+ mClientToken = token;
+ mGlobalCallbackExecutor.execute(() -> mClientCallback.onClientAttached());
+ }
+ }
+
+ @Override
+ public void onClientDetached(IBinder token) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ if (token != mClientToken) return;
+ mClientToken = null;
+ mGlobalCallbackExecutor.execute(() -> mClientCallback.onClientDetached());
+ }
+ }
+
+ @Override
+ public void onPreempted() {
+ // This is always valid, independent of session
+ mGlobalCallbackExecutor.execute(() -> mClientCallback.onPreempted());
+ // Callbacks will no longer be delivered, and injection will be silently dropped.
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+ public SoundTriggerInstrumentation(ISoundTriggerService service,
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull GlobalCallback callback) {
+ mClientCallback = Objects.requireNonNull(callback);
+ mGlobalCallbackExecutor = Objects.requireNonNull(executor);
+ try {
+ service.attachInjection(new Injection());
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Simulate a HAL restart, typically caused by the framework on an unexpected error,
+ * or a restart of the core audio HAL.
+ * Application sessions will be detached, and all state will be cleared. The framework
+ * will re-attach to the HAL following restart.
+ * @hide
+ */
+ @TestApi
+ public void triggerRestart() {
+ synchronized (mLock) {
+ if (mInjectGlobalEvent == null) {
+ throw new IllegalStateException(
+ "Attempted to trigger HAL restart before registration");
+ }
+ try {
+ mInjectGlobalEvent.triggerRestart();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Trigger a resource available callback from the fake SoundTrigger HAL to the framework.
+ * This callback notifies the framework that methods which previously failed due to
+ * resource contention may now succeed.
+ * @hide
+ */
+ @TestApi
+ public void triggerOnResourcesAvailable() {
+ synchronized (mLock) {
+ if (mInjectGlobalEvent == null) {
+ throw new IllegalStateException(
+ "Attempted to trigger HAL resources available before registration");
+ }
+ try {
+ mInjectGlobalEvent.triggerOnResourcesAvailable();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Simulate resource contention, similar to when HAL which does not
+ * support concurrent capture opens a capture stream, or when a HAL
+ * has reached its maximum number of models.
+ * Subsequent model loads and recognition starts will gracefully error.
+ * Since this call does not trigger a callback through the framework, the
+ * call will block until the fake HAL has acknowledged the state change.
+ * @param isResourceContended - true to enable contention, false to return
+ * to normal functioning.
+ * @hide
+ */
+ @TestApi
+ public void setResourceContention(boolean isResourceContended) {
+ synchronized (mLock) {
+ if (mInjectGlobalEvent == null) {
+ throw new IllegalStateException("Injection interface not set up");
+ }
+ IInjectGlobalEvent current = mInjectGlobalEvent;
+ final CountDownLatch signal = new CountDownLatch(1);
+ try {
+ current.setResourceContention(isResourceContended, new IAcknowledgeEvent.Stub() {
+ @Override
+ public void eventReceived() {
+ signal.countDown();
+ }
+ });
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ // Block until we get a callback from the service that our request was serviced.
+ try {
+ // Rely on test timeout if we don't get a response.
+ signal.await();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+}
+
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index ae8121a..c41bd1b 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -18,11 +18,13 @@
import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.app.ActivityThread;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
@@ -45,6 +47,7 @@
import android.os.IBinder;
import android.os.ParcelUuid;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.provider.Settings;
import android.util.Slog;
@@ -53,9 +56,9 @@
import com.android.internal.util.Preconditions;
import java.util.HashMap;
-import java.util.List;
import java.util.Objects;
import java.util.UUID;
+import java.util.concurrent.Executor;
/**
* This class provides management of non-voice (general sound trigger) based sound recognition
@@ -609,4 +612,24 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Create a {@link SoundTriggerInstrumentation} for test purposes, which instruments a fake
+ * STHAL. Clients must attach to the appropriate underlying ST module.
+ * @param executor - Executor to dispatch global callbacks on
+ * @param callback - Callback for unsessioned events received by the fake STHAL
+ * @return - A {@link SoundTriggerInstrumentation} for observation/injection.
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+ @NonNull
+ public static SoundTriggerInstrumentation attachInstrumentation(
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull SoundTriggerInstrumentation.GlobalCallback callback) {
+ ISoundTriggerService service = ISoundTriggerService.Stub.asInterface(
+ ServiceManager.getService(Context.SOUND_TRIGGER_SERVICE));
+ return new SoundTriggerInstrumentation(service, executor, callback);
+ }
+
}
diff --git a/media/java/android/media/voice/KeyphraseModelManager.java b/media/java/android/media/voice/KeyphraseModelManager.java
index 8ec8967..5a690a5 100644
--- a/media/java/android/media/voice/KeyphraseModelManager.java
+++ b/media/java/android/media/voice/KeyphraseModelManager.java
@@ -21,7 +21,9 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.hardware.soundtrigger.SoundTrigger;
+import android.os.Binder;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.util.Slog;
@@ -154,4 +156,23 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Override the persistent enrolled model database with an in-memory
+ * fake for testing purposes.
+ *
+ * @param enabled - {@code true} if the model enrollment database should be overridden with an
+ * in-memory fake. {@code false} if the real, persistent model enrollment database should be
+ * used.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
+ @TestApi
+ public void setModelDatabaseForTestEnabled(boolean enabled) {
+ try {
+ mVoiceInteractionManagerService.setModelDatabaseForTestEnabled(enabled, new Binder());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index ca89129..64addf2 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -252,13 +252,6 @@
))
}
is PublicKeyCredentialEntry -> {
- val passkeyUsername = credentialEntry.username.toString()
- val passkeyDisplayName = credentialEntry.displayName?.toString() ?: ""
- val (username, displayName) = userAndDisplayNameForPasskey(
- passkeyUsername = passkeyUsername,
- passkeyDisplayName = passkeyDisplayName,
- )
-
result.add(CredentialEntryInfo(
providerId = providerId,
providerDisplayName = providerLabel,
@@ -268,8 +261,8 @@
fillInIntent = it.frameworkExtrasIntent,
credentialType = CredentialType.PASSKEY,
credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(),
- userName = username,
- displayName = displayName,
+ userName = credentialEntry.username.toString(),
+ displayName = credentialEntry.displayName?.toString(),
icon = credentialEntry.icon.loadDrawable(context),
shouldTintIcon = credentialEntry.isDefaultIcon,
lastUsedTimeMillis = credentialEntry.lastUsedTime,
@@ -707,7 +700,7 @@
* 2) username on top if display-name is not available.
* 3) don't show username on second line if username == display-name
*/
-private fun userAndDisplayNameForPasskey(
+fun userAndDisplayNameForPasskey(
passkeyUsername: String,
passkeyDisplayName: String,
): Pair<String, String> {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index c1ea1d8..b8b7ac3 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -66,6 +66,7 @@
import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant
import com.android.credentialmanager.common.ui.Snackbar
import com.android.credentialmanager.logging.GetCredentialEvent
+import com.android.credentialmanager.userAndDisplayNameForPasskey
import com.android.internal.logging.UiEventLogger.UiEventEnum
@Composable
@@ -457,6 +458,10 @@
enforceOneLine: Boolean = false,
onTextLayout: (TextLayoutResult) -> Unit = {},
) {
+ val (username, displayName) = if (credentialEntryInfo.credentialType == CredentialType.PASSKEY)
+ userAndDisplayNameForPasskey(
+ credentialEntryInfo.userName, credentialEntryInfo.displayName ?: "")
+ else Pair(credentialEntryInfo.userName, credentialEntryInfo.displayName)
Entry(
onClick = { onEntrySelected(credentialEntryInfo) },
iconImageBitmap = credentialEntryInfo.icon?.toBitmap()?.asImageBitmap(),
@@ -465,13 +470,13 @@
iconPainter =
if (credentialEntryInfo.icon == null) painterResource(R.drawable.ic_other_sign_in_24)
else null,
- entryHeadlineText = credentialEntryInfo.userName,
+ entryHeadlineText = username,
entrySecondLineText = if (
credentialEntryInfo.credentialType == CredentialType.PASSWORD) {
"••••••••••••"
} else {
val itemsToDisplay = listOf(
- credentialEntryInfo.displayName,
+ displayName,
credentialEntryInfo.credentialTypeDisplayName,
credentialEntryInfo.providerDisplayName
).filterNot(TextUtils::isEmpty)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index 5342def..2c3e58e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -20,9 +20,12 @@
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ApplicationInfoFlags
import android.content.pm.ResolveInfo
import com.android.internal.R
+import com.android.settingslib.spaprivileged.framework.common.userManager
import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -33,7 +36,11 @@
*/
internal interface AppListRepository {
/** Loads the list of [ApplicationInfo]. */
- suspend fun loadApps(userId: Int, showInstantApps: Boolean): List<ApplicationInfo>
+ suspend fun loadApps(
+ userId: Int,
+ showInstantApps: Boolean = false,
+ matchAnyUserForAdmin: Boolean = false,
+ ): List<ApplicationInfo>
/** Gets the flow of predicate that could used to filter system app. */
fun showSystemPredicate(
@@ -61,10 +68,12 @@
internal class AppListRepositoryImpl(private val context: Context) : AppListRepository {
private val packageManager = context.packageManager
+ private val userManager = context.userManager
override suspend fun loadApps(
userId: Int,
showInstantApps: Boolean,
+ matchAnyUserForAdmin: Boolean,
): List<ApplicationInfo> = coroutineScope {
val hiddenSystemModulesDeferred = async {
packageManager.getInstalledModules(0)
@@ -75,12 +84,8 @@
val hideWhenDisabledPackagesDeferred = async {
context.resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)
}
- val flags = PackageManager.ApplicationInfoFlags.of(
- (PackageManager.MATCH_DISABLED_COMPONENTS or
- PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
- )
val installedApplicationsAsUser =
- packageManager.getInstalledApplicationsAsUser(flags, userId)
+ getInstalledApplications(userId, matchAnyUserForAdmin)
val hiddenSystemModules = hiddenSystemModulesDeferred.await()
val hideWhenDisabledPackages = hideWhenDisabledPackagesDeferred.await()
@@ -89,6 +94,46 @@
}
}
+ private suspend fun getInstalledApplications(
+ userId: Int,
+ matchAnyUserForAdmin: Boolean,
+ ): List<ApplicationInfo> {
+ val regularFlags = ApplicationInfoFlags.of(
+ (PackageManager.MATCH_DISABLED_COMPONENTS or
+ PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
+ )
+ return if (!matchAnyUserForAdmin || !userManager.getUserInfo(userId).isAdmin) {
+ packageManager.getInstalledApplicationsAsUser(regularFlags, userId)
+ } else {
+ coroutineScope {
+ val deferredPackageNamesInChildProfiles =
+ userManager.getProfileIdsWithDisabled(userId)
+ .filter { it != userId }
+ .map {
+ async {
+ packageManager.getInstalledApplicationsAsUser(regularFlags, it)
+ .map { it.packageName }
+ }
+ }
+ val adminFlags = ApplicationInfoFlags.of(
+ PackageManager.MATCH_ANY_USER.toLong() or regularFlags.value
+ )
+ val allInstalledApplications =
+ packageManager.getInstalledApplicationsAsUser(adminFlags, userId)
+ val packageNamesInChildProfiles = deferredPackageNamesInChildProfiles
+ .awaitAll()
+ .flatten()
+ .toSet()
+ // If an app is for a child profile and not installed on the owner, not display as
+ // 'not installed for this user' in the owner. This will prevent duplicates of work
+ // only apps showing up in the personal profile.
+ allInstalledApplications.filter {
+ it.installed || it.packageName !in packageNamesInChildProfiles
+ }
+ }
+ }
+ }
+
override fun showSystemPredicate(
userIdFlow: Flow<Int>,
showSystemFlow: Flow<Boolean>,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
index 8896042..bd99ebd 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
@@ -80,12 +80,15 @@
private val scope = viewModelScope + Dispatchers.IO
private val userSubGraphsFlow = appListConfig.flow.map { config ->
- config.userIds.map { userId -> UserSubGraph(userId, config.showInstantApps) }
+ config.userIds.map { userId ->
+ UserSubGraph(userId, config.showInstantApps, config.matchAnyUserForAdmin)
+ }
}.shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
private inner class UserSubGraph(
private val userId: Int,
private val showInstantApps: Boolean,
+ private val matchAnyUserForAdmin: Boolean,
) {
private val userIdFlow = flowOf(userId)
@@ -110,7 +113,8 @@
fun reloadApps() {
scope.launch {
- appsStateFlow.value = appListRepository.loadApps(userId, showInstantApps)
+ appsStateFlow.value =
+ appListRepository.loadApps(userId, showInstantApps, matchAnyUserForAdmin)
}
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index f4a6b59..066db34 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -62,6 +62,7 @@
data class AppListConfig(
val userIds: List<Int>,
val showInstantApps: Boolean,
+ val matchAnyUserForAdmin: Boolean,
)
data class AppListState(
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index 2ebbe8a..89bfa0e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -38,6 +38,7 @@
title: String,
listModel: AppListModel<T>,
showInstantApps: Boolean = false,
+ matchAnyUserForAdmin: Boolean = false,
primaryUserOnly: Boolean = false,
noItemMessage: String? = null,
moreOptions: @Composable MoreOptionsScope.() -> Unit = {},
@@ -59,6 +60,7 @@
config = AppListConfig(
userIds = userGroup.userInfos.map { it.id },
showInstantApps = showInstantApps,
+ matchAnyUserForAdmin = matchAnyUserForAdmin,
),
listModel = listModel,
state = AppListState(
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
index 57972ed..b732a6a 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -23,9 +23,13 @@
import android.content.pm.PackageManager.ApplicationInfoFlags
import android.content.pm.PackageManager.ResolveInfoFlags
import android.content.pm.ResolveInfo
+import android.content.pm.UserInfo
import android.content.res.Resources
+import android.os.UserManager
+import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.internal.R
+import com.android.settingslib.spaprivileged.framework.common.userManager
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
@@ -35,10 +39,13 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
+import org.mockito.Spy
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
import org.mockito.Mockito.`when` as whenever
@@ -49,8 +56,8 @@
@get:Rule
val mockito: MockitoRule = MockitoJUnit.rule()
- @Mock
- private lateinit var context: Context
+ @Spy
+ private val context: Context = ApplicationProvider.getApplicationContext()
@Mock
private lateinit var resources: Resources
@@ -58,6 +65,9 @@
@Mock
private lateinit var packageManager: PackageManager
+ @Mock
+ private lateinit var userManager: UserManager
+
private lateinit var repository: AppListRepository
@Before
@@ -66,36 +76,116 @@
whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
.thenReturn(emptyArray())
whenever(context.packageManager).thenReturn(packageManager)
+ whenever(context.userManager).thenReturn(userManager)
whenever(packageManager.getInstalledModules(anyInt())).thenReturn(emptyList())
whenever(
- packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID))
+ packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), anyInt())
).thenReturn(emptyList())
+ whenever(userManager.getUserInfo(ADMIN_USER_ID)).thenReturn(UserInfo().apply {
+ flags = UserInfo.FLAG_ADMIN
+ })
+ whenever(userManager.getProfileIdsWithDisabled(ADMIN_USER_ID))
+ .thenReturn(intArrayOf(ADMIN_USER_ID, MANAGED_PROFILE_USER_ID))
repository = AppListRepositoryImpl(context)
}
- private fun mockInstalledApplications(apps: List<ApplicationInfo>) {
+ private fun mockInstalledApplications(apps: List<ApplicationInfo>, userId: Int) {
whenever(
- packageManager.getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(USER_ID))
+ packageManager.getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(userId))
).thenReturn(apps)
}
@Test
fun loadApps_notShowInstantApps() = runTest {
- mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP))
+ mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP), ADMIN_USER_ID)
- val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
+ val appList = repository.loadApps(
+ userId = ADMIN_USER_ID,
+ showInstantApps = false,
+ )
- assertThat(appListFlow).containsExactly(NORMAL_APP)
+ assertThat(appList).containsExactly(NORMAL_APP)
}
@Test
fun loadApps_showInstantApps() = runTest {
- mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP))
+ mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP), ADMIN_USER_ID)
- val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = true)
+ val appList = repository.loadApps(
+ userId = ADMIN_USER_ID,
+ showInstantApps = true,
+ )
- assertThat(appListFlow).containsExactly(NORMAL_APP, INSTANT_APP)
+ assertThat(appList).containsExactly(NORMAL_APP, INSTANT_APP)
+ }
+
+ @Test
+ fun loadApps_notMatchAnyUserForAdmin_withRegularFlags() = runTest {
+ mockInstalledApplications(listOf(NORMAL_APP), ADMIN_USER_ID)
+
+ val appList = repository.loadApps(
+ userId = ADMIN_USER_ID,
+ matchAnyUserForAdmin = false,
+ )
+
+ assertThat(appList).containsExactly(NORMAL_APP)
+ val flags = ArgumentCaptor.forClass(ApplicationInfoFlags::class.java)
+ verify(packageManager).getInstalledApplicationsAsUser(flags.capture(), eq(ADMIN_USER_ID))
+ assertThat(flags.value.value).isEqualTo(
+ PackageManager.MATCH_DISABLED_COMPONENTS or
+ PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+ )
+ }
+
+ @Test
+ fun loadApps_matchAnyUserForAdmin_withMatchAnyUserFlag() = runTest {
+ mockInstalledApplications(listOf(NORMAL_APP), ADMIN_USER_ID)
+
+ val appList = repository.loadApps(
+ userId = ADMIN_USER_ID,
+ matchAnyUserForAdmin = true,
+ )
+
+ assertThat(appList).containsExactly(NORMAL_APP)
+ val flags = ArgumentCaptor.forClass(ApplicationInfoFlags::class.java)
+ verify(packageManager).getInstalledApplicationsAsUser(flags.capture(), eq(ADMIN_USER_ID))
+ assertThat(flags.value.value and PackageManager.MATCH_ANY_USER.toLong()).isGreaterThan(0L)
+ }
+
+ @Test
+ fun loadApps_matchAnyUserForAdminAndInstalledOnManagedProfileOnly_notDisplayed() = runTest {
+ val managedProfileOnlyPackageName = "installed.on.managed.profile.only"
+ mockInstalledApplications(listOf(ApplicationInfo().apply {
+ packageName = managedProfileOnlyPackageName
+ }), ADMIN_USER_ID)
+ mockInstalledApplications(listOf(ApplicationInfo().apply {
+ packageName = managedProfileOnlyPackageName
+ flags = ApplicationInfo.FLAG_INSTALLED
+ }), MANAGED_PROFILE_USER_ID)
+
+ val appList = repository.loadApps(
+ userId = ADMIN_USER_ID,
+ matchAnyUserForAdmin = true,
+ )
+
+ assertThat(appList).isEmpty()
+ }
+
+ @Test
+ fun loadApps_matchAnyUserForAdminAndInstalledOnSecondaryUserOnly_displayed() = runTest {
+ val secondaryUserOnlyApp = ApplicationInfo().apply {
+ packageName = "installed.on.secondary.user.only"
+ }
+ mockInstalledApplications(listOf(secondaryUserOnlyApp), ADMIN_USER_ID)
+ mockInstalledApplications(emptyList(), MANAGED_PROFILE_USER_ID)
+
+ val appList = repository.loadApps(
+ userId = ADMIN_USER_ID,
+ matchAnyUserForAdmin = true,
+ )
+
+ assertThat(appList).containsExactly(secondaryUserOnlyApp)
}
@Test
@@ -106,11 +196,11 @@
}
whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
.thenReturn(arrayOf(app.packageName))
- mockInstalledApplications(listOf(app))
+ mockInstalledApplications(listOf(app), ADMIN_USER_ID)
- val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
+ val appList = repository.loadApps(userId = ADMIN_USER_ID)
- assertThat(appListFlow).isEmpty()
+ assertThat(appList).isEmpty()
}
@Test
@@ -122,11 +212,11 @@
}
whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
.thenReturn(arrayOf(app.packageName))
- mockInstalledApplications(listOf(app))
+ mockInstalledApplications(listOf(app), ADMIN_USER_ID)
- val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
+ val appList = repository.loadApps(userId = ADMIN_USER_ID)
- assertThat(appListFlow).isEmpty()
+ assertThat(appList).isEmpty()
}
@Test
@@ -137,11 +227,11 @@
}
whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
.thenReturn(arrayOf(app.packageName))
- mockInstalledApplications(listOf(app))
+ mockInstalledApplications(listOf(app), ADMIN_USER_ID)
- val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
+ val appList = repository.loadApps(userId = ADMIN_USER_ID)
- assertThat(appListFlow).containsExactly(app)
+ assertThat(appList).containsExactly(app)
}
@Test
@@ -151,11 +241,11 @@
enabled = false
enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER
}
- mockInstalledApplications(listOf(app))
+ mockInstalledApplications(listOf(app), ADMIN_USER_ID)
- val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
+ val appList = repository.loadApps(userId = ADMIN_USER_ID)
- assertThat(appListFlow).containsExactly(app)
+ assertThat(appList).containsExactly(app)
}
@Test
@@ -164,11 +254,11 @@
packageName = "disabled"
enabled = false
}
- mockInstalledApplications(listOf(app))
+ mockInstalledApplications(listOf(app), ADMIN_USER_ID)
- val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
+ val appList = repository.loadApps(userId = ADMIN_USER_ID)
- assertThat(appListFlow).isEmpty()
+ assertThat(appList).isEmpty()
}
@Test
@@ -219,7 +309,11 @@
val app = IN_LAUNCHER_APP
whenever(
- packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID))
+ packageManager.queryIntentActivitiesAsUser(
+ any(),
+ any<ResolveInfoFlags>(),
+ eq(ADMIN_USER_ID)
+ )
).thenReturn(listOf(resolveInfoOf(packageName = app.packageName)))
val showSystemPredicate = getShowSystemPredicate(showSystem = false)
@@ -229,12 +323,16 @@
@Test
fun getSystemPackageNames_returnExpectedValues() = runTest {
- mockInstalledApplications(listOf(
- NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP))
+ mockInstalledApplications(
+ apps = listOf(
+ NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP
+ ),
+ userId = ADMIN_USER_ID,
+ )
val systemPackageNames = AppListRepositoryUtil.getSystemPackageNames(
context = context,
- userId = USER_ID,
+ userId = ADMIN_USER_ID,
showInstantApps = false,
)
@@ -243,12 +341,13 @@
private suspend fun getShowSystemPredicate(showSystem: Boolean) =
repository.showSystemPredicate(
- userIdFlow = flowOf(USER_ID),
+ userIdFlow = flowOf(ADMIN_USER_ID),
showSystemFlow = flowOf(showSystem),
).first()
private companion object {
- const val USER_ID = 0
+ const val ADMIN_USER_ID = 0
+ const val MANAGED_PROFILE_USER_ID = 11
val NORMAL_APP = ApplicationInfo().apply {
packageName = "normal"
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
index 4f0cdde..36d9db5 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
@@ -85,7 +85,11 @@
}
private object FakeAppListRepository : AppListRepository {
- override suspend fun loadApps(userId: Int, showInstantApps: Boolean) = listOf(APP)
+ override suspend fun loadApps(
+ userId: Int,
+ showInstantApps: Boolean,
+ matchAnyUserForAdmin: Boolean,
+ ) = listOf(APP)
override fun showSystemPredicate(
userIdFlow: Flow<Int>,
@@ -112,7 +116,11 @@
const val USER_ID = 0
const val PACKAGE_NAME = "package.name"
const val LABEL = "Label"
- val CONFIG = AppListConfig(userIds = listOf(USER_ID), showInstantApps = false)
+ val CONFIG = AppListConfig(
+ userIds = listOf(USER_ID),
+ showInstantApps = false,
+ matchAnyUserForAdmin = false,
+ )
val APP = ApplicationInfo().apply {
packageName = PACKAGE_NAME
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
index a99d02d..241a134 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
@@ -114,7 +114,11 @@
) {
composeTestRule.setContent {
AppListInput(
- config = AppListConfig(userIds = listOf(USER_ID), showInstantApps = false),
+ config = AppListConfig(
+ userIds = listOf(USER_ID),
+ showInstantApps = false,
+ matchAnyUserForAdmin = false,
+ ),
listModel = TestAppListModel(enableGrouping = enableGrouping),
state = AppListState(
showSystem = false.toState(),
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
index a3db6d7..e28ada4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
@@ -120,9 +120,9 @@
* @return true if the request succeeded.
*/
public static synchronized boolean setPowerSaveMode(Context context,
- boolean enable, boolean needFirstTimeWarning) {
+ boolean enable, boolean needFirstTimeWarning, @SaverManualEnabledReason int reason) {
if (DEBUG) {
- Log.d(TAG, "Battery saver turning " + (enable ? "ON" : "OFF"));
+ Log.d(TAG, "Battery saver turning " + (enable ? "ON" : "OFF") + ", reason: " + reason);
}
final ContentResolver cr = context.getContentResolver();
@@ -152,6 +152,7 @@
sendSystemUiBroadcast(context, ACTION_SHOW_AUTO_SAVER_SUGGESTION,
confirmationExtras);
}
+ recordBatterySaverEnabledReason(context, reason);
}
return true;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
index ad022a6..cb386fb 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
@@ -16,6 +16,7 @@
package com.android.settingslib.fuelgauge;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_UNKNOWN;
import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_NO_SCHEDULE;
import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_PERCENTAGE;
@@ -72,7 +73,8 @@
Secure.putString(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, "null");
Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
- assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isFalse();
+ assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true,
+ SAVER_ENABLED_UNKNOWN)).isFalse();
verify(mMockContext, times(1)).sendBroadcast(any(Intent.class));
verify(mMockPowerManager, times(0)).setPowerSaveModeEnabled(anyBoolean());
@@ -92,7 +94,8 @@
Secure.putInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1);
Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
- assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isTrue();
+ assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true,
+ SAVER_ENABLED_UNKNOWN)).isTrue();
verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true));
@@ -111,7 +114,8 @@
Secure.putInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1);
Secure.putInt(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, 1);
- assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isTrue();
+ assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true,
+ SAVER_ENABLED_UNKNOWN)).isTrue();
verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true));
@@ -129,7 +133,8 @@
Secure.putString(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, "null");
Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
- assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, false)).isTrue();
+ assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, false,
+ SAVER_ENABLED_UNKNOWN)).isTrue();
verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true));
@@ -147,7 +152,8 @@
Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
// When disabling, needFirstTimeWarning doesn't matter.
- assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, false)).isTrue();
+ assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, false,
+ SAVER_ENABLED_UNKNOWN)).isTrue();
verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(false));
@@ -166,7 +172,8 @@
Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
// When disabling, needFirstTimeWarning doesn't matter.
- assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, true)).isTrue();
+ assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, true,
+ SAVER_ENABLED_UNKNOWN)).isTrue();
verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(false));
diff --git a/packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml b/packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml
new file mode 100644
index 0000000..61a1cb5
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml
@@ -0,0 +1,165 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 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
+ -->
+<animated-vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:drawable">
+ <vector android:height="65dp" android:width="46dp" android:viewportHeight="65" android:viewportWidth="46">
+ <group android:name="_R_G">
+ <group android:name="_R_G_L_1_G" android:translateX="3.75" android:translateY="8.25">
+ <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/>
+ <path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/>
+ <path android:name="_R_G_L_1_G_D_2_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/>
+ <path android:name="_R_G_L_1_G_D_3_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/>
+ </group>
+ <group android:name="_R_G_L_0_G" android:translateX="20.357" android:translateY="35.75" android:pivotX="2.75" android:pivotY="2.75" android:scaleX="1.41866" android:scaleY="1.41866">
+ <path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#FF000000" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c "/>
+ </group>
+ </group>
+ <group android:name="time_group"/>
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " android:valueTo="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="pathData" android:duration="143" android:startOffset="107" android:valueFrom="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueTo="M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 " android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.331,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="strokeAlpha" android:duration="140" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="strokeAlpha" android:duration="50" android:startOffset="140" android:valueFrom="1" android:valueTo="0" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " android:valueTo="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="107" android:valueFrom="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueTo="M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 " android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_2_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData" android:duration="250" android:startOffset="0" android:valueFrom="M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " android:valueTo="M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 " android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.189,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_3_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData" android:duration="95" android:startOffset="0" android:valueFrom="M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " android:valueTo="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="pathData" android:duration="24" android:startOffset="95" android:valueFrom="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueTo="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.833,0.767 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="pathData" android:duration="81" android:startOffset="119" android:valueFrom="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.261,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="fillAlpha" android:duration="120" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="fillAlpha" android:duration="20" android:startOffset="120" android:valueFrom="0" android:valueTo="1" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="scaleX" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleY" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleX" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleY" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="translateX" android:duration="517" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
index 951d6fe..f3325ec 100644
--- a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
+++ b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
@@ -104,4 +104,9 @@
android:fromId="@id/unlocked"
android:toId="@id/locked"
android:drawable="@drawable/unlocked_to_locked" />
+
+ <transition
+ android:fromId="@id/locked_fp"
+ android:toId="@id/locked"
+ android:drawable="@drawable/fp_to_locked" />
</animated-selector>
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index 2143fc4..edd3047 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -21,19 +21,19 @@
<!-- Instructions telling the user to enter their PIN password to unlock the keyguard [CHAR LIMIT=30] -->
<string name="keyguard_enter_your_pin">Enter your PIN</string>
- <!-- Instructions telling the user to enter their PIN password to unlock the keyguard [CHAR LIMIT=26] -->
+ <!-- Instructions telling the user to enter their PIN password to unlock the keyguard [CHAR LIMIT=48] -->
<string name="keyguard_enter_pin">Enter PIN</string>
<!-- Instructions telling the user to enter their pattern to unlock the keyguard [CHAR LIMIT=30] -->
<string name="keyguard_enter_your_pattern">Enter your pattern</string>
- <!-- Instructions telling the user to enter their pattern to unlock the keyguard [CHAR LIMIT=26] -->
+ <!-- Instructions telling the user to enter their pattern to unlock the keyguard [CHAR LIMIT=48] -->
<string name="keyguard_enter_pattern">Draw pattern</string>
<!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=30] -->
<string name="keyguard_enter_your_password">Enter your password</string>
- <!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=26] -->
+ <!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=48] -->
<string name="keyguard_enter_password">Enter password</string>
<!-- Shown in the lock screen when there is SIM card IO error. -->
@@ -128,103 +128,103 @@
<!-- Message shown when user enters wrong pattern -->
<string name="kg_wrong_pattern">Wrong pattern</string>
- <!-- Message shown when user enters wrong pattern [CHAR LIMIT=26] -->
+ <!-- Message shown when user enters wrong pattern [CHAR LIMIT=48] -->
<string name="kg_wrong_pattern_try_again">Wrong pattern. Try again.</string>
<!-- Message shown when user enters wrong password -->
<string name="kg_wrong_password">Wrong password</string>
- <!-- Message shown when user enters wrong pattern [CHAR LIMIT=26] -->
+ <!-- Message shown when user enters wrong pattern [CHAR LIMIT=48] -->
<string name="kg_wrong_password_try_again">Wrong password. Try again.</string>
<!-- Message shown when user enters wrong PIN -->
<string name="kg_wrong_pin">Wrong PIN</string>
- <!-- Message shown when user enters wrong PIN [CHAR LIMIT=26] -->
+ <!-- Message shown when user enters wrong PIN [CHAR LIMIT=48] -->
<string name="kg_wrong_pin_try_again">Wrong PIN. Try again.</string>
- <!-- Message shown when user enters wrong PIN/password/pattern below the main message, for ex: "Wrong PIN. Try again" in line 1 and the following text in line 2. [CHAR LIMIT=52] -->
+ <!-- Message shown when user enters wrong PIN/password/pattern below the main message, for ex: "Wrong PIN. Try again" in line 1 and the following text in line 2. [CHAR LIMIT=70] -->
<string name="kg_wrong_input_try_fp_suggestion">Or unlock with fingerprint</string>
- <!-- Message shown when user fingerprint is not recognized [CHAR LIMIT=26] -->
+ <!-- Message shown when user fingerprint is not recognized [CHAR LIMIT=48] -->
<string name="kg_fp_not_recognized">Fingerprint not recognized</string>
- <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=26] -->
+ <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=48] -->
<string name="bouncer_face_not_recognized">Face not recognized</string>
- <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=52] -->
+ <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=70] -->
<string name="kg_bio_try_again_or_pin">Try again or enter PIN</string>
- <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=52] -->
+ <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=70] -->
<string name="kg_bio_try_again_or_password">Try again or enter password</string>
- <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=52] -->
+ <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=70] -->
<string name="kg_bio_try_again_or_pattern">Try again or draw pattern</string>
- <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=52] -->
+ <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=70] -->
<string name="kg_bio_too_many_attempts_pin">PIN is required after too many attempts</string>
- <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=52] -->
+ <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=70] -->
<string name="kg_bio_too_many_attempts_password">Password is required after too many attempts</string>
- <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=52] -->
+ <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=70] -->
<string name="kg_bio_too_many_attempts_pattern">Pattern is required after too many attempts</string>
- <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26] -->
+ <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=48] -->
<string name="kg_unlock_with_pin_or_fp">Unlock with PIN or fingerprint</string>
- <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26] -->
+ <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=48] -->
<string name="kg_unlock_with_password_or_fp">Unlock with password or fingerprint</string>
- <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26] -->
+ <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=48] -->
<string name="kg_unlock_with_pattern_or_fp">Unlock with pattern or fingerprint</string>
- <!-- Message shown when we are on bouncer after Device admin requested lockdown. [CHAR LIMIT=52] -->
+ <!-- Message shown when we are on bouncer after Device admin requested lockdown. [CHAR LIMIT=70] -->
<string name="kg_prompt_after_dpm_lock">For added security, device was locked by work policy</string>
- <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=52] -->
+ <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=70] -->
<string name="kg_prompt_after_user_lockdown_pin">PIN is required after lockdown</string>
- <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=52] -->
+ <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=70] -->
<string name="kg_prompt_after_user_lockdown_password">Password is required after lockdown</string>
- <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=52] -->
+ <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=70] -->
<string name="kg_prompt_after_user_lockdown_pattern">Pattern is required after lockdown</string>
- <!-- Message shown to prepare for an unattended update (OTA). Also known as an over-the-air (OTA) update. [CHAR LIMIT=52] -->
+ <!-- Message shown to prepare for an unattended update (OTA). Also known as an over-the-air (OTA) update. [CHAR LIMIT=70] -->
<string name="kg_prompt_unattended_update">Update will install during inactive hours</string>
- <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=52] -->
+ <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=70] -->
<string name="kg_prompt_pin_auth_timeout">Added security required. PIN not used for a while.</string>
- <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=52] -->
+ <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=70] -->
<string name="kg_prompt_password_auth_timeout">Added security required. Password not used for a while.</string>
- <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=52] -->
+ <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=76] -->
<string name="kg_prompt_pattern_auth_timeout">Added security required. Pattern not used for a while.</string>
- <!-- Message shown when device hasn't been unlocked for a while. [CHAR LIMIT=52] -->
+ <!-- Message shown when device hasn't been unlocked for a while. [CHAR LIMIT=82] -->
<string name="kg_prompt_auth_timeout">Added security required. Device wasn\u2019t unlocked for a while.</string>
- <!-- Message shown when face unlock is not available after too many failed face authentication attempts. [CHAR LIMIT=52] -->
+ <!-- Message shown when face unlock is not available after too many failed face authentication attempts. [CHAR LIMIT=70] -->
<string name="kg_face_locked_out">Can\u2019t unlock with face. Too many attempts.</string>
- <!-- Message shown when fingerprint unlock isn't available after too many failed fingerprint authentication attempts. [CHAR LIMIT=52] -->
+ <!-- Message shown when fingerprint unlock isn't available after too many failed fingerprint authentication attempts. [CHAR LIMIT=75] -->
<string name="kg_fp_locked_out">Can\u2019t unlock with fingerprint. Too many attempts.</string>
- <!-- Message shown when Trust Agent is disabled. [CHAR LIMIT=52] -->
+ <!-- Message shown when Trust Agent is disabled. [CHAR LIMIT=70] -->
<string name="kg_trust_agent_disabled">Trust agent is unavailable</string>
- <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52] -->
+ <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=70] -->
<string name="kg_primary_auth_locked_out_pin">Too many attempts with incorrect PIN</string>
- <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52] -->
+ <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=70] -->
<string name="kg_primary_auth_locked_out_pattern">Too many attempts with incorrect pattern</string>
- <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52] -->
+ <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=70] -->
<string name="kg_primary_auth_locked_out_password">Too many attempts with incorrect password</string>
- <!-- Countdown message shown after too many failed unlock attempts [CHAR LIMIT=26]-->
+ <!-- Countdown message shown after too many failed unlock attempts [CHAR LIMIT=48]-->
<string name="kg_too_many_failed_attempts_countdown">{count, plural,
=1 {Try again in # second.}
other {Try again in # seconds.}
@@ -296,13 +296,13 @@
<!-- Description of airplane mode -->
<string name="airplane_mode">Airplane mode</string>
- <!-- An explanation text that the pattern needs to be solved since the device has just been restarted. [CHAR LIMIT=52] -->
+ <!-- An explanation text that the pattern needs to be solved since the device has just been restarted. [CHAR LIMIT=70] -->
<string name="kg_prompt_reason_restart_pattern">Pattern is required after device restarts</string>
- <!-- An explanation text that the pin needs to be entered since the device has just been restarted. [CHAR LIMIT=52] -->
+ <!-- An explanation text that the pin needs to be entered since the device has just been restarted. [CHAR LIMIT=70] -->
<string name="kg_prompt_reason_restart_pin">PIN is required after device restarts</string>
- <!-- An explanation text that the password needs to be entered since the device has just been restarted. [CHAR LIMIT=52] -->
+ <!-- An explanation text that the password needs to be entered since the device has just been restarted. [CHAR LIMIT=70] -->
<string name="kg_prompt_reason_restart_password">Password is required after device restarts</string>
<!-- An explanation text that the pattern needs to be solved since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] -->
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index f1fca76..d710676 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -152,10 +152,4 @@
<include
layout="@layout/keyguard_bottom_area"
android:visibility="gone" />
-
- <FrameLayout
- android:id="@+id/preview_container"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- </FrameLayout>
</com.android.systemui.shade.NotificationPanelView>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 9290220..25d1792 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -109,7 +109,7 @@
private final ContentObserver mShowWeatherObserver = new ContentObserver(null) {
@Override
public void onChange(boolean change) {
- setDateWeatherVisibility();
+ setWeatherVisibility();
}
};
@@ -236,6 +236,7 @@
updateDoubleLineClock();
setDateWeatherVisibility();
+ setWeatherVisibility();
mKeyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
mKeyguardUnlockAnimationListener);
@@ -266,6 +267,8 @@
mStatusArea.removeView(mDateWeatherView);
addDateWeatherView(index);
}
+ setDateWeatherVisibility();
+ setWeatherVisibility();
}
int index = mStatusArea.indexOfChild(mSmartspaceView);
if (index >= 0) {
@@ -487,16 +490,19 @@
}
private void setDateWeatherVisibility() {
- if (mDateWeatherView != null || mWeatherView != null) {
+ if (mDateWeatherView != null) {
mUiExecutor.execute(() -> {
- if (mDateWeatherView != null) {
- mDateWeatherView.setVisibility(
- clockHasCustomWeatherDataDisplay() ? View.GONE : View.VISIBLE);
- }
- if (mWeatherView != null) {
- mWeatherView.setVisibility(
- mSmartspaceController.isWeatherEnabled() ? View.VISIBLE : View.GONE);
- }
+ mDateWeatherView.setVisibility(
+ clockHasCustomWeatherDataDisplay() ? View.GONE : View.VISIBLE);
+ });
+ }
+ }
+
+ private void setWeatherVisibility() {
+ if (mWeatherView != null) {
+ mUiExecutor.execute(() -> {
+ mWeatherView.setVisibility(
+ mSmartspaceController.isWeatherEnabled() ? View.VISIBLE : View.GONE);
});
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 235a8bc..5f2afe8 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -294,6 +294,11 @@
final CharSequence prevContentDescription = mView.getContentDescription();
if (mShowLockIcon) {
+ if (wasShowingFpIcon) {
+ // fp icon was shown by UdfpsView, and now we still want to animate the transition
+ // in this drawable
+ mView.updateIcon(ICON_FINGERPRINT, false);
+ }
mView.updateIcon(ICON_LOCK, false);
mView.setContentDescription(mLockedLabel);
mView.setVisibility(View.VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 69d7582..012c8cf 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -419,9 +419,6 @@
// TODO(b/254512758): Tracking Bug
@JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple")
- // TODO(b/270882464): Tracking Bug
- val ENABLE_DOCK_SETUP_V2 = releasedFlag(1005, "enable_dock_setup_v2")
-
// TODO(b/265045965): Tracking Bug
val SHOW_LOWLIGHT_ON_DIRECT_BOOT = releasedFlag(1003, "show_lowlight_on_direct_boot")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 56e7398..0abce82 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -92,6 +92,9 @@
/** Current state of whether face authentication is running. */
val isAuthRunning: Flow<Boolean>
+ /** Whether bypass is currently enabled */
+ val isBypassEnabled: Flow<Boolean>
+
/**
* Trigger face authentication.
*
@@ -166,7 +169,7 @@
override val isAuthenticated: Flow<Boolean>
get() = _isAuthenticated
- private val bypassEnabled: Flow<Boolean> =
+ override val isBypassEnabled: Flow<Boolean> =
keyguardBypassController?.let {
conflatedCallbackFlow {
val callback =
@@ -222,7 +225,7 @@
// & detection is supported & biometric unlock is not allowed.
listOf(
canFaceAuthOrDetectRun(),
- logAndObserve(bypassEnabled, "bypassEnabled"),
+ logAndObserve(isBypassEnabled, "isBypassEnabled"),
logAndObserve(
biometricSettingsRepository.isNonStrongBiometricAllowed.isFalse(),
"nonStrongBiometricIsNotAllowed"
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index c2c1306..a765702 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -18,6 +18,10 @@
import static android.app.PendingIntent.FLAG_IMMUTABLE;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_CONFIRMATION;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_LOW_WARNING;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SaverManualEnabledReason;
+
import android.app.Dialog;
import android.app.KeyguardManager;
import android.app.Notification;
@@ -691,7 +695,7 @@
d.setTitle(R.string.battery_saver_confirmation_title);
d.setPositiveButton(R.string.battery_saver_confirmation_ok,
(dialog, which) -> {
- setSaverMode(true, false);
+ setSaverMode(true, false, SAVER_ENABLED_CONFIRMATION);
logEvent(BatteryWarningEvents.LowBatteryWarningEvent.SAVER_CONFIRM_OK);
});
d.setNegativeButton(android.R.string.cancel, (dialog, which) ->
@@ -790,8 +794,9 @@
return builder;
}
- private void setSaverMode(boolean mode, boolean needFirstTimeWarning) {
- BatterySaverUtils.setPowerSaveMode(mContext, mode, needFirstTimeWarning);
+ private void setSaverMode(boolean mode, boolean needFirstTimeWarning,
+ @SaverManualEnabledReason int reason) {
+ BatterySaverUtils.setPowerSaveMode(mContext, mode, needFirstTimeWarning, reason);
}
private void startBatterySaverSchedulePage() {
@@ -839,7 +844,7 @@
} else if (action.equals(ACTION_START_SAVER)) {
logEvent(BatteryWarningEvents
.LowBatteryWarningEvent.LOW_BATTERY_NOTIFICATION_TURN_ON);
- setSaverMode(true, true);
+ setSaverMode(true, true, SAVER_ENABLED_LOW_WARNING);
dismissLowBatteryNotification();
} else if (action.equals(ACTION_SHOW_START_SAVER_CONFIRMATION)) {
dismissLowBatteryNotification();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 9b1e2fa..142689e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -1423,7 +1423,7 @@
private boolean canUnlockWithFingerprint() {
return mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- getCurrentUser());
+ getCurrentUser()) && mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed();
}
private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index e6715a1..d1c6aef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -24,6 +24,7 @@
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.IndentingPrintWriter;
+import android.util.Log;
import android.util.MathUtils;
import android.view.View;
import android.view.ViewGroup;
@@ -96,6 +97,8 @@
private NotificationShelfController mController;
private float mActualWidth = -1;
private boolean mSensitiveRevealAnimEndabled;
+ private boolean mShelfRefactorFlagEnabled;
+ private boolean mCanModifyColorOfNotifications;
public NotificationShelf(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -425,7 +428,7 @@
transitionAmount = inShelfAmount;
}
// We don't want to modify the color if the notification is hun'd
- if (isLastChild && mController.canModifyColorOfNotifications()) {
+ if (isLastChild && canModifyColorOfNotifications()) {
if (colorOfViewBeforeLast == NO_COLOR) {
colorOfViewBeforeLast = ownColorUntinted;
}
@@ -490,6 +493,14 @@
}
}
+ private boolean canModifyColorOfNotifications() {
+ if (mShelfRefactorFlagEnabled) {
+ return mCanModifyColorOfNotifications && mAmbientState.isShadeExpanded();
+ } else {
+ return mController.canModifyColorOfNotifications();
+ }
+ }
+
private void updateCornerRoundnessOnScroll(
ActivatableNotificationView anv,
float viewStart,
@@ -959,10 +970,31 @@
return false;
}
+ private void assertRefactorFlagDisabled() {
+ if (mShelfRefactorFlagEnabled) {
+ throw new IllegalStateException(
+ "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is enabled.");
+ }
+ }
+
+ private boolean checkRefactorFlagEnabled() {
+ if (!mShelfRefactorFlagEnabled) {
+ Log.wtf(TAG,
+ "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is disabled.");
+ }
+ return mShelfRefactorFlagEnabled;
+ }
+
public void setController(NotificationShelfController notificationShelfController) {
+ assertRefactorFlagDisabled();
mController = notificationShelfController;
}
+ public void setCanModifyColorOfNotifications(boolean canModifyColorOfNotifications) {
+ if (!checkRefactorFlagEnabled()) return;
+ mCanModifyColorOfNotifications = canModifyColorOfNotifications;
+ }
+
public void setIndexOfFirstViewInShelf(ExpandableView firstViewInShelf) {
mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
}
@@ -975,6 +1007,10 @@
mSensitiveRevealAnimEndabled = enabled;
}
+ public void setRefactorFlagEnabled(boolean enabled) {
+ mShelfRefactorFlagEnabled = enabled;
+ }
+
/**
* This method resets the OnScroll roundness of a view to 0f
* <p>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
new file mode 100644
index 0000000..db550c0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.statusbar.notification.shelf.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/** Interactor for the [NotificationShelf] */
+@CentralSurfacesComponent.CentralSurfacesScope
+class NotificationShelfInteractor
+@Inject
+constructor(
+ private val keyguardRepository: KeyguardRepository,
+ private val deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository,
+) {
+ /** Is the system in a state where the shelf is just a static display of notification icons? */
+ val isShelfStatic: Flow<Boolean>
+ get() =
+ combine(
+ keyguardRepository.isKeyguardShowing,
+ deviceEntryFaceAuthRepository.isBypassEnabled,
+ ) { isKeyguardShowing, isBypassEnabled ->
+ isKeyguardShowing && isBypassEnabled
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/view/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/view/NotificationShelfViewBinder.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
index a235157..bd531ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/view/NotificationShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
@@ -14,14 +14,17 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.shelf.view
+package com.android.systemui.statusbar.notification.shelf.ui.viewbinder
import android.view.View
import android.view.View.OnAttachStateChangeListener
import android.view.accessibility.AccessibilityManager
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl
import com.android.systemui.statusbar.NotificationShelf
@@ -31,21 +34,16 @@
import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController
import com.android.systemui.statusbar.notification.row.ExpandableOutlineViewController
import com.android.systemui.statusbar.notification.row.ExpandableViewController
+import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
import com.android.systemui.statusbar.notification.stack.AmbientState
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
-import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.NotificationIconContainer
import com.android.systemui.statusbar.phone.NotificationTapHelper
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
-import dagger.Binds
-import dagger.Module
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
import javax.inject.Inject
-/** Binds a [NotificationShelf] to its backend. */
-interface NotificationShelfViewBinder {
- fun bind(shelf: NotificationShelf)
-}
-
/**
* Controller class for [NotificationShelf]. This implementation serves as a temporary wrapper
* around a [NotificationShelfViewBinder], so that external code can continue to depend on the
@@ -57,8 +55,7 @@
@Inject
constructor(
private val shelf: NotificationShelf,
- private val viewBinder: NotificationShelfViewBinder,
- private val keyguardBypassController: KeyguardBypassController,
+ private val viewModel: NotificationShelfViewModel,
featureFlags: FeatureFlags,
private val notifTapHelperFactory: NotificationTapHelper.Factory,
private val a11yManager: AccessibilityManager,
@@ -67,20 +64,19 @@
private val statusBarStateController: SysuiStatusBarStateController,
) : NotificationShelfController {
- private var ambientState: AmbientState? = null
-
override val view: NotificationShelf
get() = shelf
init {
shelf.apply {
+ setRefactorFlagEnabled(featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR))
useRoundnessSourceTypes(featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES))
setSensitiveRevealAnimEndabled(featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM))
}
}
fun init() {
- viewBinder.bind(shelf)
+ NotificationShelfViewBinder.bind(viewModel, shelf)
ActivatableNotificationViewController(
shelf,
@@ -91,7 +87,6 @@
falsingCollector,
)
.init()
- shelf.setController(this)
val onAttachStateListener =
object : OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {
@@ -117,10 +112,7 @@
override val shelfIcons: NotificationIconContainer
get() = shelf.shelfIcons
- override fun canModifyColorOfNotifications(): Boolean {
- return (ambientState?.isShadeExpanded == true &&
- !(ambientState?.isOnKeyguard == true && keyguardBypassController.bypassEnabled))
- }
+ override fun canModifyColorOfNotifications(): Boolean = unsupported
override fun setOnActivatedListener(listener: ActivatableNotificationView.OnActivatedListener) {
shelf.setOnActivatedListener(listener)
@@ -128,25 +120,28 @@
override fun bind(
ambientState: AmbientState,
- notificationStackScrollLayoutController: NotificationStackScrollLayoutController
+ notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
) {
shelf.bind(ambientState, notificationStackScrollLayoutController)
- this.ambientState = ambientState
}
override fun setOnClickListener(listener: View.OnClickListener) {
shelf.setOnClickListener(listener)
}
+
+ private val unsupported: Nothing
+ get() = error("Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is enabled")
}
-@Module(includes = [PrivateShelfViewBinderModule::class]) object NotificationShelfViewBinderModule
-
-@Module
-private interface PrivateShelfViewBinderModule {
- @Binds fun bindImpl(impl: NotificationShelfViewBinderImpl): NotificationShelfViewBinder
-}
-
-@CentralSurfacesScope
-private class NotificationShelfViewBinderImpl @Inject constructor() : NotificationShelfViewBinder {
- override fun bind(shelf: NotificationShelf) {}
+/** Binds a [NotificationShelf] to its backend. */
+object NotificationShelfViewBinder {
+ fun bind(viewModel: NotificationShelfViewModel, shelf: NotificationShelf) {
+ shelf.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.canModifyColorOfNotifications
+ .onEach(shelf::setCanModifyColorOfNotifications)
+ .launchIn(this)
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
new file mode 100644
index 0000000..b84834a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 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.statusbar.notification.shelf.ui.viewmodel
+
+import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** ViewModel for [NotificationShelf]. */
+@CentralSurfacesScope
+class NotificationShelfViewModel
+@Inject
+constructor(
+ private val interactor: NotificationShelfInteractor,
+) {
+ /** Is the shelf allowed to modify the color of notifications in the host layout? */
+ val canModifyColorOfNotifications: Flow<Boolean>
+ get() = interactor.isShelfStatic.map { static -> !static }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java
deleted file mode 100644
index 076e5f1..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2014 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.statusbar.phone;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.WindowInsets;
-import android.widget.FrameLayout;
-
-/**
- * A view group which contains the preview of phone/camera and draws a black bar at the bottom as
- * the fake navigation bar.
- */
-public class KeyguardPreviewContainer extends FrameLayout {
-
- private Drawable mBlackBarDrawable = new Drawable() {
- @Override
- public void draw(Canvas canvas) {
- canvas.save();
- canvas.clipRect(0, getHeight() - getPaddingBottom(), getWidth(), getHeight());
- canvas.drawColor(Color.BLACK);
- canvas.restore();
- }
-
- @Override
- public void setAlpha(int alpha) {
- // noop
- }
-
- @Override
- public void setColorFilter(ColorFilter colorFilter) {
- // noop
- }
-
- @Override
- public int getOpacity() {
- return android.graphics.PixelFormat.OPAQUE;
- }
- };
-
- public KeyguardPreviewContainer(Context context, AttributeSet attrs) {
- super(context, attrs);
- setBackground(mBlackBarDrawable);
- }
-
- @Override
- public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- setPadding(0, 0, 0, insets.getStableInsetBottom());
- return super.onApplyWindowInsets(insets);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index cc2a0ba..5d4adda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -52,8 +52,7 @@
import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
-import com.android.systemui.statusbar.notification.shelf.view.NotificationShelfViewBinderModule;
-import com.android.systemui.statusbar.notification.shelf.view.NotificationShelfViewBinderWrapperControllerImpl;
+import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator;
@@ -87,8 +86,7 @@
import dagger.Provides;
import dagger.multibindings.IntoSet;
-@Module(subcomponents = StatusBarFragmentComponent.class,
- includes = { NotificationShelfViewBinderModule.class })
+@Module(subcomponents = StatusBarFragmentComponent.class)
public abstract class StatusBarViewModule {
public static final String SHADE_HEADER = "large_screen_shade_header";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 654ba04..1e63b2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -21,6 +21,8 @@
import static android.os.BatteryManager.EXTRA_HEALTH;
import static android.os.BatteryManager.EXTRA_PRESENT;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
+
import android.annotation.WorkerThread;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -169,7 +171,8 @@
@Override
public void setPowerSaveMode(boolean powerSave, View view) {
if (powerSave) mPowerSaverStartView.set(new WeakReference<>(view));
- BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true);
+ BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true,
+ SAVER_ENABLED_QS);
}
@Override
diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java
deleted file mode 100644
index e93e862..0000000
--- a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2023 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 androidx.core.animation;
-
-import android.os.Looper;
-import android.os.SystemClock;
-import android.util.AndroidRuntimeException;
-
-import androidx.annotation.NonNull;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * NOTE: this is a copy of the {@link androidx.core.animation.AnimatorTestRule} which attempts to
- * circumvent the problems with {@link androidx.core.animation.AnimationHandler} having a static
- * list of callbacks.
- *
- * TODO(b/275602127): remove this and use the original rule once we have the updated androidx code.
- */
-public final class AnimatorTestRule2 implements TestRule {
-
- class TestAnimationHandler extends AnimationHandler {
- TestAnimationHandler() {
- super(new TestProvider());
- }
-
- List<AnimationFrameCallback> animationCallbacks = new ArrayList<>();
-
- @Override
- void addAnimationFrameCallback(AnimationFrameCallback callback) {
- animationCallbacks.add(callback);
- callback.doAnimationFrame(getCurrentTime());
- }
-
- @Override
- public void removeCallback(AnimationFrameCallback callback) {
- int id = animationCallbacks.indexOf(callback);
- if (id >= 0) {
- animationCallbacks.set(id, null);
- }
- }
-
- void onAnimationFrame(long frameTime) {
- for (int i = 0; i < animationCallbacks.size(); i++) {
- final AnimationFrameCallback callback = animationCallbacks.get(i);
- if (callback == null) {
- continue;
- }
- callback.doAnimationFrame(frameTime);
- }
- }
-
- @Override
- void autoCancelBasedOn(ObjectAnimator objectAnimator) {
- for (int i = animationCallbacks.size() - 1; i >= 0; i--) {
- AnimationFrameCallback cb = animationCallbacks.get(i);
- if (cb == null) {
- continue;
- }
- if (objectAnimator.shouldAutoCancel(cb)) {
- ((Animator) animationCallbacks.get(i)).cancel();
- }
- }
- }
- }
-
- final TestAnimationHandler mTestHandler;
- final long mStartTime;
- private long mTotalTimeDelta = 0;
- private final Object mLock = new Object();
-
- public AnimatorTestRule2() {
- mStartTime = SystemClock.uptimeMillis();
- mTestHandler = new TestAnimationHandler();
- }
-
- @NonNull
- @Override
- public Statement apply(@NonNull final Statement base, @NonNull Description description) {
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- AnimationHandler.setTestHandler(mTestHandler);
- try {
- base.evaluate();
- } finally {
- AnimationHandler.setTestHandler(null);
- }
- }
- };
- }
-
- /**
- * Advances the animation clock by the given amount of delta in milliseconds. This call will
- * produce an animation frame to all the ongoing animations. This method needs to be
- * called on the same thread as {@link Animator#start()}.
- *
- * @param timeDelta the amount of milliseconds to advance
- */
- public void advanceTimeBy(long timeDelta) {
- if (Looper.myLooper() == null) {
- // Throw an exception
- throw new AndroidRuntimeException("AnimationTestRule#advanceTimeBy(long) may only be"
- + "called on Looper threads");
- }
- synchronized (mLock) {
- // Advance time & pulse a frame
- mTotalTimeDelta += timeDelta < 0 ? 0 : timeDelta;
- }
- // produce a frame
- mTestHandler.onAnimationFrame(getCurrentTime());
- }
-
-
- /**
- * Returns the current time in milliseconds tracked by AnimationHandler. Note that this is a
- * different time than the time tracked by {@link SystemClock} This method needs to be called on
- * the same thread as {@link Animator#start()}.
- */
- public long getCurrentTime() {
- if (Looper.myLooper() == null) {
- // Throw an exception
- throw new AndroidRuntimeException("AnimationTestRule#getCurrentTime() may only be"
- + "called on Looper threads");
- }
- synchronized (mLock) {
- return mStartTime + mTotalTimeDelta;
- }
- }
-
-
- private class TestProvider implements AnimationHandler.AnimationFrameCallbackProvider {
- TestProvider() {
- }
-
- @Override
- public void onNewCallbackAdded(AnimationHandler.AnimationFrameCallback callback) {
- callback.doAnimationFrame(getCurrentTime());
- }
-
- @Override
- public void postFrameCallback() {
- }
-
- @Override
- public void setFrameDelay(long delay) {
- }
-
- @Override
- public long getFrameDelay() {
- return 0;
- }
- }
-}
-
diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt
index bddd60b..e7738af 100644
--- a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt
+++ b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt
@@ -30,7 +30,7 @@
@RunWithLooper(setAsMainLooper = true)
class AnimatorTestRuleTest : SysuiTestCase() {
- @get:Rule val animatorTestRule = AnimatorTestRule2()
+ @get:Rule val animatorTestRule = AnimatorTestRule()
@Test
fun testA() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 2f627cb..b9f8dd94 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -48,8 +48,10 @@
import com.android.systemui.plugins.ClockAnimations;
import com.android.systemui.plugins.ClockController;
import com.android.systemui.plugins.ClockEvents;
+import com.android.systemui.plugins.ClockFaceConfig;
import com.android.systemui.plugins.ClockFaceController;
import com.android.systemui.plugins.ClockFaceEvents;
+import com.android.systemui.plugins.ClockTickRate;
import com.android.systemui.plugins.log.LogBuffer;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.clocks.AnimatableClockView;
@@ -185,6 +187,10 @@
when(mClockController.getAnimations()).thenReturn(mClockAnimations);
when(mClockRegistry.createCurrentClock()).thenReturn(mClockController);
when(mClockEventController.getClock()).thenReturn(mClockController);
+ when(mSmallClockController.getConfig())
+ .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false));
+ when(mLargeClockController.getConfig())
+ .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false));
mSliceView = new View(getContext());
when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView);
@@ -367,6 +373,28 @@
}
@Test
+ public void testChangeClockDateWeatherEnabled_SetsDateWeatherViewVisibility() {
+ ArgumentCaptor<ClockRegistry.ClockChangeListener> listenerArgumentCaptor =
+ ArgumentCaptor.forClass(ClockRegistry.ClockChangeListener.class);
+ when(mSmartspaceController.isEnabled()).thenReturn(true);
+ when(mSmartspaceController.isDateWeatherDecoupled()).thenReturn(true);
+ when(mSmartspaceController.isWeatherEnabled()).thenReturn(true);
+ mController.init();
+ mExecutor.runAllReady();
+ assertEquals(View.VISIBLE, mFakeDateView.getVisibility());
+
+ when(mSmallClockController.getConfig())
+ .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true));
+ when(mLargeClockController.getConfig())
+ .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true));
+ verify(mClockRegistry).registerClockChangeListener(listenerArgumentCaptor.capture());
+ listenerArgumentCaptor.getValue().onCurrentClockChanged();
+
+ mExecutor.runAllReady();
+ assertEquals(View.GONE, mFakeDateView.getVisibility());
+ }
+
+ @Test
public void testGetClock_nullClock_returnsNull() {
when(mClockEventController.getClock()).thenReturn(null);
assertNull(mController.getClock());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 6e002f5..2489e04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -59,11 +59,10 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.mockito.KotlinArgumentCaptor
+import com.android.systemui.util.mockito.captureMany
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.SystemClock
import com.google.common.truth.Truth.assertThat
-import java.io.PrintWriter
-import java.io.StringWriter
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -81,6 +80,7 @@
import org.mockito.ArgumentMatchers.eq
import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.isNull
import org.mockito.Mockito.mock
@@ -88,6 +88,8 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
+import java.io.PrintWriter
+import java.io.StringWriter
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -120,6 +122,7 @@
private lateinit var authStatus: FlowValue<AuthenticationStatus?>
private lateinit var detectStatus: FlowValue<DetectionStatus?>
private lateinit var authRunning: FlowValue<Boolean?>
+ private lateinit var bypassEnabled: FlowValue<Boolean?>
private lateinit var lockedOut: FlowValue<Boolean?>
private lateinit var canFaceAuthRun: FlowValue<Boolean?>
private lateinit var authenticated: FlowValue<Boolean?>
@@ -726,6 +729,23 @@
}
@Test
+ fun isBypassEnabledReflectsBypassControllerState() =
+ testScope.runTest {
+ initCollectors()
+ runCurrent()
+ val listeners = captureMany {
+ verify(bypassController, atLeastOnce())
+ .registerOnBypassStateChangedListener(capture())
+ }
+
+ listeners.forEach { it.onBypassStateChanged(true) }
+ assertThat(bypassEnabled()).isTrue()
+
+ listeners.forEach { it.onBypassStateChanged(false) }
+ assertThat(bypassEnabled()).isFalse()
+ }
+
+ @Test
fun detectDoesNotRunWhenNonStrongBiometricIsAllowed() =
testScope.runTest {
testGatingCheckForDetect {
@@ -844,6 +864,7 @@
lockedOut = collectLastValue(underTest.isLockedOut)
canFaceAuthRun = collectLastValue(underTest.canRunFaceAuth)
authenticated = collectLastValue(underTest.isAuthenticated)
+ bypassEnabled = collectLastValue(underTest.isBypassEnabled)
fakeUserRepository.setSelectedUserInfo(primaryUser)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 569f90b..4438b98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -614,9 +614,8 @@
public void onBiometricHelp_coEx_faceFailure() {
createController();
- // GIVEN unlocking with fingerprint is possible
- when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(anyInt()))
- .thenReturn(true);
+ // GIVEN unlocking with fingerprint is possible and allowed
+ fingerprintUnlockIsPossibleAndAllowed();
String message = "A message";
mController.setVisible(true);
@@ -641,9 +640,8 @@
public void onBiometricHelp_coEx_faceUnavailable() {
createController();
- // GIVEN unlocking with fingerprint is possible
- when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(anyInt()))
- .thenReturn(true);
+ // GIVEN unlocking with fingerprint is possible and allowed
+ fingerprintUnlockIsPossibleAndAllowed();
String message = "A message";
mController.setVisible(true);
@@ -664,6 +662,35 @@
mContext.getString(R.string.keyguard_suggest_fingerprint));
}
+
+ @Test
+ public void onBiometricHelp_coEx_faceUnavailable_fpNotAllowed() {
+ createController();
+
+ // GIVEN unlocking with fingerprint is possible but not allowed
+ setupFingerprintUnlockPossible(true);
+ when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed())
+ .thenReturn(false);
+
+ String message = "A message";
+ mController.setVisible(true);
+
+ // WHEN there's a face unavailable message
+ mController.getKeyguardCallback().onBiometricHelp(
+ BIOMETRIC_HELP_FACE_NOT_AVAILABLE,
+ message,
+ BiometricSourceType.FACE);
+
+ // THEN show sequential messages such as: 'face unlock unavailable' and
+ // 'try fingerprint instead'
+ verifyIndicationMessage(
+ INDICATION_TYPE_BIOMETRIC_MESSAGE,
+ message);
+ verifyIndicationMessage(
+ INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ mContext.getString(R.string.keyguard_unlock));
+ }
+
@Test
public void onBiometricHelp_coEx_fpFailure_faceAlreadyUnlocked() {
createController();
@@ -818,8 +845,7 @@
@Test
public void faceErrorTimeout_whenFingerprintEnrolled_doesNotShowMessage() {
createController();
- when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- getCurrentUser())).thenReturn(true);
+ fingerprintUnlockIsPossibleAndAllowed();
String message = "A message";
mController.setVisible(true);
@@ -832,9 +858,8 @@
public void sendFaceHelpMessages_fingerprintEnrolled() {
createController();
- // GIVEN fingerprint enrolled
- when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- getCurrentUser())).thenReturn(true);
+ // GIVEN unlocking with fingerprint is possible and allowed
+ fingerprintUnlockIsPossibleAndAllowed();
// WHEN help messages received that are allowed to show
final String helpString = "helpString";
@@ -859,9 +884,8 @@
public void doNotSendMostFaceHelpMessages_fingerprintEnrolled() {
createController();
- // GIVEN fingerprint enrolled
- when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- getCurrentUser())).thenReturn(true);
+ // GIVEN unlocking with fingerprint is possible and allowed
+ fingerprintUnlockIsPossibleAndAllowed();
// WHEN help messages received that aren't supposed to show
final String helpString = "helpString";
@@ -886,9 +910,8 @@
public void sendAllFaceHelpMessages_fingerprintNotEnrolled() {
createController();
- // GIVEN fingerprint NOT enrolled
- when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- getCurrentUser())).thenReturn(false);
+ // GIVEN fingerprint NOT possible
+ fingerprintUnlockIsNotPossible();
// WHEN help messages received
final Set<CharSequence> helpStrings = new HashSet<>();
@@ -917,9 +940,8 @@
public void sendTooDarkFaceHelpMessages_onTimeout_noFpEnrolled() {
createController();
- // GIVEN fingerprint NOT enrolled
- when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- getCurrentUser())).thenReturn(false);
+ // GIVEN fingerprint not possible
+ fingerprintUnlockIsNotPossible();
// WHEN help message received and deferred message is valid
final String helpString = "helpMsg";
@@ -948,9 +970,8 @@
public void sendTooDarkFaceHelpMessages_onTimeout_fingerprintEnrolled() {
createController();
- // GIVEN fingerprint enrolled
- when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- getCurrentUser())).thenReturn(true);
+ // GIVEN unlocking with fingerprint is possible and allowed
+ fingerprintUnlockIsPossibleAndAllowed();
// WHEN help message received and deferredMessage is valid
final String helpString = "helpMsg";
@@ -1500,7 +1521,7 @@
@Test
public void onBiometricError_faceLockedOutFirstTimeAndFpAllowed_showsTheFpFollowupMessage() {
createController();
- fingerprintUnlockIsPossible();
+ fingerprintUnlockIsPossibleAndAllowed();
onFaceLockoutError("first lockout");
verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
@@ -1559,7 +1580,7 @@
@Test
public void onBiometricError_faceLockedOutAgainAndFpAllowed_showsTheFpFollowupMessage() {
createController();
- fingerprintUnlockIsPossible();
+ fingerprintUnlockIsPossibleAndAllowed();
onFaceLockoutError("first lockout");
clearInvocations(mRotateTextViewController);
@@ -1668,7 +1689,7 @@
public void onBiometricError_screenIsTurningOn_faceLockedOutFpIsAvailable_showsMessage() {
createController();
screenIsTurningOn();
- fingerprintUnlockIsPossible();
+ fingerprintUnlockIsPossibleAndAllowed();
onFaceLockoutError("lockout error");
verifyNoMoreInteractions(mRotateTextViewController);
@@ -1746,8 +1767,9 @@
setupFingerprintUnlockPossible(false);
}
- private void fingerprintUnlockIsPossible() {
+ private void fingerprintUnlockIsPossibleAndAllowed() {
setupFingerprintUnlockPossible(true);
+ when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed()).thenReturn(true);
}
private void setupFingerprintUnlockPossible(boolean possible) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 7b59cc2..08a9f31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -22,7 +22,7 @@
import android.testing.TestableLooper.RunWithLooper
import android.view.View
import android.widget.FrameLayout
-import androidx.core.animation.AnimatorTestRule2
+import androidx.core.animation.AnimatorTestRule
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
@@ -70,7 +70,7 @@
private lateinit var systemStatusAnimationScheduler: SystemStatusAnimationScheduler
private val fakeFeatureFlags = FakeFeatureFlags()
- @get:Rule val animatorTestRule = AnimatorTestRule2()
+ @get:Rule val animatorTestRule = AnimatorTestRule()
@Before
fun setup() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
index be3b723..aff705f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
@@ -18,7 +18,7 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
-import androidx.core.animation.AnimatorTestRule2
+import androidx.core.animation.AnimatorTestRule
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
@@ -51,7 +51,7 @@
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
- @get:Rule val animatorTestRule = AnimatorTestRule2()
+ @get:Rule val animatorTestRule = AnimatorTestRule()
private val dumpManager: DumpManager = mock()
private val headsUpManager: HeadsUpManager = mock()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
new file mode 100644
index 0000000..14e5f9e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.shelf.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class NotificationShelfInteractorTest : SysuiTestCase() {
+
+ private val keyguardRepository = FakeKeyguardRepository()
+ private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository()
+ private val underTest =
+ NotificationShelfInteractor(keyguardRepository, deviceEntryFaceAuthRepository)
+
+ @Test
+ fun shelfIsNotStatic_whenKeyguardNotShowing() = runTest {
+ val shelfStatic by collectLastValue(underTest.isShelfStatic)
+
+ keyguardRepository.setKeyguardShowing(false)
+
+ assertThat(shelfStatic).isFalse()
+ }
+
+ @Test
+ fun shelfIsNotStatic_whenKeyguardShowingAndNotBypass() = runTest {
+ val shelfStatic by collectLastValue(underTest.isShelfStatic)
+
+ keyguardRepository.setKeyguardShowing(true)
+ deviceEntryFaceAuthRepository.isBypassEnabled.value = false
+
+ assertThat(shelfStatic).isFalse()
+ }
+
+ @Test
+ fun shelfIsStatic_whenBypass() = runTest {
+ val shelfStatic by collectLastValue(underTest.isShelfStatic)
+
+ keyguardRepository.setKeyguardShowing(true)
+ deviceEntryFaceAuthRepository.isBypassEnabled.value = true
+
+ assertThat(shelfStatic).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
new file mode 100644
index 0000000..6c5fb8b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.shelf.ui.viewmodel
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class NotificationShelfViewModelTest : SysuiTestCase() {
+
+ private val keyguardRepository = FakeKeyguardRepository()
+ private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository()
+ private val interactor =
+ NotificationShelfInteractor(keyguardRepository, deviceEntryFaceAuthRepository)
+ private val underTest = NotificationShelfViewModel(interactor)
+
+ @Test
+ fun canModifyColorOfNotifications_whenKeyguardNotShowing() = runTest {
+ val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
+
+ keyguardRepository.setKeyguardShowing(false)
+
+ assertThat(canModifyNotifColor).isTrue()
+ }
+
+ @Test
+ fun canModifyColorOfNotifications_whenKeyguardShowingAndNotBypass() = runTest {
+ val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
+
+ keyguardRepository.setKeyguardShowing(true)
+ deviceEntryFaceAuthRepository.isBypassEnabled.value = false
+
+ assertThat(canModifyNotifColor).isTrue()
+ }
+
+ @Test
+ fun cannotModifyColorOfNotifications_whenBypass() = runTest {
+ val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
+
+ keyguardRepository.setKeyguardShowing(true)
+ deviceEntryFaceAuthRepository.isBypassEnabled.value = true
+
+ assertThat(canModifyNotifColor).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index 1eee08c..91c88ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -21,6 +21,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
@@ -167,8 +168,10 @@
mBatteryController.setPowerSaveMode(false, mView);
StaticInOrder inOrder = inOrder(staticMockMarker(BatterySaverUtils.class));
- inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), true, true));
- inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), false, true));
+ inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), true, true,
+ SAVER_ENABLED_QS));
+ inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), false, true,
+ SAVER_ENABLED_QS));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 01e94ba..391c8ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -62,7 +62,7 @@
import android.window.WindowOnBackInvokedDispatcher;
import androidx.annotation.NonNull;
-import androidx.core.animation.AnimatorTestRule2;
+import androidx.core.animation.AnimatorTestRule;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
@@ -110,7 +110,7 @@
private final UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
@ClassRule
- public static AnimatorTestRule2 mAnimatorTestRule = new AnimatorTestRule2();
+ public static AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
@Before
public void setUp() throws Exception {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
new file mode 100644
index 0000000..c08ecd0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 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.keyguard.data.repository
+
+import com.android.keyguard.FaceAuthUiEvent
+import com.android.systemui.keyguard.shared.model.AuthenticationStatus
+import com.android.systemui.keyguard.shared.model.DetectionStatus
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+
+class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository {
+
+ override val isAuthenticated = MutableStateFlow(false)
+ override val canRunFaceAuth = MutableStateFlow(false)
+ private val _authenticationStatus = MutableStateFlow<AuthenticationStatus?>(null)
+ override val authenticationStatus: Flow<AuthenticationStatus> =
+ _authenticationStatus.filterNotNull()
+ fun setAuthenticationStatus(status: AuthenticationStatus) {
+ _authenticationStatus.value = status
+ }
+ private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null)
+ override val detectionStatus: Flow<DetectionStatus>
+ get() = _detectionStatus.filterNotNull()
+ fun setDetectionStatus(status: DetectionStatus) {
+ _detectionStatus.value = status
+ }
+ override val isLockedOut = MutableStateFlow(false)
+ private val _runningAuthRequest = MutableStateFlow<Pair<FaceAuthUiEvent, Boolean>?>(null)
+ val runningAuthRequest: StateFlow<Pair<FaceAuthUiEvent, Boolean>?> =
+ _runningAuthRequest.asStateFlow()
+ override val isAuthRunning = _runningAuthRequest.map { it != null }
+ override val isBypassEnabled = MutableStateFlow(false)
+
+ override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
+ _runningAuthRequest.value = uiEvent to fallbackToDetection
+ }
+
+ override fun cancel() {
+ _runningAuthRequest.value = null
+ }
+}
diff --git a/services/Android.bp b/services/Android.bp
index 6e6c553..b0a0e5e 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -112,6 +112,7 @@
":services.searchui-sources",
":services.selectiontoolbar-sources",
":services.smartspace-sources",
+ ":services.soundtrigger-sources",
":services.systemcaptions-sources",
":services.translation-sources",
":services.texttospeech-sources",
@@ -169,6 +170,7 @@
"services.searchui",
"services.selectiontoolbar",
"services.smartspace",
+ "services.soundtrigger",
"services.systemcaptions",
"services.translation",
"services.texttospeech",
diff --git a/services/core/Android.bp b/services/core/Android.bp
index c8caab9..cfdf3ac 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -174,7 +174,6 @@
"android.hardware.configstore-V1.1-java",
"android.hardware.ir-V1-java",
"android.hardware.rebootescrow-V1-java",
- "android.hardware.soundtrigger-V2.3-java",
"android.hardware.power.stats-V2-java",
"android.hardware.power-V4-java",
"android.hidl.manager-V1.2-java",
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java b/services/core/java/com/android/server/SoundTriggerInternal.java
similarity index 97%
rename from services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
rename to services/core/java/com/android/server/SoundTriggerInternal.java
index cc398d9..e6c1750 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
+++ b/services/core/java/com/android/server/SoundTriggerInternal.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.soundtrigger;
+package com.android.server;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -29,15 +29,13 @@
import android.media.permission.Identity;
import android.os.IBinder;
-import com.android.server.voiceinteraction.VoiceInteractionManagerService;
-
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.List;
/**
* Provides a local service for managing voice-related recoginition models. This is primarily used
- * by the {@link VoiceInteractionManagerService}.
+ * by the {@code VoiceInteractionManagerService}.
*/
public interface SoundTriggerInternal {
/**
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 7b618b1..e248007 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -26,6 +26,17 @@
import static android.app.ActivityManager.PROCESS_STATE_HEAVY_WEIGHT;
import static android.app.ActivityManager.PROCESS_STATE_RECEIVER;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_STOP_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UID_IDLE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE;
import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_DEPRECATED;
import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_DISABLED;
import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_OK;
@@ -117,6 +128,7 @@
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.app.ActivityManagerInternal.OomAdjReason;
import android.app.ActivityManagerInternal.ServiceNotificationPolicy;
import android.app.ActivityThread;
import android.app.AppGlobals;
@@ -1146,7 +1158,7 @@
} finally {
/* Will be a no-op if nothing pending */
mAm.updateOomAdjPendingTargetsLocked(
- OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+ OOM_ADJ_REASON_START_SERVICE);
}
} else {
unbindServiceLocked(connection);
@@ -1236,8 +1248,7 @@
/* ignore - local call */
} finally {
/* Will be a no-op if nothing pending */
- mAm.updateOomAdjPendingTargetsLocked(
- OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
}
} else { // Starting a service
try {
@@ -1311,7 +1322,7 @@
false /* packageFrozen */,
true /* enqueueOomAdj */);
/* Will be a no-op if nothing pending */
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
if (error != null) {
return new ComponentName("!!", error);
}
@@ -1496,7 +1507,7 @@
stopServiceLocked(service, true);
}
if (size > 0) {
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_UID_IDLE);
}
}
}
@@ -3296,7 +3307,7 @@
Slog.e(TAG_SERVICE, "Short FGS procstate demoted: " + sr);
- mAm.updateOomAdjLocked(sr.app, OomAdjuster.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT);
+ mAm.updateOomAdjLocked(sr.app, OOM_ADJ_REASON_SHORT_FGS_TIMEOUT);
}
}
@@ -3630,7 +3641,7 @@
needOomAdj = true;
if (bringUpServiceLocked(s, service.getFlags(), callerFg, false,
permissionsReviewRequired, packageFrozen, true) != null) {
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_BIND_SERVICE);
return 0;
}
}
@@ -3655,7 +3666,7 @@
mAm.enqueueOomAdjTargetLocked(s.app);
}
if (needOomAdj) {
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_BIND_SERVICE);
}
final int packageState = wasStopped
@@ -3787,7 +3798,8 @@
}
}
- serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false, false);
+ serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false, false,
+ OOM_ADJ_REASON_EXECUTING_SERVICE);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -3878,7 +3890,7 @@
}
}
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_UNBIND_SERVICE);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
@@ -3925,7 +3937,8 @@
}
}
- serviceDoneExecutingLocked(r, inDestroying, false, false);
+ serviceDoneExecutingLocked(r, inDestroying, false, false,
+ OOM_ADJ_REASON_UNBIND_SERVICE);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -4360,11 +4373,11 @@
/**
* Bump the given service record into executing state.
* @param oomAdjReason The caller requests it to perform the oomAdjUpdate not {@link
- * OomAdjuster#OOM_ADJ_REASON_NONE}.
+ * ActivityManagerInternal#OOM_ADJ_REASON_NONE}.
* @return {@code true} if it performed oomAdjUpdate.
*/
private boolean bumpServiceExecutingLocked(
- ServiceRecord r, boolean fg, String why, @OomAdjuster.OomAdjReason int oomAdjReason) {
+ ServiceRecord r, boolean fg, String why, @OomAdjReason int oomAdjReason) {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, ">>> EXECUTING "
+ why + " of " + r + " in app " + r.app);
else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING, ">>> EXECUTING "
@@ -4416,7 +4429,7 @@
}
}
boolean oomAdjusted = false;
- if (oomAdjReason != OomAdjuster.OOM_ADJ_REASON_NONE && r.app != null
+ if (oomAdjReason != OOM_ADJ_REASON_NONE && r.app != null
&& r.app.mState.getCurProcState() > ActivityManager.PROCESS_STATE_SERVICE) {
// Force an immediate oomAdjUpdate, so the client app could be in the correct process
// state before doing any service related transactions
@@ -4440,8 +4453,7 @@
+ " rebind=" + rebind);
if ((!i.requested || rebind) && i.apps.size() > 0) {
try {
- bumpServiceExecutingLocked(r, execInFg, "bind",
- OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE);
+ bumpServiceExecutingLocked(r, execInFg, "bind", OOM_ADJ_REASON_BIND_SERVICE);
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, "requestServiceBinding="
+ i.intent.getIntent() + ". bindSeq=" + mBindServiceSeqCounter);
@@ -4457,13 +4469,15 @@
// Keep the executeNesting count accurate.
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r, e);
final boolean inDestroying = mDestroyingServices.contains(r);
- serviceDoneExecutingLocked(r, inDestroying, inDestroying, false);
+ serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
+ OOM_ADJ_REASON_UNBIND_SERVICE);
throw e;
} catch (RemoteException e) {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r);
// Keep the executeNesting count accurate.
final boolean inDestroying = mDestroyingServices.contains(r);
- serviceDoneExecutingLocked(r, inDestroying, inDestroying, false);
+ serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
+ OOM_ADJ_REASON_UNBIND_SERVICE);
return false;
}
}
@@ -4841,7 +4855,7 @@
// Ignore, it's been logged and nothing upstack cares.
} finally {
/* Will be a no-op if nothing pending */
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
}
}
@@ -5193,13 +5207,14 @@
final ProcessServiceRecord psr = app.mServices;
final boolean newService = psr.startService(r);
- bumpServiceExecutingLocked(r, execInFg, "create", OomAdjuster.OOM_ADJ_REASON_NONE);
+ bumpServiceExecutingLocked(r, execInFg, "create",
+ OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */);
mAm.updateLruProcessLocked(app, false, null);
updateServiceForegroundLocked(psr, /* oomAdj= */ false);
// Force an immediate oomAdjUpdate, so the client app could be in the correct process state
// before doing any service related transactions
mAm.enqueueOomAdjTargetLocked(app);
- mAm.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+ mAm.updateOomAdjLocked(app, OOM_ADJ_REASON_START_SERVICE);
boolean created = false;
try {
@@ -5233,7 +5248,8 @@
if (!created) {
// Keep the executeNesting count accurate.
final boolean inDestroying = mDestroyingServices.contains(r);
- serviceDoneExecutingLocked(r, inDestroying, inDestroying, false);
+ serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
+ OOM_ADJ_REASON_STOP_SERVICE);
// Cleanup.
if (newService) {
@@ -5319,7 +5335,8 @@
mAm.grantImplicitAccess(r.userId, si.intent, si.callingId,
UserHandle.getAppId(r.appInfo.uid)
);
- bumpServiceExecutingLocked(r, execInFg, "start", OomAdjuster.OOM_ADJ_REASON_NONE);
+ bumpServiceExecutingLocked(r, execInFg, "start",
+ OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */);
if (r.fgRequired && !r.fgWaiting) {
if (!r.isForeground) {
if (DEBUG_BACKGROUND_CHECK) {
@@ -5345,7 +5362,7 @@
if (!oomAdjusted) {
mAm.enqueueOomAdjTargetLocked(r.app);
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
}
ParceledListSlice<ServiceStartArgs> slice = new ParceledListSlice<>(args);
slice.setInlineCountLimit(4);
@@ -5371,10 +5388,11 @@
// Keep nesting count correct
final boolean inDestroying = mDestroyingServices.contains(r);
for (int i = 0, size = args.size(); i < size; i++) {
- serviceDoneExecutingLocked(r, inDestroying, inDestroying, true);
+ serviceDoneExecutingLocked(r, inDestroying, inDestroying, true,
+ OOM_ADJ_REASON_STOP_SERVICE);
}
/* Will be a no-op if nothing pending */
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_STOP_SERVICE);
if (caughtException instanceof TransactionTooLargeException) {
throw (TransactionTooLargeException)caughtException;
}
@@ -5461,7 +5479,7 @@
if (ibr.hasBound) {
try {
oomAdjusted |= bumpServiceExecutingLocked(r, false, "bring down unbind",
- OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ OOM_ADJ_REASON_UNBIND_SERVICE);
ibr.hasBound = false;
ibr.requested = false;
r.app.getThread().scheduleUnbindService(r,
@@ -5615,7 +5633,7 @@
} else {
try {
oomAdjusted |= bumpServiceExecutingLocked(r, false, "destroy",
- oomAdjusted ? 0 : OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ oomAdjusted ? 0 : OOM_ADJ_REASON_STOP_SERVICE);
mDestroyingServices.add(r);
r.destroying = true;
r.app.getThread().scheduleStopService(r);
@@ -5637,7 +5655,7 @@
if (!oomAdjusted) {
mAm.enqueueOomAdjTargetLocked(r.app);
if (!enqueueOomAdj) {
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_STOP_SERVICE);
}
}
if (r.bindings.size() > 0) {
@@ -5762,8 +5780,7 @@
if (s.app != null && s.app.getThread() != null && b.intent.apps.size() == 0
&& b.intent.hasBound) {
try {
- bumpServiceExecutingLocked(s, false, "unbind",
- OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ bumpServiceExecutingLocked(s, false, "unbind", OOM_ADJ_REASON_UNBIND_SERVICE);
if (b.client != s.app && c.notHasFlag(Context.BIND_WAIVE_PRIORITY)
&& s.app.mState.getSetProcState() <= PROCESS_STATE_HEAVY_WEIGHT) {
// If this service's process is not already in the cached list,
@@ -5886,7 +5903,8 @@
}
}
final long origId = Binder.clearCallingIdentity();
- serviceDoneExecutingLocked(r, inDestroying, inDestroying, enqueueOomAdj);
+ serviceDoneExecutingLocked(r, inDestroying, inDestroying, enqueueOomAdj,
+ OOM_ADJ_REASON_EXECUTING_SERVICE);
Binder.restoreCallingIdentity(origId);
} else {
Slog.w(TAG, "Done executing unknown service from pid "
@@ -5905,11 +5923,11 @@
r.tracker.setStarted(false, memFactor, now);
}
}
- serviceDoneExecutingLocked(r, true, true, enqueueOomAdj);
+ serviceDoneExecutingLocked(r, true, true, enqueueOomAdj, OOM_ADJ_REASON_PROCESS_END);
}
private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
- boolean finishing, boolean enqueueOomAdj) {
+ boolean finishing, boolean enqueueOomAdj, @OomAdjReason int oomAdjReason) {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "<<< DONE EXECUTING " + r
+ ": nesting=" + r.executeNesting
+ ", inDestroying=" + inDestroying + ", app=" + r.app);
@@ -5945,7 +5963,7 @@
if (enqueueOomAdj) {
mAm.enqueueOomAdjTargetLocked(r.app);
} else {
- mAm.updateOomAdjLocked(r.app, OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mAm.updateOomAdjLocked(r.app, oomAdjReason);
}
}
r.executeFg = false;
@@ -6015,7 +6033,7 @@
bringDownServiceLocked(sr, true);
}
/* Will be a no-op if nothing pending */
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
}
} catch (RemoteException e) {
Slog.w(TAG, "Exception in new application when starting service "
@@ -6075,7 +6093,7 @@
}
}
if (needOomAdj) {
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_PROCESS_END);
}
}
@@ -6146,7 +6164,7 @@
bringDownServiceLocked(mTmpCollectionResults.get(i), true);
}
if (size > 0) {
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_COMPONENT_DISABLED);
}
if (fullStop && !mTmpCollectionResults.isEmpty()) {
// if we're tearing down the app's entire service state, account for possible
@@ -6273,7 +6291,7 @@
}
}
if (needOomAdj) {
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_REMOVE_TASK);
}
}
@@ -6444,7 +6462,7 @@
}
}
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_STOP_SERVICE);
if (!allowRestart) {
psr.stopAllServices();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 9752ade..b32f8c9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -44,6 +44,14 @@
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED;
import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.OP_NONE;
import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT;
@@ -199,6 +207,7 @@
import android.app.ActivityManagerInternal.BroadcastEventListener;
import android.app.ActivityManagerInternal.ForegroundServiceStateListener;
import android.app.ActivityManagerInternal.MediaProjectionTokenEvent;
+import android.app.ActivityManagerInternal.OomAdjReason;
import android.app.ActivityTaskManager.RootTaskInfo;
import android.app.ActivityThread;
import android.app.AnrController;
@@ -368,7 +377,6 @@
import android.util.IndentingPrintWriter;
import android.util.IntArray;
import android.util.Log;
-import android.util.LogWriter;
import android.util.Pair;
import android.util.PrintWriterPrinter;
import android.util.Slog;
@@ -1968,7 +1976,7 @@
app.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_SYSTEM);
addPidLocked(app);
updateLruProcessLocked(app, false, null);
- updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdjLocked(OOM_ADJ_REASON_SYSTEM_INIT);
}
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException(
@@ -2502,7 +2510,7 @@
// bind background threads to little cores
// this is expected to fail inside of framework tests because apps can't touch cpusets directly
// make sure we've already adjusted system_server's internal view of itself first
- updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdjLocked(OOM_ADJ_REASON_SYSTEM_INIT);
try {
Process.setThreadGroupAndCpuset(BackgroundThread.get().getThreadId(),
Process.THREAD_GROUP_SYSTEM);
@@ -3387,7 +3395,7 @@
handleAppDiedLocked(app, pid, false, true, fromBinderDied);
if (doOomAdj) {
- updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_PROCESS_END);
+ updateOomAdjLocked(OOM_ADJ_REASON_PROCESS_END);
}
if (doLowMem) {
mAppProfiler.doLowMemReportIfNeededLocked(app);
@@ -4843,7 +4851,7 @@
}
if (!didSomething) {
- updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
+ updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN);
checkTime(startTime, "finishAttachApplicationInner: after updateOomAdjLocked");
}
@@ -5485,7 +5493,7 @@
"setProcessLimit()");
synchronized (this) {
mConstants.setOverrideMaxCachedProcesses(max);
- trimApplicationsLocked(true, OomAdjuster.OOM_ADJ_REASON_PROCESS_END);
+ trimApplicationsLocked(true, OOM_ADJ_REASON_PROCESS_END);
}
}
@@ -5513,7 +5521,7 @@
pr.mState.setForcingToImportant(null);
clearProcessForegroundLocked(pr);
}
- updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+ updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
}
}
@@ -5560,7 +5568,7 @@
}
if (changed) {
- updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+ updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
}
}
}
@@ -6869,7 +6877,7 @@
new HostingRecord(HostingRecord.HOSTING_TYPE_ADDED_APPLICATION,
customProcess != null ? customProcess : info.processName));
updateLruProcessLocked(app, false, null);
- updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
+ updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN);
}
// Report usage as process is persistent and being started.
@@ -6986,7 +6994,7 @@
mOomAdjProfiler.onWakefulnessChanged(wakefulness);
mOomAdjuster.onWakefulnessChanged(wakefulness);
- updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+ updateOomAdjLocked(OOM_ADJ_REASON_UI_VISIBILITY);
}
}
}
@@ -7748,7 +7756,7 @@
}
}
if (changed) {
- updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+ updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
}
}
} finally {
@@ -8728,7 +8736,9 @@
// 'recoverable' is that the app doesn't crash). Normally, for nonrecoreable native crashes,
// debuggerd will terminate the process, but there's a backup where ActivityManager will
// also kill it. Avoid that.
- if (!recoverable) {
+ if (recoverable) {
+ mAppErrors.sendRecoverableCrashToAppExitInfo(r, crashInfo);
+ } else {
mAppErrors.crashApplication(r, crashInfo);
}
}
@@ -9508,7 +9518,7 @@
mAppProfiler.setMemFactorOverrideLocked(level);
// Kick off an oom adj update since we forced a mem factor update.
- updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdjLocked(OOM_ADJ_REASON_SHELL);
}
}
@@ -13408,7 +13418,7 @@
proc.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_BACKUP);
// Try not to kill the process during backup
- updateOomAdjLocked(proc, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdjLocked(proc, OOM_ADJ_REASON_BACKUP);
// If the process is already attached, schedule the creation of the backup agent now.
// If it is not yet live, this will be done when it attaches to the framework.
@@ -13532,7 +13542,7 @@
// Not backing this app up any more; reset its OOM adjustment
final ProcessRecord proc = backupTarget.app;
- updateOomAdjLocked(proc, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdjLocked(proc, OOM_ADJ_REASON_BACKUP);
proc.setInFullBackup(false);
proc.mProfile.clearHostingComponentType(HOSTING_COMPONENT_TYPE_BACKUP);
@@ -13923,7 +13933,7 @@
// If we actually concluded any broadcasts, we might now be able
// to trim the recipients' apps from our working set
if (doTrim) {
- trimApplicationsLocked(false, OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER);
+ trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
return;
}
}
@@ -15185,7 +15195,7 @@
queue.finishReceiverLocked(callerApp, resultCode,
resultData, resultExtras, resultAbort, true);
// updateOomAdjLocked() will be done here
- trimApplicationsLocked(false, OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER);
+ trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
}
} finally {
@@ -16130,7 +16140,7 @@
item.foregroundServiceTypes = fgServiceTypes;
}
if (oomAdj) {
- updateOomAdjLocked(proc, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+ updateOomAdjLocked(proc, OOM_ADJ_REASON_UI_VISIBILITY);
}
}
@@ -16196,7 +16206,7 @@
* {@link #enqueueOomAdjTargetLocked}.
*/
@GuardedBy("this")
- void updateOomAdjPendingTargetsLocked(@OomAdjuster.OomAdjReason int oomAdjReason) {
+ void updateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
mOomAdjuster.updateOomAdjPendingTargetsLocked(oomAdjReason);
}
@@ -16215,7 +16225,7 @@
}
@GuardedBy("this")
- final void updateOomAdjLocked(@OomAdjuster.OomAdjReason int oomAdjReason) {
+ final void updateOomAdjLocked(@OomAdjReason int oomAdjReason) {
mOomAdjuster.updateOomAdjLocked(oomAdjReason);
}
@@ -16227,8 +16237,7 @@
* @return whether updateOomAdjLocked(app) was successful.
*/
@GuardedBy("this")
- final boolean updateOomAdjLocked(
- ProcessRecord app, @OomAdjuster.OomAdjReason int oomAdjReason) {
+ final boolean updateOomAdjLocked(ProcessRecord app, @OomAdjReason int oomAdjReason) {
return mOomAdjuster.updateOomAdjLocked(app, oomAdjReason);
}
@@ -16461,16 +16470,14 @@
mOomAdjuster.setUidTempAllowlistStateLSP(uid, onAllowlist);
}
- private void trimApplications(
- boolean forceFullOomAdj, @OomAdjuster.OomAdjReason int oomAdjReason) {
+ private void trimApplications(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) {
synchronized (this) {
trimApplicationsLocked(forceFullOomAdj, oomAdjReason);
}
}
@GuardedBy("this")
- private void trimApplicationsLocked(
- boolean forceFullOomAdj, @OomAdjuster.OomAdjReason int oomAdjReason) {
+ private void trimApplicationsLocked(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) {
// First remove any unused application processes whose package
// has been removed.
boolean didSomething = false;
@@ -17442,7 +17449,7 @@
}
pr.mState.setHasOverlayUi(hasOverlayUi);
//Slog.i(TAG, "Setting hasOverlayUi=" + pr.hasOverlayUi + " for pid=" + pid);
- updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+ updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
}
}
@@ -17577,7 +17584,7 @@
@Override
public void trimApplications() {
- ActivityManagerService.this.trimApplications(true, OomAdjuster.OOM_ADJ_REASON_ACTIVITY);
+ ActivityManagerService.this.trimApplications(true, OOM_ADJ_REASON_ACTIVITY);
}
public void killProcessesForRemovedTask(ArrayList<Object> procsToKill) {
@@ -17626,9 +17633,9 @@
}
@Override
- public void updateOomAdj() {
+ public void updateOomAdj(@OomAdjReason int oomAdjReason) {
synchronized (ActivityManagerService.this) {
- ActivityManagerService.this.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ ActivityManagerService.this.updateOomAdjLocked(oomAdjReason);
}
}
@@ -18288,8 +18295,7 @@
// sends to the activity. After this race issue between WM/ATMS and AMS is solved, this
// workaround can be removed. (b/213288355)
if (isNewPending) {
- mOomAdjuster.mCachedAppOptimizer.unfreezeProcess(pid,
- OomAdjuster.OOM_ADJ_REASON_ACTIVITY);
+ mOomAdjuster.mCachedAppOptimizer.unfreezeProcess(pid, OOM_ADJ_REASON_ACTIVITY);
}
// We need to update the network rules for the app coming to the top state so that
// it can access network when the device or the app is in a restricted state
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index c343ec2..061bcd7 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -555,6 +555,15 @@
}
}
+ void sendRecoverableCrashToAppExitInfo(
+ ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) {
+ if (r == null || crashInfo == null
+ || !"Native crash".equals(crashInfo.exceptionClassName)) return;
+ synchronized (mService) {
+ mService.mProcessList.noteAppRecoverableCrash(r);
+ }
+ }
+
/**
* Bring up the "unexpected error" dialog box for a crashing app.
* Deal with edge cases (intercepts from instrumented applications,
diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java
index 4443636..4c0dd11 100644
--- a/services/core/java/com/android/server/am/AppExitInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java
@@ -308,6 +308,16 @@
mKillHandler.obtainMessage(KillHandler.MSG_APP_KILL, raw).sendToTarget();
}
+ void scheduleNoteAppRecoverableCrash(final ProcessRecord app) {
+ if (!mAppExitInfoLoaded.get() || app == null || app.info == null) return;
+
+ ApplicationExitInfo raw = obtainRawRecord(app, System.currentTimeMillis());
+ raw.setReason(ApplicationExitInfo.REASON_CRASH_NATIVE);
+ raw.setSubReason(ApplicationExitInfo.SUBREASON_UNKNOWN);
+ raw.setDescription("recoverable_crash");
+ mKillHandler.obtainMessage(KillHandler.MSG_APP_RECOVERABLE_CRASH, raw).sendToTarget();
+ }
+
void scheduleNoteAppKill(final int pid, final int uid, final @Reason int reason,
final @SubReason int subReason, final String msg) {
if (!mAppExitInfoLoaded.get()) {
@@ -421,8 +431,24 @@
scheduleLogToStatsdLocked(info, true);
}
+ /**
+ * Make note when ActivityManagerService gets a recoverable native crash, as the process isn't
+ * being killed but the crash should still be added to AppExitInfo. Also, because we're not
+ * crashing, don't log out to statsd.
+ */
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ void handleNoteAppRecoverableCrashLocked(final ApplicationExitInfo raw) {
+ addExitInfoLocked(raw, /* recoverable */ true);
+ }
+
@GuardedBy("mLock")
private ApplicationExitInfo addExitInfoLocked(ApplicationExitInfo raw) {
+ return addExitInfoLocked(raw, /* recoverable */ false);
+ }
+
+ @GuardedBy("mLock")
+ private ApplicationExitInfo addExitInfoLocked(ApplicationExitInfo raw, boolean recoverable) {
if (!mAppExitInfoLoaded.get()) {
Slog.w(TAG, "Skipping saving the exit info due to ongoing loading from storage");
return null;
@@ -438,7 +464,7 @@
}
}
for (int i = 0; i < packages.length; i++) {
- addExitInfoInnerLocked(packages[i], uid, info);
+ addExitInfoInnerLocked(packages[i], uid, info, recoverable);
}
schedulePersistProcessExitInfo(false);
@@ -845,7 +871,8 @@
}
@GuardedBy("mLock")
- private void addExitInfoInnerLocked(String packageName, int uid, ApplicationExitInfo info) {
+ private void addExitInfoInnerLocked(String packageName, int uid, ApplicationExitInfo info,
+ boolean recoverable) {
AppExitInfoContainer container = mData.get(packageName, uid);
if (container == null) {
container = new AppExitInfoContainer(mAppExitInfoHistoryListSize);
@@ -859,7 +886,11 @@
}
mData.put(packageName, uid, container);
}
- container.addExitInfoLocked(info);
+ if (recoverable) {
+ container.addRecoverableCrashLocked(info);
+ } else {
+ container.addExitInfoLocked(info);
+ }
}
@GuardedBy("mLock")
@@ -1284,38 +1315,40 @@
* A container class of {@link android.app.ApplicationExitInfo}
*/
final class AppExitInfoContainer {
- private SparseArray<ApplicationExitInfo> mInfos; // index is pid
+ private SparseArray<ApplicationExitInfo> mInfos; // index is a pid
+ private SparseArray<ApplicationExitInfo> mRecoverableCrashes; // index is a pid
private int mMaxCapacity;
private int mUid; // Application uid, not isolated uid.
AppExitInfoContainer(final int maxCapacity) {
mInfos = new SparseArray<ApplicationExitInfo>();
+ mRecoverableCrashes = new SparseArray<ApplicationExitInfo>();
mMaxCapacity = maxCapacity;
}
@GuardedBy("mLock")
- void getExitInfoLocked(final int filterPid, final int maxNum,
- ArrayList<ApplicationExitInfo> results) {
+ void getInfosLocked(SparseArray<ApplicationExitInfo> map, final int filterPid,
+ final int maxNum, ArrayList<ApplicationExitInfo> results) {
if (filterPid > 0) {
- ApplicationExitInfo r = mInfos.get(filterPid);
+ ApplicationExitInfo r = map.get(filterPid);
if (r != null) {
results.add(r);
}
} else {
- final int numRep = mInfos.size();
+ final int numRep = map.size();
if (maxNum <= 0 || numRep <= maxNum) {
// Return all records.
for (int i = 0; i < numRep; i++) {
- results.add(mInfos.valueAt(i));
+ results.add(map.valueAt(i));
}
Collections.sort(results,
(a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp()));
} else {
if (maxNum == 1) {
// Most of the caller might be only interested with the most recent one
- ApplicationExitInfo r = mInfos.valueAt(0);
+ ApplicationExitInfo r = map.valueAt(0);
for (int i = 1; i < numRep; i++) {
- ApplicationExitInfo t = mInfos.valueAt(i);
+ ApplicationExitInfo t = map.valueAt(i);
if (r.getTimestamp() < t.getTimestamp()) {
r = t;
}
@@ -1326,7 +1359,7 @@
ArrayList<ApplicationExitInfo> list = mTmpInfoList2;
list.clear();
for (int i = 0; i < numRep; i++) {
- list.add(mInfos.valueAt(i));
+ list.add(map.valueAt(i));
}
Collections.sort(list,
(a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp()));
@@ -1340,24 +1373,30 @@
}
@GuardedBy("mLock")
- void addExitInfoLocked(ApplicationExitInfo info) {
+ void getExitInfoLocked(final int filterPid, final int maxNum,
+ ArrayList<ApplicationExitInfo> results) {
+ getInfosLocked(mInfos, filterPid, maxNum, results);
+ }
+
+ @GuardedBy("mLock")
+ void addInfoLocked(SparseArray<ApplicationExitInfo> map, ApplicationExitInfo info) {
int size;
- if ((size = mInfos.size()) >= mMaxCapacity) {
+ if ((size = map.size()) >= mMaxCapacity) {
int oldestIndex = -1;
long oldestTimeStamp = Long.MAX_VALUE;
for (int i = 0; i < size; i++) {
- ApplicationExitInfo r = mInfos.valueAt(i);
+ ApplicationExitInfo r = map.valueAt(i);
if (r.getTimestamp() < oldestTimeStamp) {
oldestTimeStamp = r.getTimestamp();
oldestIndex = i;
}
}
if (oldestIndex >= 0) {
- final File traceFile = mInfos.valueAt(oldestIndex).getTraceFile();
+ final File traceFile = map.valueAt(oldestIndex).getTraceFile();
if (traceFile != null) {
traceFile.delete();
}
- mInfos.removeAt(oldestIndex);
+ map.removeAt(oldestIndex);
}
}
// Claim the state information if there is any
@@ -1367,7 +1406,17 @@
mActiveAppStateSummary, uid, pid));
info.setTraceFile(findAndRemoveFromSparse2dArray(mActiveAppTraces, uid, pid));
info.setAppTraceRetriever(mAppTraceRetriever);
- mInfos.append(pid, info);
+ map.append(pid, info);
+ }
+
+ @GuardedBy("mLock")
+ void addExitInfoLocked(ApplicationExitInfo info) {
+ addInfoLocked(mInfos, info);
+ }
+
+ @GuardedBy("mLock")
+ void addRecoverableCrashLocked(ApplicationExitInfo info) {
+ addInfoLocked(mRecoverableCrashes, info);
}
@GuardedBy("mLock")
@@ -1382,9 +1431,9 @@
}
@GuardedBy("mLock")
- void destroyLocked() {
- for (int i = mInfos.size() - 1; i >= 0; i--) {
- ApplicationExitInfo ai = mInfos.valueAt(i);
+ void destroyLocked(SparseArray<ApplicationExitInfo> map) {
+ for (int i = map.size() - 1; i >= 0; i--) {
+ ApplicationExitInfo ai = map.valueAt(i);
final File traceFile = ai.getTraceFile();
if (traceFile != null) {
traceFile.delete();
@@ -1395,24 +1444,37 @@
}
@GuardedBy("mLock")
+ void destroyLocked() {
+ destroyLocked(mInfos);
+ destroyLocked(mRecoverableCrashes);
+ }
+
+ @GuardedBy("mLock")
void forEachRecordLocked(final BiFunction<Integer, ApplicationExitInfo, Integer> callback) {
- if (callback != null) {
- for (int i = mInfos.size() - 1; i >= 0; i--) {
- switch (callback.apply(mInfos.keyAt(i), mInfos.valueAt(i))) {
- case FOREACH_ACTION_REMOVE_ITEM:
- final File traceFile = mInfos.valueAt(i).getTraceFile();
- if (traceFile != null) {
- traceFile.delete();
- }
- mInfos.removeAt(i);
- break;
- case FOREACH_ACTION_STOP_ITERATION:
- i = 0;
- break;
- case FOREACH_ACTION_NONE:
- default:
- break;
- }
+ if (callback == null) return;
+ for (int i = mInfos.size() - 1; i >= 0; i--) {
+ switch (callback.apply(mInfos.keyAt(i), mInfos.valueAt(i))) {
+ case FOREACH_ACTION_STOP_ITERATION: return;
+ case FOREACH_ACTION_REMOVE_ITEM:
+ final File traceFile = mInfos.valueAt(i).getTraceFile();
+ if (traceFile != null) {
+ traceFile.delete();
+ }
+ mInfos.removeAt(i);
+ break;
+ }
+ }
+ for (int i = mRecoverableCrashes.size() - 1; i >= 0; i--) {
+ switch (callback.apply(
+ mRecoverableCrashes.keyAt(i), mRecoverableCrashes.valueAt(i))) {
+ case FOREACH_ACTION_STOP_ITERATION: return;
+ case FOREACH_ACTION_REMOVE_ITEM:
+ final File traceFile = mRecoverableCrashes.valueAt(i).getTraceFile();
+ if (traceFile != null) {
+ traceFile.delete();
+ }
+ mRecoverableCrashes.removeAt(i);
+ break;
}
}
}
@@ -1423,6 +1485,9 @@
for (int i = mInfos.size() - 1; i >= 0; i--) {
list.add(mInfos.valueAt(i));
}
+ for (int i = mRecoverableCrashes.size() - 1; i >= 0; i--) {
+ list.add(mRecoverableCrashes.valueAt(i));
+ }
Collections.sort(list, (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp()));
int size = list.size();
for (int i = 0; i < size; i++) {
@@ -1434,10 +1499,13 @@
void writeToProto(ProtoOutputStream proto, long fieldId) {
long token = proto.start(fieldId);
proto.write(AppsExitInfoProto.Package.User.UID, mUid);
- int size = mInfos.size();
- for (int i = 0; i < size; i++) {
+ for (int i = 0; i < mInfos.size(); i++) {
mInfos.valueAt(i).writeToProto(proto, AppsExitInfoProto.Package.User.APP_EXIT_INFO);
}
+ for (int i = 0; i < mRecoverableCrashes.size(); i++) {
+ mRecoverableCrashes.valueAt(i).writeToProto(
+ proto, AppsExitInfoProto.Package.User.APP_RECOVERABLE_CRASH);
+ }
proto.end(token);
}
@@ -1448,14 +1516,23 @@
next != ProtoInputStream.NO_MORE_FIELDS;
next = proto.nextField()) {
switch (next) {
- case (int) AppsExitInfoProto.Package.User.UID:
+ case (int) AppsExitInfoProto.Package.User.UID: {
mUid = proto.readInt(AppsExitInfoProto.Package.User.UID);
break;
- case (int) AppsExitInfoProto.Package.User.APP_EXIT_INFO:
+ }
+ case (int) AppsExitInfoProto.Package.User.APP_EXIT_INFO: {
ApplicationExitInfo info = new ApplicationExitInfo();
info.readFromProto(proto, AppsExitInfoProto.Package.User.APP_EXIT_INFO);
mInfos.put(info.getPid(), info);
break;
+ }
+ case (int) AppsExitInfoProto.Package.User.APP_RECOVERABLE_CRASH: {
+ ApplicationExitInfo info = new ApplicationExitInfo();
+ info.readFromProto(
+ proto, AppsExitInfoProto.Package.User.APP_RECOVERABLE_CRASH);
+ mRecoverableCrashes.put(info.getPid(), info);
+ break;
+ }
}
}
proto.end(token);
@@ -1472,6 +1549,11 @@
list.add(mInfos.valueAt(i));
}
}
+ for (int i = mRecoverableCrashes.size() - 1; i >= 0; i--) {
+ if (filterPid == 0 || filterPid == mRecoverableCrashes.keyAt(i)) {
+ list.add(mRecoverableCrashes.valueAt(i));
+ }
+ }
return list;
}
}
@@ -1610,6 +1692,7 @@
static final int MSG_PROC_DIED = 4103;
static final int MSG_APP_KILL = 4104;
static final int MSG_STATSD_LOG = 4105;
+ static final int MSG_APP_RECOVERABLE_CRASH = 4106;
KillHandler(Looper looper) {
super(looper, null, true);
@@ -1648,6 +1731,14 @@
}
}
break;
+ case MSG_APP_RECOVERABLE_CRASH: {
+ ApplicationExitInfo raw = (ApplicationExitInfo) msg.obj;
+ synchronized (mLock) {
+ handleNoteAppRecoverableCrashLocked(raw);
+ }
+ recycleRawRecord(raw);
+ }
+ break;
default:
super.handleMessage(msg);
}
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index bfc8251..9e61ce4 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -18,7 +18,6 @@
import static com.android.internal.util.Preconditions.checkState;
import static com.android.server.am.BroadcastRecord.deliveryStateToString;
-import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal;
import static com.android.server.am.BroadcastRecord.isReceiverEquals;
import android.annotation.IntDef;
@@ -709,7 +708,7 @@
|| consecutiveHighPriorityCount >= maxHighPriorityDispatchLimit);
final boolean isLPQueueEligible = shouldConsiderLPQueue
&& nextLPRecord.enqueueTime <= nextHPRecord.enqueueTime
- && !blockedOnOrderedDispatch(nextLPRecord, nextLPRecordIndex);
+ && !nextLPRecord.isBlocked(nextLPRecordIndex);
return isLPQueueEligible ? lowPriorityQueue : highPriorityQueue;
}
@@ -912,39 +911,20 @@
}
}
- private boolean blockedOnOrderedDispatch(BroadcastRecord r, int index) {
- final int blockedUntilTerminalCount = r.blockedUntilTerminalCount[index];
-
- int existingDeferredCount = 0;
- if (r.deferUntilActive) {
- for (int i = 0; i < index; i++) {
- if (r.deferredUntilActive[i]) existingDeferredCount++;
- }
- }
-
- // We might be blocked waiting for other receivers to finish,
- // typically for an ordered broadcast or priority traunches
- if ((r.terminalCount + existingDeferredCount) < blockedUntilTerminalCount
- && !isDeliveryStateTerminal(r.getDeliveryState(index))) {
- return true;
- }
- return false;
- }
-
/**
* Update {@link #getRunnableAt()} if it's currently invalidated.
*/
private void updateRunnableAt() {
- final SomeArgs next = peekNextBroadcast();
+ if (!mRunnableAtInvalidated) return;
mRunnableAtInvalidated = false;
+
+ final SomeArgs next = peekNextBroadcast();
if (next != null) {
final BroadcastRecord r = (BroadcastRecord) next.arg1;
final int index = next.argi1;
final long runnableAt = r.enqueueTime;
- // If we're specifically queued behind other ordered dispatch activity,
- // we aren't runnable yet
- if (blockedOnOrderedDispatch(r, index)) {
+ if (r.isBlocked(index)) {
mRunnableAt = Long.MAX_VALUE;
mRunnableAtReason = REASON_BLOCKED;
return;
@@ -1262,12 +1242,12 @@
pw.print(info.activityInfo.name);
}
pw.println();
- final int blockedUntilTerminalCount = record.blockedUntilTerminalCount[recordIndex];
- if (blockedUntilTerminalCount != -1) {
+ final int blockedUntilBeyondCount = record.blockedUntilBeyondCount[recordIndex];
+ if (blockedUntilBeyondCount != -1) {
pw.print(" blocked until ");
- pw.print(blockedUntilTerminalCount);
+ pw.print(blockedUntilBeyondCount);
pw.print(", currently at ");
- pw.print(record.terminalCount);
+ pw.print(record.beyondCount);
pw.print(" of ");
pw.println(record.receivers.size());
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index bd36c3f..5a4d315 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -17,6 +17,7 @@
package com.android.server.am;
import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
import static android.text.TextUtils.formatSimple;
@@ -37,7 +38,6 @@
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
-import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index a4bdf61..e532c15 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
@@ -38,7 +39,6 @@
import static com.android.server.am.BroadcastRecord.getReceiverProcessName;
import static com.android.server.am.BroadcastRecord.getReceiverUid;
import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal;
-import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1008,6 +1008,7 @@
}
final BroadcastRecord r = queue.getActive();
+ final int index = queue.getActiveIndex();
if (r.ordered) {
r.resultCode = resultCode;
r.resultData = resultData;
@@ -1015,18 +1016,24 @@
if (!r.isNoAbort()) {
r.resultAbort = resultAbort;
}
+ }
- // When the caller aborted an ordered broadcast, we mark all
- // remaining receivers as skipped
- if (r.resultAbort) {
- for (int i = r.terminalCount + 1; i < r.receivers.size(); i++) {
- setDeliveryState(null, null, r, i, r.receivers.get(i),
- BroadcastRecord.DELIVERY_SKIPPED, "resultAbort");
- }
+ // To ensure that "beyond" high-water marks are updated in a monotonic
+ // way, we finish this receiver before possibly skipping any remaining
+ // aborted receivers
+ final boolean res = finishReceiverActiveLocked(queue,
+ BroadcastRecord.DELIVERY_DELIVERED, "remote app");
+
+ // When the caller aborted an ordered broadcast, we mark all
+ // remaining receivers as skipped
+ if (r.resultAbort) {
+ for (int i = index + 1; i < r.receivers.size(); i++) {
+ setDeliveryState(null, null, r, i, r.receivers.get(i),
+ BroadcastRecord.DELIVERY_SKIPPED, "resultAbort");
}
}
- return finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_DELIVERED, "remote app");
+ return res;
}
/**
@@ -1108,21 +1115,10 @@
@NonNull Object receiver, @DeliveryState int newDeliveryState,
@NonNull String reason) {
final int cookie = traceBegin("setDeliveryState");
- final int oldDeliveryState = getDeliveryState(r, index);
- boolean checkFinished = false;
- // Only apply state when we haven't already reached a terminal state;
- // this is how we ignore racing timeout messages
- if (!isDeliveryStateTerminal(oldDeliveryState)) {
- r.setDeliveryState(index, newDeliveryState, reason);
- if (oldDeliveryState == BroadcastRecord.DELIVERY_DEFERRED) {
- r.deferredCount--;
- } else if (newDeliveryState == BroadcastRecord.DELIVERY_DEFERRED) {
- // If we're deferring a broadcast, maybe that's enough to unblock the final callback
- r.deferredCount++;
- checkFinished = true;
- }
- }
+ // Remember the old state and apply the new state
+ final int oldDeliveryState = getDeliveryState(r, index);
+ final boolean beyondCountChanged = r.setDeliveryState(index, newDeliveryState, reason);
// Emit any relevant tracing results when we're changing the delivery
// state as part of running from a queue
@@ -1147,15 +1143,13 @@
+ deliveryStateToString(newDeliveryState) + " because " + reason);
}
- r.terminalCount++;
notifyFinishReceiver(queue, app, r, index, receiver);
- checkFinished = true;
}
- // When entire ordered broadcast finished, deliver final result
- if (checkFinished) {
- final boolean recordFinished =
- ((r.terminalCount + r.deferredCount) == r.receivers.size());
- if (recordFinished) {
+
+ // When we've reached a new high-water mark, we might be in a position
+ // to unblock other receivers or the final resultTo
+ if (beyondCountChanged) {
+ if (r.beyondCount == r.receivers.size()) {
scheduleResultTo(r);
}
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index c368290..64fe393 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -24,6 +24,7 @@
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_TARGET_T_ONLY;
+import android.annotation.CheckResult;
import android.annotation.CurrentTimeMillisLong;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.IntDef;
@@ -101,8 +102,7 @@
final @NonNull List<Object> receivers; // contains BroadcastFilter and ResolveInfo
final @DeliveryState int[] delivery; // delivery state of each receiver
final @NonNull String[] deliveryReasons; // reasons for delivery state of each receiver
- final boolean[] deferredUntilActive; // whether each receiver is infinitely deferred
- final int[] blockedUntilTerminalCount; // blocked until count of each receiver
+ final int[] blockedUntilBeyondCount; // blocked until count of each receiver
@Nullable ProcessRecord resultToApp; // who receives final result if non-null
@Nullable IIntentReceiver resultTo; // who receives final result if non-null
boolean deferred;
@@ -134,6 +134,7 @@
int manifestSkipCount; // number of manifest receivers skipped.
int terminalCount; // number of receivers in terminal state.
int deferredCount; // number of receivers in deferred state.
+ int beyondCount; // high-water number of receivers we've moved beyond.
@Nullable BroadcastQueue queue; // the outbound queue handling this broadcast
// Determines the privileges the app's process has in regard to background starts.
@@ -219,6 +220,23 @@
}
}
+ /**
+ * Return if the given delivery state is "beyond", which means that we've
+ * moved beyond this receiver, and future receivers are now unblocked.
+ */
+ static boolean isDeliveryStateBeyond(@DeliveryState int deliveryState) {
+ switch (deliveryState) {
+ case DELIVERY_DELIVERED:
+ case DELIVERY_SKIPPED:
+ case DELIVERY_TIMEOUT:
+ case DELIVERY_FAILURE:
+ case DELIVERY_DEFERRED:
+ return true;
+ default:
+ return false;
+ }
+ }
+
ProcessRecord curApp; // hosting application of current receiver.
ComponentName curComponent; // the receiver class that is currently running.
ActivityInfo curReceiver; // the manifest receiver that is currently running.
@@ -356,7 +374,7 @@
TimeUtils.formatDuration(terminalTime[i] - scheduledTime[i], pw);
pw.print(' ');
}
- pw.print("("); pw.print(blockedUntilTerminalCount[i]); pw.print(") ");
+ pw.print("("); pw.print(blockedUntilBeyondCount[i]); pw.print(") ");
pw.print("#"); pw.print(i); pw.print(": ");
if (o instanceof BroadcastFilter) {
pw.println(o);
@@ -411,8 +429,7 @@
urgent = calculateUrgent(_intent, _options);
deferUntilActive = calculateDeferUntilActive(_callingUid,
_options, _resultTo, _serialized, urgent);
- deferredUntilActive = new boolean[deferUntilActive ? delivery.length : 0];
- blockedUntilTerminalCount = calculateBlockedUntilTerminalCount(receivers, _serialized);
+ blockedUntilBeyondCount = calculateBlockedUntilBeyondCount(receivers, _serialized);
scheduledTime = new long[delivery.length];
terminalTime = new long[delivery.length];
resultToApp = _resultToApp;
@@ -423,7 +440,7 @@
ordered = _serialized;
sticky = _sticky;
initialSticky = _initialSticky;
- prioritized = isPrioritized(blockedUntilTerminalCount, _serialized);
+ prioritized = isPrioritized(blockedUntilBeyondCount, _serialized);
userId = _userId;
nextReceiver = 0;
state = IDLE;
@@ -467,8 +484,7 @@
delivery = from.delivery;
deliveryReasons = from.deliveryReasons;
deferUntilActive = from.deferUntilActive;
- deferredUntilActive = from.deferredUntilActive;
- blockedUntilTerminalCount = from.blockedUntilTerminalCount;
+ blockedUntilBeyondCount = from.blockedUntilBeyondCount;
scheduledTime = from.scheduledTime;
terminalTime = from.terminalTime;
resultToApp = from.resultToApp;
@@ -627,32 +643,72 @@
/**
* Update the delivery state of the given {@link #receivers} index.
* Automatically updates any time measurements related to state changes.
+ *
+ * @return if {@link #beyondCount} changed due to this state transition,
+ * indicating that other events may be unblocked.
*/
- void setDeliveryState(int index, @DeliveryState int deliveryState,
+ @CheckResult
+ boolean setDeliveryState(int index, @DeliveryState int newDeliveryState,
@NonNull String reason) {
- delivery[index] = deliveryState;
- deliveryReasons[index] = reason;
- if (deferUntilActive) deferredUntilActive[index] = false;
- switch (deliveryState) {
+ final int oldDeliveryState = delivery[index];
+ if (isDeliveryStateTerminal(oldDeliveryState)
+ || newDeliveryState == oldDeliveryState) {
+ // We've already arrived in terminal or requested state, so leave
+ // any statistics and reasons intact from the first transition
+ return false;
+ }
+
+ switch (oldDeliveryState) {
+ case DELIVERY_DEFERRED:
+ deferredCount--;
+ break;
+ }
+ switch (newDeliveryState) {
+ case DELIVERY_SCHEDULED:
+ scheduledTime[index] = SystemClock.uptimeMillis();
+ break;
+ case DELIVERY_DEFERRED:
+ deferredCount++;
+ break;
case DELIVERY_DELIVERED:
case DELIVERY_SKIPPED:
case DELIVERY_TIMEOUT:
case DELIVERY_FAILURE:
terminalTime[index] = SystemClock.uptimeMillis();
- break;
- case DELIVERY_SCHEDULED:
- scheduledTime[index] = SystemClock.uptimeMillis();
- break;
- case DELIVERY_DEFERRED:
- if (deferUntilActive) deferredUntilActive[index] = true;
+ terminalCount++;
break;
}
+
+ delivery[index] = newDeliveryState;
+ deliveryReasons[index] = reason;
+
+ // If this state change might bring us to a new high-water mark, bring
+ // ourselves as high as we possibly can
+ final int oldBeyondCount = beyondCount;
+ if (index >= beyondCount) {
+ for (int i = beyondCount; i < delivery.length; i++) {
+ if (isDeliveryStateBeyond(getDeliveryState(i))) {
+ beyondCount = i + 1;
+ } else {
+ break;
+ }
+ }
+ }
+ return (beyondCount != oldBeyondCount);
}
@DeliveryState int getDeliveryState(int index) {
return delivery[index];
}
+ /**
+ * @return if the given {@link #receivers} index should be considered
+ * blocked based on the current status of the overall broadcast.
+ */
+ boolean isBlocked(int index) {
+ return (beyondCount < blockedUntilBeyondCount[index]);
+ }
+
boolean wasDeliveryAttempted(int index) {
final int deliveryState = getDeliveryState(index);
switch (deliveryState) {
@@ -757,36 +813,36 @@
* has prioritized tranches of receivers.
*/
@VisibleForTesting
- static boolean isPrioritized(@NonNull int[] blockedUntilTerminalCount,
+ static boolean isPrioritized(@NonNull int[] blockedUntilBeyondCount,
boolean ordered) {
- return !ordered && (blockedUntilTerminalCount.length > 0)
- && (blockedUntilTerminalCount[0] != -1);
+ return !ordered && (blockedUntilBeyondCount.length > 0)
+ && (blockedUntilBeyondCount[0] != -1);
}
/**
- * Calculate the {@link #terminalCount} that each receiver should be
+ * Calculate the {@link #beyondCount} that each receiver should be
* considered blocked until.
* <p>
* For example, in an ordered broadcast, receiver {@code N} is blocked until
- * receiver {@code N-1} reaches a terminal state. Similarly, in a
- * prioritized broadcast, receiver {@code N} is blocked until all receivers
- * of a higher priority reach a terminal state.
+ * receiver {@code N-1} reaches a terminal or deferred state. Similarly, in
+ * a prioritized broadcast, receiver {@code N} is blocked until all
+ * receivers of a higher priority reach a terminal or deferred state.
* <p>
- * When there are no terminal count constraints, the blocked value for each
+ * When there are no beyond count constraints, the blocked value for each
* receiver is {@code -1}.
*/
@VisibleForTesting
- static @NonNull int[] calculateBlockedUntilTerminalCount(
+ static @NonNull int[] calculateBlockedUntilBeyondCount(
@NonNull List<Object> receivers, boolean ordered) {
final int N = receivers.size();
- final int[] blockedUntilTerminalCount = new int[N];
+ final int[] blockedUntilBeyondCount = new int[N];
int lastPriority = 0;
int lastPriorityIndex = 0;
for (int i = 0; i < N; i++) {
if (ordered) {
// When sending an ordered broadcast, we need to block this
// receiver until all previous receivers have terminated
- blockedUntilTerminalCount[i] = i;
+ blockedUntilBeyondCount[i] = i;
} else {
// When sending a prioritized broadcast, we only need to wait
// for the previous tranche of receivers to be terminated
@@ -794,18 +850,18 @@
if ((i == 0) || (thisPriority != lastPriority)) {
lastPriority = thisPriority;
lastPriorityIndex = i;
- blockedUntilTerminalCount[i] = i;
+ blockedUntilBeyondCount[i] = i;
} else {
- blockedUntilTerminalCount[i] = lastPriorityIndex;
+ blockedUntilBeyondCount[i] = lastPriorityIndex;
}
}
}
// If the entire list is in the same priority tranche, mark as -1 to
// indicate that none of them need to wait
- if (N > 0 && blockedUntilTerminalCount[N - 1] == 0) {
- Arrays.fill(blockedUntilTerminalCount, -1);
+ if (N > 0 && blockedUntilBeyondCount[N - 1] == 0) {
+ Arrays.fill(blockedUntilBeyondCount, -1);
}
- return blockedUntilTerminalCount;
+ return blockedUntilBeyondCount;
}
static int getReceiverUid(@NonNull Object receiver) {
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 4328efe..f42087f 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -18,6 +18,28 @@
import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN;
import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ALLOWLIST;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_STOP_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UID_IDLE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE;
import static android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_COMPACTION;
@@ -26,6 +48,7 @@
import android.annotation.IntDef;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal.OomAdjReason;
import android.app.ActivityThread;
import android.app.ApplicationExitInfo;
import android.app.IApplicationThread;
@@ -139,6 +162,26 @@
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_BINDER_TXNS;
static final int UNFREEZE_REASON_FEATURE_FLAGS =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_FEATURE_FLAGS;
+ static final int UNFREEZE_REASON_SHORT_FGS_TIMEOUT =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SHORT_FGS_TIMEOUT;
+ static final int UNFREEZE_REASON_SYSTEM_INIT =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SYSTEM_INIT;
+ static final int UNFREEZE_REASON_BACKUP =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_BACKUP;
+ static final int UNFREEZE_REASON_SHELL =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SHELL;
+ static final int UNFREEZE_REASON_REMOVE_TASK =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_REMOVE_TASK;
+ static final int UNFREEZE_REASON_UID_IDLE =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_UID_IDLE;
+ static final int UNFREEZE_REASON_STOP_SERVICE =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_STOP_SERVICE;
+ static final int UNFREEZE_REASON_EXECUTING_SERVICE =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_EXECUTING_SERVICE;
+ static final int UNFREEZE_REASON_RESTRICTION_CHANGE =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_RESTRICTION_CHANGE;
+ static final int UNFREEZE_REASON_COMPONENT_DISABLED =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_COMPONENT_DISABLED;
@IntDef(prefix = {"UNFREEZE_REASON_"}, value = {
UNFREEZE_REASON_NONE,
@@ -160,6 +203,16 @@
UNFREEZE_REASON_FILE_LOCK_CHECK_FAILURE,
UNFREEZE_REASON_BINDER_TXNS,
UNFREEZE_REASON_FEATURE_FLAGS,
+ UNFREEZE_REASON_SHORT_FGS_TIMEOUT,
+ UNFREEZE_REASON_SYSTEM_INIT,
+ UNFREEZE_REASON_BACKUP,
+ UNFREEZE_REASON_SHELL,
+ UNFREEZE_REASON_REMOVE_TASK,
+ UNFREEZE_REASON_UID_IDLE,
+ UNFREEZE_REASON_STOP_SERVICE,
+ UNFREEZE_REASON_EXECUTING_SERVICE,
+ UNFREEZE_REASON_RESTRICTION_CHANGE,
+ UNFREEZE_REASON_COMPONENT_DISABLED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface UnfreezeReason {}
@@ -1365,7 +1418,7 @@
* The caller of this function should still trigger updateOomAdj for AMS to unfreeze the app.
* @param pid pid of the process to be unfrozen
*/
- void unfreezeProcess(int pid, @OomAdjuster.OomAdjReason int reason) {
+ void unfreezeProcess(int pid, @OomAdjReason int reason) {
synchronized (mFreezerLock) {
ProcessRecord app = mFrozenProcesses.get(pid);
if (app == null) {
@@ -1546,12 +1599,12 @@
public long mOrigAnonRss;
public int mProcState;
public int mOomAdj;
- public @OomAdjuster.OomAdjReason int mOomAdjReason;
+ public @OomAdjReason int mOomAdjReason;
SingleCompactionStats(long[] rss, CompactSource source, String processName,
long deltaAnonRss, long zramConsumed, long anonMemFreed, long origAnonRss,
long cpuTimeMillis, int procState, int oomAdj,
- @OomAdjuster.OomAdjReason int oomAdjReason, int uid) {
+ @OomAdjReason int oomAdjReason, int uid) {
mRssAfterCompaction = rss;
mSourceType = source;
mProcessName = processName;
@@ -2207,32 +2260,52 @@
}
}
- static int getUnfreezeReasonCodeFromOomAdjReason(@OomAdjuster.OomAdjReason int oomAdjReason) {
+ static int getUnfreezeReasonCodeFromOomAdjReason(@OomAdjReason int oomAdjReason) {
switch (oomAdjReason) {
- case OomAdjuster.OOM_ADJ_REASON_ACTIVITY:
+ case OOM_ADJ_REASON_ACTIVITY:
return UNFREEZE_REASON_ACTIVITY;
- case OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER:
+ case OOM_ADJ_REASON_FINISH_RECEIVER:
return UNFREEZE_REASON_FINISH_RECEIVER;
- case OomAdjuster.OOM_ADJ_REASON_START_RECEIVER:
+ case OOM_ADJ_REASON_START_RECEIVER:
return UNFREEZE_REASON_START_RECEIVER;
- case OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE:
+ case OOM_ADJ_REASON_BIND_SERVICE:
return UNFREEZE_REASON_BIND_SERVICE;
- case OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE:
+ case OOM_ADJ_REASON_UNBIND_SERVICE:
return UNFREEZE_REASON_UNBIND_SERVICE;
- case OomAdjuster.OOM_ADJ_REASON_START_SERVICE:
+ case OOM_ADJ_REASON_START_SERVICE:
return UNFREEZE_REASON_START_SERVICE;
- case OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER:
+ case OOM_ADJ_REASON_GET_PROVIDER:
return UNFREEZE_REASON_GET_PROVIDER;
- case OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER:
+ case OOM_ADJ_REASON_REMOVE_PROVIDER:
return UNFREEZE_REASON_REMOVE_PROVIDER;
- case OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY:
+ case OOM_ADJ_REASON_UI_VISIBILITY:
return UNFREEZE_REASON_UI_VISIBILITY;
- case OomAdjuster.OOM_ADJ_REASON_ALLOWLIST:
+ case OOM_ADJ_REASON_ALLOWLIST:
return UNFREEZE_REASON_ALLOWLIST;
- case OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN:
+ case OOM_ADJ_REASON_PROCESS_BEGIN:
return UNFREEZE_REASON_PROCESS_BEGIN;
- case OomAdjuster.OOM_ADJ_REASON_PROCESS_END:
+ case OOM_ADJ_REASON_PROCESS_END:
return UNFREEZE_REASON_PROCESS_END;
+ case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT:
+ return UNFREEZE_REASON_SHORT_FGS_TIMEOUT;
+ case OOM_ADJ_REASON_SYSTEM_INIT:
+ return UNFREEZE_REASON_SYSTEM_INIT;
+ case OOM_ADJ_REASON_BACKUP:
+ return UNFREEZE_REASON_BACKUP;
+ case OOM_ADJ_REASON_SHELL:
+ return UNFREEZE_REASON_SHELL;
+ case OOM_ADJ_REASON_REMOVE_TASK:
+ return UNFREEZE_REASON_REMOVE_TASK;
+ case OOM_ADJ_REASON_UID_IDLE:
+ return UNFREEZE_REASON_UID_IDLE;
+ case OOM_ADJ_REASON_STOP_SERVICE:
+ return UNFREEZE_REASON_STOP_SERVICE;
+ case OOM_ADJ_REASON_EXECUTING_SERVICE:
+ return UNFREEZE_REASON_EXECUTING_SERVICE;
+ case OOM_ADJ_REASON_RESTRICTION_CHANGE:
+ return UNFREEZE_REASON_RESTRICTION_CHANGE;
+ case OOM_ADJ_REASON_COMPONENT_DISABLED:
+ return UNFREEZE_REASON_COMPONENT_DISABLED;
default:
return UNFREEZE_REASON_NONE;
}
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index a1fcd42..d8cb094 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -16,6 +16,8 @@
package com.android.server.am;
import static android.Manifest.permission.GET_ANY_PROVIDER_TYPE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER;
import static android.content.ContentProvider.isAuthorityRedirectedForCloneProfile;
import static android.os.Process.PROC_CHAR;
import static android.os.Process.PROC_OUT_LONG;
@@ -292,7 +294,7 @@
checkTime(startTime, "getContentProviderImpl: before updateOomAdj");
final int verifiedAdj = cpr.proc.mState.getVerifiedAdj();
boolean success = mService.updateOomAdjLocked(cpr.proc,
- OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER);
+ OOM_ADJ_REASON_GET_PROVIDER);
// XXX things have changed so updateOomAdjLocked doesn't actually tell us
// if the process has been successfully adjusted. So to reduce races with
// it, we will check whether the process still exists. Note that this doesn't
@@ -757,7 +759,7 @@
// update the app's oom adj value and each provider's usage stats
if (providersPublished) {
- mService.updateOomAdjLocked(r, OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER);
+ mService.updateOomAdjLocked(r, OOM_ADJ_REASON_GET_PROVIDER);
for (int i = 0, size = providers.size(); i < size; i++) {
ContentProviderHolder src = providers.get(i);
if (src == null || src.info == null || src.provider == null) {
@@ -835,8 +837,7 @@
ContentProviderRecord localCpr = mProviderMap.getProviderByClass(comp, userId);
if (localCpr.hasExternalProcessHandles()) {
if (localCpr.removeExternalProcessHandleLocked(token)) {
- mService.updateOomAdjLocked(localCpr.proc,
- OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER);
+ mService.updateOomAdjLocked(localCpr.proc, OOM_ADJ_REASON_REMOVE_PROVIDER);
} else {
Slog.e(TAG, "Attempt to remove content provider " + localCpr
+ " with no external reference for token: " + token + ".");
@@ -1506,8 +1507,7 @@
mService.stopAssociationLocked(conn.client.uid, conn.client.processName, cpr.uid,
cpr.appInfo.longVersionCode, cpr.name, cpr.info.processName);
if (updateOomAdj) {
- mService.updateOomAdjLocked(conn.provider.proc,
- OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER);
+ mService.updateOomAdjLocked(conn.provider.proc, OOM_ADJ_REASON_REMOVE_PROVIDER);
}
}
}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index a98571b..365dcd9 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -41,6 +41,29 @@
import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ALLOWLIST;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_STOP_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UID_IDLE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE;
import static android.content.Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
@@ -101,9 +124,9 @@
import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
-import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal.OomAdjReason;
import android.app.ActivityThread;
import android.app.AppProtoEnums;
import android.app.ApplicationExitInfo;
@@ -141,8 +164,6 @@
import com.android.server.wm.WindowProcessController;
import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
@@ -154,32 +175,6 @@
public class OomAdjuster {
static final String TAG = "OomAdjuster";
- static final int OOM_ADJ_REASON_NONE = 0;
- static final int OOM_ADJ_REASON_ACTIVITY = 1;
- static final int OOM_ADJ_REASON_FINISH_RECEIVER = 2;
- static final int OOM_ADJ_REASON_START_RECEIVER = 3;
- static final int OOM_ADJ_REASON_BIND_SERVICE = 4;
- static final int OOM_ADJ_REASON_UNBIND_SERVICE = 5;
- static final int OOM_ADJ_REASON_START_SERVICE = 6;
- static final int OOM_ADJ_REASON_GET_PROVIDER = 7;
- static final int OOM_ADJ_REASON_REMOVE_PROVIDER = 8;
- static final int OOM_ADJ_REASON_UI_VISIBILITY = 9;
- static final int OOM_ADJ_REASON_ALLOWLIST = 10;
- static final int OOM_ADJ_REASON_PROCESS_BEGIN = 11;
- static final int OOM_ADJ_REASON_PROCESS_END = 12;
- static final int OOM_ADJ_REASON_SHORT_FGS_TIMEOUT = 13;
-
- @IntDef(prefix = {"OOM_ADJ_REASON_"},
- value = {OOM_ADJ_REASON_NONE, OOM_ADJ_REASON_ACTIVITY, OOM_ADJ_REASON_FINISH_RECEIVER,
- OOM_ADJ_REASON_START_RECEIVER, OOM_ADJ_REASON_BIND_SERVICE,
- OOM_ADJ_REASON_UNBIND_SERVICE, OOM_ADJ_REASON_START_SERVICE,
- OOM_ADJ_REASON_GET_PROVIDER, OOM_ADJ_REASON_REMOVE_PROVIDER,
- OOM_ADJ_REASON_UI_VISIBILITY, OOM_ADJ_REASON_ALLOWLIST,
- OOM_ADJ_REASON_PROCESS_BEGIN, OOM_ADJ_REASON_PROCESS_END,
- OOM_ADJ_REASON_SHORT_FGS_TIMEOUT})
- @Retention(RetentionPolicy.SOURCE)
- public @interface OomAdjReason {}
-
public static final int oomAdjReasonToProto(@OomAdjReason int oomReason) {
switch (oomReason) {
case OOM_ADJ_REASON_NONE:
@@ -210,6 +205,24 @@
return AppProtoEnums.OOM_ADJ_REASON_PROCESS_END;
case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT:
return AppProtoEnums.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
+ case OOM_ADJ_REASON_SYSTEM_INIT:
+ return AppProtoEnums.OOM_ADJ_REASON_SYSTEM_INIT;
+ case OOM_ADJ_REASON_BACKUP:
+ return AppProtoEnums.OOM_ADJ_REASON_BACKUP;
+ case OOM_ADJ_REASON_SHELL:
+ return AppProtoEnums.OOM_ADJ_REASON_SHELL;
+ case OOM_ADJ_REASON_REMOVE_TASK:
+ return AppProtoEnums.OOM_ADJ_REASON_REMOVE_TASK;
+ case OOM_ADJ_REASON_UID_IDLE:
+ return AppProtoEnums.OOM_ADJ_REASON_UID_IDLE;
+ case OOM_ADJ_REASON_STOP_SERVICE:
+ return AppProtoEnums.OOM_ADJ_REASON_STOP_SERVICE;
+ case OOM_ADJ_REASON_EXECUTING_SERVICE:
+ return AppProtoEnums.OOM_ADJ_REASON_EXECUTING_SERVICE;
+ case OOM_ADJ_REASON_RESTRICTION_CHANGE:
+ return AppProtoEnums.OOM_ADJ_REASON_RESTRICTION_CHANGE;
+ case OOM_ADJ_REASON_COMPONENT_DISABLED:
+ return AppProtoEnums.OOM_ADJ_REASON_COMPONENT_DISABLED;
default:
return AppProtoEnums.OOM_ADJ_REASON_UNKNOWN_TO_PROTO;
}
@@ -246,6 +259,24 @@
return OOM_ADJ_REASON_METHOD + "_processEnd";
case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT:
return OOM_ADJ_REASON_METHOD + "_shortFgs";
+ case OOM_ADJ_REASON_SYSTEM_INIT:
+ return OOM_ADJ_REASON_METHOD + "_systemInit";
+ case OOM_ADJ_REASON_BACKUP:
+ return OOM_ADJ_REASON_METHOD + "_backup";
+ case OOM_ADJ_REASON_SHELL:
+ return OOM_ADJ_REASON_METHOD + "_shell";
+ case OOM_ADJ_REASON_REMOVE_TASK:
+ return OOM_ADJ_REASON_METHOD + "_removeTask";
+ case OOM_ADJ_REASON_UID_IDLE:
+ return OOM_ADJ_REASON_METHOD + "_uidIdle";
+ case OOM_ADJ_REASON_STOP_SERVICE:
+ return OOM_ADJ_REASON_METHOD + "_stopService";
+ case OOM_ADJ_REASON_EXECUTING_SERVICE:
+ return OOM_ADJ_REASON_METHOD + "_executingService";
+ case OOM_ADJ_REASON_RESTRICTION_CHANGE:
+ return OOM_ADJ_REASON_METHOD + "_restrictionChange";
+ case OOM_ADJ_REASON_COMPONENT_DISABLED:
+ return OOM_ADJ_REASON_METHOD + "_componentDisabled";
default:
return "_unknown";
}
@@ -874,8 +905,7 @@
}
@GuardedBy("mService")
- private void performUpdateOomAdjPendingTargetsLocked(
- @OomAdjuster.OomAdjReason int oomAdjReason) {
+ private void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
final ProcessRecord topApp = mService.getTopApp();
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
@@ -3453,7 +3483,7 @@
}
@GuardedBy("mService")
- void unfreezeTemporarily(ProcessRecord app, @OomAdjuster.OomAdjReason int reason) {
+ void unfreezeTemporarily(ProcessRecord app, @OomAdjReason int reason) {
if (!mCachedAppOptimizer.useFreezer()) {
return;
}
diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
index 24cc533..f233107 100644
--- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
+++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
@@ -16,6 +16,8 @@
package com.android.server.am;
+import android.app.ActivityManagerInternal.OomAdjReason;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -51,7 +53,7 @@
/**
* Last oom adjust change reason for this app.
*/
- @GuardedBy("mProcLock") private @OomAdjuster.OomAdjReason int mLastOomAdjChangeReason;
+ @GuardedBy("mProcLock") private @OomAdjReason int mLastOomAdjChangeReason;
/**
* The most recent compaction action performed for this app.
@@ -139,12 +141,12 @@
}
@GuardedBy("mProcLock")
- void setLastOomAdjChangeReason(@OomAdjuster.OomAdjReason int reason) {
+ void setLastOomAdjChangeReason(@OomAdjReason int reason) {
mLastOomAdjChangeReason = reason;
}
@GuardedBy("mProcLock")
- @OomAdjuster.OomAdjReason
+ @OomAdjReason
int getLastOomAdjChangeReason() {
return mLastOomAdjChangeReason;
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index b1322ef..312f98a 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -19,6 +19,8 @@
import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
import static android.app.ActivityThread.PROC_START_SEQ_IDENT;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode;
@@ -2875,7 +2877,7 @@
reasonCode, subReason, reason, !doFreeze /* async */);
}
killAppZygotesLocked(packageName, appId, userId, false /* force */);
- mService.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_PROCESS_END);
+ mService.updateOomAdjLocked(OOM_ADJ_REASON_PROCESS_END);
if (doFreeze) {
freezePackageCgroup(packageUID, false);
}
@@ -5140,7 +5142,7 @@
}
});
/* Will be a no-op if nothing pending */
- mService.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_RESTRICTION_CHANGE);
}
}
@@ -5203,6 +5205,17 @@
}
/**
+ * Called by ActivityManagerService when a recoverable native crash occurs.
+ */
+ @GuardedBy("mService")
+ void noteAppRecoverableCrash(final ProcessRecord app) {
+ if (DEBUG_PROCESSES) {
+ Slog.i(TAG, "note: " + app + " has a recoverable native crash");
+ }
+ mAppExitInfoTracker.scheduleNoteAppRecoverableCrash(app);
+ }
+
+ /**
* Called by ActivityManagerService when it decides to kill an application process.
*/
@GuardedBy("mService")
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index afae623..7aae4d5 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -16,6 +16,8 @@
package com.android.server.am;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+
import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -1450,7 +1452,7 @@
}
mService.updateLruProcessLocked(this, activityChange, null /* client */);
if (updateOomAdj) {
- mService.updateOomAdjLocked(this, OomAdjuster.OOM_ADJ_REASON_ACTIVITY);
+ mService.updateOomAdjLocked(this, OOM_ADJ_REASON_ACTIVITY);
}
}
}
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index 71d5d39..ab71acd 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -18,6 +18,7 @@
import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_ACTIVITY;
@@ -613,7 +614,7 @@
void forceProcessStateUpTo(int newState) {
if (mRepProcState > newState) {
synchronized (mProcLock) {
- mRepProcState = newState;
+ setReportedProcState(newState);
setCurProcState(newState);
setCurRawProcState(newState);
}
@@ -766,7 +767,7 @@
Slog.i(TAG, "Setting runningRemoteAnimation=" + runningRemoteAnimation
+ " for pid=" + mApp.getPid());
}
- mService.updateOomAdjLocked(mApp, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+ mService.updateOomAdjLocked(mApp, OOM_ADJ_REASON_UI_VISIBILITY);
}
@GuardedBy({"mService", "mProcLock"})
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 81ba4b8..a110169 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -34,6 +34,7 @@
import static android.app.AppOpsManager.MODE_FOREGROUND;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.AppOpsManager.OP_CAMERA;
+import static android.app.AppOpsManager.OP_CAMERA_SANDBOXED;
import static android.app.AppOpsManager.OP_FLAGS_ALL;
import static android.app.AppOpsManager.OP_FLAG_SELF;
import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
@@ -42,6 +43,7 @@
import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO_SANDBOXED;
import static android.app.AppOpsManager.OP_VIBRATE;
import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
@@ -3027,17 +3029,29 @@
packageName);
}
- // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution
- // purposes and not as a check, also make sure that the caller is allowed to access
- // the data gated by OP_RECORD_AUDIO.
+ // As a special case for OP_RECORD_AUDIO_HOTWORD, OP_RECEIVE_AMBIENT_TRIGGER_AUDIO and
+ // OP_RECORD_AUDIO_SANDBOXED which we use only for attribution purposes and not as a check,
+ // also make sure that the caller is allowed to access the data gated by OP_RECORD_AUDIO.
//
// TODO: Revert this change before Android 12.
- if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) {
- int result = checkOperation(OP_RECORD_AUDIO, uid, packageName);
+ int result = MODE_DEFAULT;
+ if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO
+ || code == OP_RECORD_AUDIO_SANDBOXED) {
+ result = checkOperation(OP_RECORD_AUDIO, uid, packageName);
+ // Check result
if (result != AppOpsManager.MODE_ALLOWED) {
return new SyncNotedAppOp(result, code, attributionTag, packageName);
}
}
+ // As a special case for OP_CAMERA_SANDBOXED.
+ if (code == OP_CAMERA_SANDBOXED) {
+ result = checkOperation(OP_CAMERA, uid, packageName);
+ // Check result
+ if (result != AppOpsManager.MODE_ALLOWED) {
+ return new SyncNotedAppOp(result, code, attributionTag, packageName);
+ }
+ }
+
return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags,
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 7c8e6df..5127d26 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -19,6 +19,8 @@
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR_BASE;
import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE;
@@ -519,6 +521,9 @@
try {
mStatusBarService.onBiometricHelp(sensorIdToModality(sensorId), message);
+ final int aAcquiredInfo = acquiredInfo == FINGERPRINT_ACQUIRED_VENDOR
+ ? (vendorCode + FINGERPRINT_ACQUIRED_VENDOR_BASE) : acquiredInfo;
+ mClientReceiver.onAcquired(aAcquiredInfo, message);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 128ef0b..6c26e2b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -413,6 +413,11 @@
Slog.e(TAG, "Remote exception in onAuthenticationAcquired()", e);
}
}
+
+ @Override
+ public void onAuthenticationHelp(int acquireInfo, CharSequence helpString) {
+ onAuthenticationAcquired(acquireInfo);
+ }
};
return biometricPrompt.authenticateForOperation(
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
index 2ac2833..c039a83 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
@@ -334,8 +334,8 @@
}
private int installCert(ShellCommand shell) throws SystemFontException {
- if (!(Build.IS_USERDEBUG || Build.IS_ENG)) {
- throw new SecurityException("Only userdebug/eng device can add debug certificate");
+ if (!Build.IS_DEBUGGABLE) {
+ throw new SecurityException("Only debuggable device can add debug certificate");
}
if (Binder.getCallingUid() != Process.ROOT_UID) {
throw new SecurityException("Only root can add debug certificate");
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 43e346a..2d40661 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -323,7 +323,7 @@
*/
void notifyInstallerOfAppWhoseLocaleChanged(String appPackageName, int userId,
LocaleList locales) {
- String installingPackageName = getInstallingPackageName(appPackageName);
+ String installingPackageName = getInstallingPackageName(appPackageName, userId);
if (installingPackageName != null) {
Intent intent = createBaseIntent(Intent.ACTION_APPLICATION_LOCALE_CHANGED,
appPackageName, locales);
@@ -464,7 +464,7 @@
* Checks if the calling app is the installer of the app whose locale changed.
*/
private boolean isCallerInstaller(String appPackageName, int userId) {
- String installingPackageName = getInstallingPackageName(appPackageName);
+ String installingPackageName = getInstallingPackageName(appPackageName, userId);
if (installingPackageName != null) {
// Get the uid of installer-on-record to compare with the calling uid.
int installerUid = getPackageUid(installingPackageName, userId);
@@ -513,10 +513,11 @@
}
@Nullable
- String getInstallingPackageName(String packageName) {
+ String getInstallingPackageName(String packageName, int userId) {
try {
- return mContext.getPackageManager()
- .getInstallSourceInfo(packageName).getInstallingPackageName();
+ return mContext.createContextAsUser(UserHandle.of(userId), /* flags= */
+ 0).getPackageManager().getInstallSourceInfo(
+ packageName).getInstallingPackageName();
} catch (PackageManager.NameNotFoundException e) {
Slog.w(TAG, "Package not found " + packageName);
}
diff --git a/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java b/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java
index 215c653..373d355 100644
--- a/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java
+++ b/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java
@@ -152,9 +152,10 @@
void onPackageUpdateFinished(String packageName, int uid) {
try {
if ((!mUpdatedApps.contains(packageName)) && isUpdatedSystemApp(packageName)) {
+ int userId = UserHandle.getUserId(uid);
// If a system app is updated, verify that it has an installer-on-record.
String installingPackageName = mLocaleManagerService.getInstallingPackageName(
- packageName);
+ packageName, userId);
if (installingPackageName == null) {
// We want to broadcast the locales info to the installer.
// If this app does not have an installer then do nothing.
@@ -162,7 +163,6 @@
}
try {
- int userId = UserHandle.getUserId(uid);
// Fetch the app-specific locales.
// If non-empty then send the info to the installer.
LocaleList appLocales = mLocaleManagerService.getApplicationLocales(
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 653b718..5f78374 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -519,17 +519,24 @@
BroadcastReceiver btReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())
- || BluetoothAdapter.ACTION_BLE_STATE_CHANGED.equals(
- intent.getAction())) {
+ if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) {
sendBtSettingUpdate(/* forceUpdate= */ false);
}
}
};
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
- filter.addAction(BluetoothAdapter.ACTION_BLE_STATE_CHANGED);
mContext.registerReceiver(btReceiver, filter);
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE),
+ /* notifyForDescendants= */ false,
+ new ContentObserver(/* handler= */ null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ sendBtSettingUpdate(/* forceUpdate= */ false);
+ }
+ }, UserHandle.USER_ALL);
}
/**
diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java
index 35b94e7..88d23ce 100644
--- a/services/core/java/com/android/server/notification/ZenLog.java
+++ b/services/core/java/com/android/server/notification/ZenLog.java
@@ -27,6 +27,7 @@
import android.service.notification.IConditionProvider;
import android.service.notification.NotificationListenerService;
import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeDiff;
import android.util.Log;
import android.util.Slog;
@@ -146,13 +147,13 @@
public static void traceConfig(String reason, ZenModeConfig oldConfig,
ZenModeConfig newConfig) {
- ZenModeConfig.Diff diff = ZenModeConfig.diff(oldConfig, newConfig);
- if (diff.isEmpty()) {
+ ZenModeDiff.ConfigDiff diff = new ZenModeDiff.ConfigDiff(oldConfig, newConfig);
+ if (diff == null || !diff.hasDiff()) {
append(TYPE_CONFIG, reason + " no changes");
} else {
append(TYPE_CONFIG, reason
+ ",\n" + (newConfig != null ? newConfig.toString() : null)
- + ",\n" + ZenModeConfig.diff(oldConfig, newConfig));
+ + ",\n" + diff);
}
}
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index 401eac6..7a5664f 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -316,6 +316,7 @@
private int resolveDatasourceOp(int code, int uid, @NonNull String packageName,
@Nullable String attributionTag) {
code = resolveRecordAudioOp(code, uid);
+ code = resolveSandboxedServiceOp(code, uid);
if (attributionTag == null) {
return code;
}
@@ -439,6 +440,28 @@
return code;
}
+ private int resolveSandboxedServiceOp(int code, int uid) {
+ if (!Process.isIsolated(uid) // simple check which fails-fast for the common case
+ || !(code == AppOpsManager.OP_RECORD_AUDIO || code == AppOpsManager.OP_CAMERA)) {
+ return code;
+ }
+ final HotwordDetectionServiceIdentity hotwordDetectionServiceIdentity =
+ mVoiceInteractionManagerInternal.getHotwordDetectionServiceIdentity();
+ if (hotwordDetectionServiceIdentity != null
+ && uid == hotwordDetectionServiceIdentity.getIsolatedUid()) {
+ // Upgrade the op such that no indicators is shown for camera or audio service. This
+ // will bypass the permission checking for the original OP_RECORD_AUDIO and OP_CAMERA.
+ switch (code) {
+ case AppOpsManager.OP_RECORD_AUDIO:
+ return AppOpsManager.OP_RECORD_AUDIO_SANDBOXED;
+ case AppOpsManager.OP_CAMERA:
+ return AppOpsManager.OP_CAMERA_SANDBOXED;
+ }
+ }
+ return code;
+ }
+
+
private int resolveUid(int code, int uid) {
// The HotwordDetectionService is an isolated service, which ordinarily cannot hold
// permissions. So we allow it to assume the owning package identity for certain
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index df360b8..f300113 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -25,6 +25,8 @@
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
+import static com.android.internal.util.DumpUtils.dumpSparseArray;
+import static com.android.internal.util.DumpUtils.dumpSparseArrayValues;
import static com.android.server.accessibility.AccessibilityTraceFileProto.ENTRY;
import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER;
import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER_H;
@@ -44,8 +46,6 @@
import static com.android.server.accessibility.AccessibilityTraceProto.WINDOW_MANAGER_SERVICE;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.dumpSparseArray;
-import static com.android.server.wm.WindowManagerService.dumpSparseArrayValues;
import static com.android.server.wm.WindowTracing.WINSCOPE_EXT;
import android.accessibilityservice.AccessibilityTrace;
diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
index afe1640..70f2007 100644
--- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
+++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
@@ -16,8 +16,9 @@
package com.android.server.wm;
-import static com.android.server.wm.WindowManagerService.ValueDumper;
-import static com.android.server.wm.WindowManagerService.dumpSparseArray;
+import static com.android.internal.util.DumpUtils.KeyDumper;
+import static com.android.internal.util.DumpUtils.ValueDumper;
+import static com.android.internal.util.DumpUtils.dumpSparseArray;
import static com.android.server.wm.utils.RegionUtils.forEachRect;
import android.annotation.NonNull;
@@ -41,7 +42,6 @@
import android.window.WindowInfosListener;
import com.android.internal.annotations.GuardedBy;
-import com.android.server.wm.WindowManagerService.KeyDumper;
import java.io.PrintWriter;
import java.util.ArrayList;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 12fe6a0..8123c07 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -831,7 +831,7 @@
private final Runnable mUpdateOomAdjRunnable = new Runnable() {
@Override
public void run() {
- mAmInternal.updateOomAdj();
+ mAmInternal.updateOomAdj(ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY);
}
};
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index fb592e1..10055bd 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5684,6 +5684,7 @@
// Usually resuming a top activity triggers the next app transition, but nothing's got
// resumed in this case, so we need to execute it explicitly.
mDisplayContent.executeAppTransition();
+ mDisplayContent.setFocusedApp(topActivity);
} else {
mRootWindowContainer.resumeFocusedTasksTopActivities();
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 3bf8969..742ab7ce 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -443,26 +443,36 @@
return type == TRANSIT_OPEN || type == TRANSIT_CLOSE;
}
- /** Whether the display change should run with blast sync. */
- private static boolean shouldSync(@NonNull TransitionRequestInfo.DisplayChange displayChange) {
- if ((displayChange.getStartRotation() + displayChange.getEndRotation()) % 2 == 0) {
+ /** Sets the sync method for the display change. */
+ private void setDisplaySyncMethod(@NonNull TransitionRequestInfo.DisplayChange displayChange,
+ @NonNull Transition displayTransition, @NonNull DisplayContent displayContent) {
+ final int startRotation = displayChange.getStartRotation();
+ final int endRotation = displayChange.getEndRotation();
+ if (startRotation != endRotation && (startRotation + endRotation) % 2 == 0) {
// 180 degrees rotation change may not change screen size. So the clients may draw
// some frames before and after the display projection transaction is applied by the
// remote player. That may cause some buffers to show in different rotation. So use
// sync method to pause clients drawing until the projection transaction is applied.
- return true;
+ mAtm.mWindowManager.mSyncEngine.setSyncMethod(displayTransition.getSyncId(),
+ BLASTSyncEngine.METHOD_BLAST);
}
final Rect startBounds = displayChange.getStartAbsBounds();
final Rect endBounds = displayChange.getEndAbsBounds();
- if (startBounds == null || endBounds == null) return false;
+ if (startBounds == null || endBounds == null) return;
final int startWidth = startBounds.width();
final int startHeight = startBounds.height();
final int endWidth = endBounds.width();
final int endHeight = endBounds.height();
// This is changing screen resolution. Because the screen decor layers are excluded from
// screenshot, their draw transactions need to run with the start transaction.
- return (endWidth > startWidth) == (endHeight > startHeight)
- && (endWidth != startWidth || endHeight != startHeight);
+ if ((endWidth > startWidth) == (endHeight > startHeight)
+ && (endWidth != startWidth || endHeight != startHeight)) {
+ displayContent.forAllWindows(w -> {
+ if (w.mToken.mRoundedCornerOverlay && w.mHasSurface) {
+ w.mSyncMethodOverride = BLASTSyncEngine.METHOD_BLAST;
+ }
+ }, true /* traverseTopToBottom */);
+ }
}
/**
@@ -494,9 +504,9 @@
} else {
newTransition = requestStartTransition(createTransition(type, flags),
trigger != null ? trigger.asTask() : null, remoteTransition, displayChange);
- if (newTransition != null && displayChange != null && shouldSync(displayChange)) {
- mAtm.mWindowManager.mSyncEngine.setSyncMethod(newTransition.getSyncId(),
- BLASTSyncEngine.METHOD_BLAST);
+ if (newTransition != null && displayChange != null && trigger != null
+ && trigger.asDisplayContent() != null) {
+ setDisplaySyncMethod(displayChange, newTransition, trigger.asDisplayContent());
}
}
if (trigger != null) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 82c057b..8fecf11 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -235,7 +235,6 @@
import android.util.MergedConfiguration;
import android.util.Pair;
import android.util.Slog;
-import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.TimeUtils;
@@ -9460,53 +9459,4 @@
return List.copyOf(notifiedApps);
}
}
-
- // TODO(b/271188189): move dump stuff below to common code / add unit tests
-
- interface ValueDumper<T> {
- void dump(T value);
- }
-
- interface KeyDumper{
- void dump(int index, int key);
- }
-
- static void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<?> array, String name) {
- dumpSparseArray(pw, prefix, array, name, /* keyDumper= */ null, /* valuedumper= */ null);
- }
-
- static <T> void dumpSparseArrayValues(PrintWriter pw, String prefix, SparseArray<T> array,
- String name) {
- dumpSparseArray(pw, prefix, array, name, (i, k) -> {}, /* valueDumper= */ null);
- }
-
- static <T> void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<T> array,
- String name, @Nullable KeyDumper keyDumper, @Nullable ValueDumper<T> valueDumper) {
- int size = array.size();
- if (size == 0) {
- pw.print(prefix); pw.print("No "); pw.print(name); pw.println("s");
- return;
- }
- pw.print(prefix); pw.print(size); pw.print(' ');
- pw.print(name); pw.print(size > 1 ? "s" : ""); pw.println(':');
-
- String prefix2 = prefix + " ";
- for (int i = 0; i < size; i++) {
- int key = array.keyAt(i);
- T value = array.valueAt(i);
- if (keyDumper != null) {
- keyDumper.dump(i, key);
- } else {
- pw.print(prefix2); pw.print(i); pw.print(": "); pw.print(key); pw.print("->");
- }
- if (value == null) {
- pw.print("(null)");
- } else if (valueDumper != null) {
- valueDumper.dump(value);
- } else {
- pw.print(value);
- }
- pw.println();
- }
- }
}
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index aeb4801..208a4be 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -161,7 +161,8 @@
@Override
public void onProviderStatusChanged(ProviderSession.Status status,
ComponentName componentName, ProviderSession.CredentialsSource source) {
- Slog.d(TAG, "in onStatusChanged with status: " + status + ", and source: " + source);
+ Slog.d(TAG, "in onStatusChanged for: " + componentName + ", with status: "
+ + status + ", and source: " + source);
// Auth entry was selected, and it did not have any underlying credentials
if (status == ProviderSession.Status.NO_CREDENTIALS_FROM_AUTH_ENTRY) {
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
index 0c3d3f4..9ec0ecd 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -26,7 +26,6 @@
import android.os.ICancellationSignal;
import android.service.credentials.CallingAppInfo;
import android.service.credentials.ClearCredentialStateRequest;
-import android.util.Log;
import android.util.Slog;
/**
@@ -81,7 +80,7 @@
@Override
public void onProviderResponseSuccess(@Nullable Void response) {
- Log.i(TAG, "in onProviderResponseSuccess");
+ Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName);
mProviderResponseSet = true;
updateStatusAndInvokeCallback(Status.COMPLETE,
/*source=*/ CredentialsSource.REMOTE_PROVIDER);
@@ -105,7 +104,7 @@
updateStatusAndInvokeCallback(Status.SERVICE_DEAD,
/*source=*/ CredentialsSource.REMOTE_PROVIDER);
} else {
- Slog.i(TAG, "Component names different in onProviderServiceDied - "
+ Slog.w(TAG, "Component names different in onProviderServiceDied - "
+ "this should not happen");
}
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 8b9255a..09433db 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -37,7 +37,6 @@
import android.service.credentials.CreateEntry;
import android.service.credentials.CredentialProviderService;
import android.service.credentials.RemoteEntry;
-import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -93,7 +92,8 @@
createRequestSession.mHybridService
);
}
- Log.i(TAG, "Unable to create provider session");
+ Slog.d(TAG, "Unable to create provider session for: "
+ + providerInfo.getComponentName());
return null;
}
@@ -122,7 +122,6 @@
return new CreateCredentialRequest(callingAppInfo, capability,
clientRequest.getCredentialData());
}
- Log.i(TAG, "Unable to create provider request - capabilities do not match");
return null;
}
@@ -146,7 +145,7 @@
@Override
public void onProviderResponseSuccess(
@Nullable BeginCreateCredentialResponse response) {
- Log.i(TAG, "in onProviderResponseSuccess");
+ Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName);
onSetInitialRemoteResponse(response);
}
@@ -169,7 +168,7 @@
updateStatusAndInvokeCallback(Status.SERVICE_DEAD,
/*source=*/ CredentialsSource.REMOTE_PROVIDER);
} else {
- Slog.i(TAG, "Component names different in onProviderServiceDied - "
+ Slog.w(TAG, "Component names different in onProviderServiceDied - "
+ "this should not happen");
}
}
@@ -180,7 +179,6 @@
}
private void onSetInitialRemoteResponse(BeginCreateCredentialResponse response) {
- Log.i(TAG, "onSetInitialRemoteResponse with save entries");
mProviderResponse = response;
mProviderResponseDataHandler.addResponseContent(response.getCreateEntries(),
response.getRemoteCreateEntry());
@@ -199,14 +197,12 @@
@Nullable
protected CreateCredentialProviderData prepareUiData()
throws IllegalArgumentException {
- Log.i(TAG, "In prepareUiData");
if (!ProviderSession.isUiInvokingStatus(getStatus())) {
- Log.i(TAG, "In prepareUiData not in uiInvokingStatus");
+ Slog.d(TAG, "No data for UI from: " + mComponentName.flattenToString());
return null;
}
if (mProviderResponse != null && !mProviderResponseDataHandler.isEmptyResponse()) {
- Log.i(TAG, "In prepareUiData save entries not null");
return mProviderResponseDataHandler.toCreateCredentialProviderData();
}
return null;
@@ -218,7 +214,7 @@
switch (entryType) {
case SAVE_ENTRY_KEY:
if (mProviderResponseDataHandler.getCreateEntry(entryKey) == null) {
- Log.i(TAG, "Unexpected save entry key");
+ Slog.w(TAG, "Unexpected save entry key");
invokeCallbackOnInternalInvalidState();
return;
}
@@ -226,14 +222,14 @@
break;
case REMOTE_ENTRY_KEY:
if (mProviderResponseDataHandler.getRemoteEntry(entryKey) == null) {
- Log.i(TAG, "Unexpected remote entry key");
+ Slog.w(TAG, "Unexpected remote entry key");
invokeCallbackOnInternalInvalidState();
return;
}
onRemoteEntrySelected(providerPendingIntentResponse);
break;
default:
- Log.i(TAG, "Unsupported entry type selected");
+ Slog.w(TAG, "Unsupported entry type selected");
invokeCallbackOnInternalInvalidState();
}
}
@@ -268,7 +264,7 @@
if (credentialResponse != null) {
mCallbacks.onFinalResponseReceived(mComponentName, credentialResponse);
} else {
- Log.i(TAG, "onSaveEntrySelected - no response or error found in pending "
+ Slog.w(TAG, "onSaveEntrySelected - no response or error found in pending "
+ "intent response");
invokeCallbackOnInternalInvalidState();
}
@@ -284,14 +280,14 @@
private CreateCredentialException maybeGetPendingIntentException(
ProviderPendingIntentResponse pendingIntentResponse) {
if (pendingIntentResponse == null) {
- Log.i(TAG, "pendingIntentResponse is null");
+ Slog.w(TAG, "pendingIntentResponse is null");
return new CreateCredentialException(CreateCredentialException.TYPE_NO_CREATE_OPTIONS);
}
if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
CreateCredentialException exception = PendingIntentResultHandler
.extractCreateCredentialException(pendingIntentResponse.getResultData());
if (exception != null) {
- Log.i(TAG, "Pending intent contains provider exception");
+ Slog.d(TAG, "Pending intent contains provider exception");
return exception;
}
} else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
@@ -343,7 +339,7 @@
public void setRemoteEntry(@Nullable RemoteEntry remoteEntry) {
if (!enforceRemoteEntryRestrictions(mExpectedRemoteEntryProviderService)) {
- Log.i(TAG, "Remote entry being dropped as it does not meet the restriction"
+ Slog.w(TAG, "Remote entry being dropped as it does not meet the restriction"
+ "checks.");
return;
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 8d3d064..0c2b563 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -115,7 +115,8 @@
getRequestSession.mHybridService
);
}
- Log.i(TAG, "Unable to create provider session");
+ Slog.d(TAG, "Unable to create provider session for: "
+ + providerInfo.getComponentName());
return null;
}
@@ -146,17 +147,15 @@
android.credentials.GetCredentialRequest clientRequest,
CredentialProviderInfo info
) {
+ Slog.d(TAG, "Filtering request options for: " + info.getComponentName());
List<CredentialOption> filteredOptions = new ArrayList<>();
for (CredentialOption option : clientRequest.getCredentialOptions()) {
if (providerCapabilities.contains(option.getType())
&& isProviderAllowed(option, info.getComponentName())
&& checkSystemProviderRequirement(option, info.isSystemProvider())) {
- Log.i(TAG, "In createProviderRequest - capability found : "
- + option.getType());
+ Slog.d(TAG, "Option of type: " + option.getType() + " meets all filtering"
+ + "conditions");
filteredOptions.add(option);
- } else {
- Log.i(TAG, "In createProviderRequest - capability not "
- + "found, or provider not allowed : " + option.getType());
}
}
if (!filteredOptions.isEmpty()) {
@@ -165,15 +164,14 @@
.setCredentialOptions(
filteredOptions).build();
}
- Log.i(TAG, "In createProviderRequest - returning null");
+ Slog.d(TAG, "No options filtered");
return null;
}
private static boolean isProviderAllowed(CredentialOption option, ComponentName componentName) {
if (!option.getAllowedProviders().isEmpty() && !option.getAllowedProviders().contains(
componentName)) {
- Log.d(TAG, "Provider allow list specified but does not contain this provider: "
- + componentName.flattenToString());
+ Slog.d(TAG, "Provider allow list specified but does not contain this provider");
return false;
}
return true;
@@ -182,7 +180,7 @@
private static boolean checkSystemProviderRequirement(CredentialOption option,
boolean isSystemProvider) {
if (option.isSystemProviderRequired() && !isSystemProvider) {
- Log.d(TAG, "System provider required, but this service is not a system provider");
+ Slog.d(TAG, "System provider required, but this service is not a system provider");
return false;
}
return true;
@@ -210,6 +208,7 @@
/** Called when the provider response has been updated by an external source. */
@Override // Callback from the remote provider
public void onProviderResponseSuccess(@Nullable BeginGetCredentialResponse response) {
+ Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName);
onSetInitialRemoteResponse(response);
}
@@ -231,7 +230,7 @@
updateStatusAndInvokeCallback(Status.SERVICE_DEAD,
/*source=*/ CredentialsSource.REMOTE_PROVIDER);
} else {
- Slog.i(TAG, "Component names different in onProviderServiceDied - "
+ Slog.w(TAG, "Component names different in onProviderServiceDied - "
+ "this should not happen");
}
}
@@ -244,13 +243,14 @@
@Override // Selection call from the request provider
protected void onUiEntrySelected(String entryType, String entryKey,
ProviderPendingIntentResponse providerPendingIntentResponse) {
- Log.i(TAG, "onUiEntrySelected with entryKey: " + entryKey);
+ Slog.d(TAG, "onUiEntrySelected with entryType: " + entryType + ", and entryKey: "
+ + entryKey);
switch (entryType) {
case CREDENTIAL_ENTRY_KEY:
CredentialEntry credentialEntry = mProviderResponseDataHandler
.getCredentialEntry(entryKey);
if (credentialEntry == null) {
- Log.i(TAG, "Unexpected credential entry key");
+ Slog.w(TAG, "Unexpected credential entry key");
invokeCallbackOnInternalInvalidState();
return;
}
@@ -259,7 +259,7 @@
case ACTION_ENTRY_KEY:
Action actionEntry = mProviderResponseDataHandler.getActionEntry(entryKey);
if (actionEntry == null) {
- Log.i(TAG, "Unexpected action entry key");
+ Slog.w(TAG, "Unexpected action entry key");
invokeCallbackOnInternalInvalidState();
return;
}
@@ -269,21 +269,21 @@
Action authenticationEntry = mProviderResponseDataHandler
.getAuthenticationAction(entryKey);
if (authenticationEntry == null) {
- Log.i(TAG, "Unexpected authenticationEntry key");
+ Slog.w(TAG, "Unexpected authenticationEntry key");
invokeCallbackOnInternalInvalidState();
return;
}
boolean additionalContentReceived =
onAuthenticationEntrySelected(providerPendingIntentResponse);
if (additionalContentReceived) {
- Log.i(TAG, "Additional content received - removing authentication entry");
+ Slog.d(TAG, "Additional content received - removing authentication entry");
mProviderResponseDataHandler.removeAuthenticationAction(entryKey);
if (!mProviderResponseDataHandler.isEmptyResponse()) {
updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED,
/*source=*/ CredentialsSource.AUTH_ENTRY);
}
} else {
- Log.i(TAG, "Additional content not received");
+ Slog.d(TAG, "Additional content not received from authentication entry");
mProviderResponseDataHandler
.updateAuthEntryWithNoCredentialsReceived(entryKey);
updateStatusAndInvokeCallback(Status.NO_CREDENTIALS_FROM_AUTH_ENTRY,
@@ -294,12 +294,12 @@
if (mProviderResponseDataHandler.getRemoteEntry(entryKey) != null) {
onRemoteEntrySelected(providerPendingIntentResponse);
} else {
- Log.i(TAG, "Unexpected remote entry key");
+ Slog.d(TAG, "Unexpected remote entry key");
invokeCallbackOnInternalInvalidState();
}
break;
default:
- Log.i(TAG, "Unsupported entry type selected");
+ Slog.w(TAG, "Unsupported entry type selected");
invokeCallbackOnInternalInvalidState();
}
}
@@ -320,26 +320,24 @@
@Override // Call from request session to data to be shown on the UI
@Nullable
protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException {
- Log.i(TAG, "In prepareUiData");
if (!ProviderSession.isUiInvokingStatus(getStatus())) {
- Log.i(TAG, "In prepareUiData - provider does not want to show UI: "
- + mComponentName.flattenToString());
+ Slog.d(TAG, "No data for UI from: " + mComponentName.flattenToString());
return null;
}
if (mProviderResponse != null && !mProviderResponseDataHandler.isEmptyResponse()) {
return mProviderResponseDataHandler.toGetCredentialProviderData();
}
- Log.i(TAG, "In prepareUiData response null");
+ Slog.d(TAG, "In prepareUiData response null");
return null;
}
- private Intent setUpFillInIntent(@NonNull String id) {
+ private Intent setUpFillInIntentWithFinalRequest(@NonNull String id) {
// TODO: Determine if we should skip this entry if entry id is not set, or is set
// but does not resolve to a valid option. For now, not skipping it because
// it may be possible that the provider adds their own extras and expects to receive
// those and complete the flow.
if (mBeginGetOptionToCredentialOptionMap.get(id) == null) {
- Log.i(TAG, "Id from Credential Entry does not resolve to a valid option");
+ Slog.w(TAG, "Id from Credential Entry does not resolve to a valid option");
return new Intent();
}
return new Intent().putExtra(CredentialProviderService.EXTRA_GET_CREDENTIAL_REQUEST,
@@ -382,7 +380,8 @@
getCredentialResponse);
return;
}
- Log.i(TAG, "Pending intent response contains no credential, or error");
+ Slog.d(TAG, "Pending intent response contains no credential, or error "
+ + "for a credential entry");
invokeCallbackOnInternalInvalidState();
}
@@ -390,14 +389,12 @@
private GetCredentialException maybeGetPendingIntentException(
ProviderPendingIntentResponse pendingIntentResponse) {
if (pendingIntentResponse == null) {
- Log.i(TAG, "pendingIntentResponse is null");
return null;
}
if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
GetCredentialException exception = PendingIntentResultHandler
.extractGetCredentialException(pendingIntentResponse.getResultData());
if (exception != null) {
- Log.i(TAG, "Pending intent contains provider exception");
return exception;
}
} else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
@@ -463,7 +460,7 @@
/** Returns true if either an exception or a response is found. */
private void onActionEntrySelected(ProviderPendingIntentResponse
providerPendingIntentResponse) {
- Log.i(TAG, "onActionEntrySelected");
+ Slog.d(TAG, "onActionEntrySelected");
onCredentialEntrySelected(providerPendingIntentResponse);
}
@@ -559,7 +556,8 @@
String id = generateUniqueId();
Entry entry = new Entry(CREDENTIAL_ENTRY_KEY,
id, credentialEntry.getSlice(),
- setUpFillInIntent(credentialEntry.getBeginGetCredentialOptionId()));
+ setUpFillInIntentWithFinalRequest(credentialEntry
+ .getBeginGetCredentialOptionId()));
mUiCredentialEntries.put(id, new Pair<>(credentialEntry, entry));
mCredentialEntryTypes.add(credentialEntry.getType());
}
@@ -574,9 +572,7 @@
public void addAuthenticationAction(Action authenticationAction,
@AuthenticationEntry.Status int status) {
- Log.i(TAG, "In addAuthenticationAction");
String id = generateUniqueId();
- Log.i(TAG, "In addAuthenticationAction, id : " + id);
AuthenticationEntry entry = new AuthenticationEntry(
AUTHENTICATION_ACTION_ENTRY_KEY,
id, authenticationAction.getSlice(),
@@ -591,7 +587,7 @@
public void setRemoteEntry(@Nullable RemoteEntry remoteEntry) {
if (!enforceRemoteEntryRestrictions(mExpectedRemoteEntryProviderService)) {
- Log.i(TAG, "Remote entry being dropped as it does not meet the restriction"
+ Slog.w(TAG, "Remote entry being dropped as it does not meet the restriction"
+ " checks.");
return;
}
@@ -715,7 +711,6 @@
== AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT)
.findFirst();
if (previousMostRecentAuthEntry.isEmpty()) {
- Log.i(TAG, "In updatePreviousMostRecentAuthEntry - previous entry not found");
return;
}
String id = previousMostRecentAuthEntry.get().getKey();
diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
index 24292ef2..c10f564 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
@@ -33,7 +33,7 @@
import android.service.credentials.CallingAppInfo;
import android.service.credentials.CredentialEntry;
import android.service.credentials.CredentialProviderService;
-import android.telecom.Log;
+import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -145,14 +145,12 @@
private List<Entry> prepareUiCredentialEntries(
@NonNull List<CredentialEntry> credentialEntries) {
- Log.i(TAG, "in prepareUiProviderDataWithCredentials");
List<Entry> credentialUiEntries = new ArrayList<>();
// Populate the credential entries
for (CredentialEntry credentialEntry : credentialEntries) {
String entryId = generateUniqueId();
mUiCredentialEntries.put(entryId, credentialEntry);
- Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
credentialEntry.getSlice(),
setUpFillInIntent()));
@@ -172,15 +170,13 @@
@Override
protected ProviderData prepareUiData() {
- Log.i(TAG, "In prepareUiData");
if (!ProviderSession.isUiInvokingStatus(getStatus())) {
- Log.i(TAG, "In prepareUiData - provider does not want to show UI: "
- + mComponentName.flattenToString());
+ Slog.d(TAG, "No date for UI coming from: " + mComponentName.flattenToString());
return null;
}
if (mProviderResponse == null) {
- Log.i(TAG, "In prepareUiData response null");
- throw new IllegalStateException("Response must be in completion mode");
+ Slog.w(TAG, "In prepareUiData but response is null. This is strange.");
+ return null;
}
return new GetCredentialProviderData.Builder(
mComponentName.flattenToString()).setActionChips(null)
@@ -200,13 +196,13 @@
case CREDENTIAL_ENTRY_KEY:
CredentialEntry credentialEntry = mUiCredentialEntries.get(entryKey);
if (credentialEntry == null) {
- Log.i(TAG, "Unexpected credential entry key");
+ Slog.w(TAG, "Unexpected credential entry key");
return;
}
onCredentialEntrySelected(credentialEntry, providerPendingIntentResponse);
break;
default:
- Log.i(TAG, "Unsupported entry type selected");
+ Slog.w(TAG, "Unsupported entry type selected");
}
}
@@ -233,10 +229,8 @@
}
return;
}
-
- Log.i(TAG, "Pending intent response contains no credential, or error");
}
- Log.i(TAG, "CredentialEntry does not have a credential or a pending intent result");
+ Slog.w(TAG, "CredentialEntry does not have a credential or a pending intent result");
}
@Override
@@ -279,14 +273,13 @@
protected GetCredentialException maybeGetPendingIntentException(
ProviderPendingIntentResponse pendingIntentResponse) {
if (pendingIntentResponse == null) {
- android.util.Log.i(TAG, "pendingIntentResponse is null");
return null;
}
if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
GetCredentialException exception = PendingIntentResultHandler
.extractGetCredentialException(pendingIntentResponse.getResultData());
if (exception != null) {
- android.util.Log.i(TAG, "Pending intent contains provider exception");
+ Slog.d(TAG, "Pending intent contains provider exception");
return exception;
}
} else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index d165756..d02a8c1 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -29,7 +29,6 @@
import android.credentials.ui.ProviderPendingIntentResponse;
import android.os.ICancellationSignal;
import android.os.RemoteException;
-import android.util.Log;
import android.util.Slog;
import com.android.server.credentials.metrics.ProviderSessionMetric;
@@ -253,7 +252,7 @@
@Nullable ComponentName expectedRemoteEntryProviderService) {
// Check if the service is the one set by the OEM. If not silently reject this entry
if (!mComponentName.equals(expectedRemoteEntryProviderService)) {
- Log.i(TAG, "Remote entry being dropped as it is not from the service "
+ Slog.w(TAG, "Remote entry being dropped as it is not from the service "
+ "configured by the OEM.");
return false;
}
@@ -270,15 +269,12 @@
return true;
}
} catch (SecurityException e) {
- Log.i(TAG, "Error getting info for "
- + mComponentName.flattenToString() + ": " + e.getMessage());
+ Slog.e(TAG, "Error getting info for " + mComponentName.flattenToString(), e);
return false;
} catch (PackageManager.NameNotFoundException e) {
- Log.i(TAG, "Error getting info for "
- + mComponentName.flattenToString() + ": " + e.getMessage());
+ Slog.i(TAG, "Error getting info for " + mComponentName.flattenToString(), e);
return false;
}
- Log.i(TAG, "In enforceRemoteEntryRestrictions - remote entry checks fail");
return false;
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 492d477..b1d6131 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -125,6 +125,7 @@
import com.android.server.connectivity.PacProxyService;
import com.android.server.contentcapture.ContentCaptureManagerInternal;
import com.android.server.coverage.CoverageService;
+import com.android.server.cpu.CpuMonitorService;
import com.android.server.devicepolicy.DevicePolicyManagerService;
import com.android.server.devicestate.DeviceStateManagerService;
import com.android.server.display.DisplayManagerService;
@@ -1405,6 +1406,15 @@
mSystemServiceManager.startService(RemoteProvisioningService.class);
t.traceEnd();
+ // TODO(b/277600174): Start CpuMonitorService on all builds and not just on debuggable
+ // builds once the Android JobScheduler starts using this service.
+ if (Build.IS_DEBUGGABLE || Build.IS_ENG) {
+ // Service for CPU monitor.
+ t.traceBegin("CpuMonitorService");
+ mSystemServiceManager.startService(CpuMonitorService.class);
+ t.traceEnd();
+ }
+
t.traceEnd(); // startCoreServices
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 8211d6f..ec177c9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -463,7 +463,8 @@
assertEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason());
// Bumping past barrier makes us now runnable
- airplaneRecord.terminalCount++;
+ airplaneRecord.setDeliveryState(0, BroadcastRecord.DELIVERY_DELIVERED,
+ "testRunnableAt_Ordered");
queue.invalidateRunnableAt();
assertTrue(queue.isRunnable());
assertNotEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason());
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index b6bc02a..90e6a2d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -16,6 +16,8 @@
package com.android.server.am;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
import static android.os.UserHandle.USER_SYSTEM;
import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO;
@@ -39,7 +41,6 @@
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -463,7 +464,7 @@
doAnswer((invocation) -> {
Log.v(TAG, "Intercepting scheduleReceiver() for "
- + Arrays.toString(invocation.getArguments()));
+ + Arrays.toString(invocation.getArguments()) + " package " + ai.packageName);
assertHealth();
final Intent intent = invocation.getArgument(0);
final Bundle extras = invocation.getArgument(5);
@@ -485,7 +486,7 @@
doAnswer((invocation) -> {
Log.v(TAG, "Intercepting scheduleRegisteredReceiver() for "
- + Arrays.toString(invocation.getArguments()));
+ + Arrays.toString(invocation.getArguments()) + " package " + ai.packageName);
assertHealth();
final Intent intent = invocation.getArgument(1);
final Bundle extras = invocation.getArgument(4);
@@ -961,7 +962,7 @@
} else {
// Confirm that app was thawed
verify(mAms.mOomAdjuster, atLeastOnce()).unfreezeTemporarily(
- eq(receiverApp), eq(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER));
+ eq(receiverApp), eq(OOM_ADJ_REASON_START_RECEIVER));
// Confirm that we added package to process
verify(receiverApp, atLeastOnce()).addPackage(eq(receiverApp.info.packageName),
@@ -1404,7 +1405,7 @@
// Finally, verify that we thawed the final receiver
verify(mAms.mOomAdjuster).unfreezeTemporarily(eq(callerApp),
- eq(OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER));
+ eq(OOM_ADJ_REASON_FINISH_RECEIVER));
}
/**
@@ -1980,6 +1981,46 @@
}
/**
+ * Confirm how many times a pathological broadcast pattern results in OOM
+ * adjusts; watches for performance regressions.
+ */
+ @Test
+ public void testOomAdjust_TriggerCount() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+
+ // Send 8 broadcasts, 4 receivers in the first process,
+ // and 2 alternating in each of the remaining processes
+ synchronized (mAms) {
+ for (int i = 0; i < 8; i++) {
+ final Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+ mQueue.enqueueBroadcastLocked(makeBroadcastRecord(intent, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+ makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+ makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+ makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+ makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
+ makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW),
+ makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
+ makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW))));
+ }
+ }
+ waitForIdle();
+
+ final int expectedTimes;
+ switch (mImpl) {
+ // Original stack requested for every single receiver; yikes
+ case DEFAULT: expectedTimes = 64; break;
+ // Modern stack requests once each time we promote a process to
+ // running; we promote "green" twice, and "blue" and "yellow" once
+ case MODERN: expectedTimes = 4; break;
+ default: throw new UnsupportedOperationException();
+ }
+
+ verify(mAms, times(expectedTimes))
+ .updateOomAdjPendingTargetsLocked(eq(OOM_ADJ_REASON_START_RECEIVER));
+ }
+
+ /**
* Verify that expected events are triggered when a broadcast is finished.
*/
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index 2b6f217..08952ea 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -24,7 +24,12 @@
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_ALL;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
-import static com.android.server.am.BroadcastRecord.calculateBlockedUntilTerminalCount;
+import static com.android.server.am.BroadcastRecord.DELIVERY_DEFERRED;
+import static com.android.server.am.BroadcastRecord.DELIVERY_DELIVERED;
+import static com.android.server.am.BroadcastRecord.DELIVERY_PENDING;
+import static com.android.server.am.BroadcastRecord.DELIVERY_SKIPPED;
+import static com.android.server.am.BroadcastRecord.DELIVERY_TIMEOUT;
+import static com.android.server.am.BroadcastRecord.calculateBlockedUntilBeyondCount;
import static com.android.server.am.BroadcastRecord.calculateDeferUntilActive;
import static com.android.server.am.BroadcastRecord.calculateUrgent;
import static com.android.server.am.BroadcastRecord.isReceiverEquals;
@@ -58,7 +63,6 @@
import com.android.server.am.BroadcastDispatcher.DeferredBootCompletedBroadcastPerUser;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -79,6 +83,7 @@
@SmallTest
@RunWith(MockitoJUnitRunner.class)
public class BroadcastRecordTest {
+ private static final String TAG = "BroadcastRecordTest";
private static final int USER0 = UserHandle.USER_SYSTEM;
private static final int USER1 = USER0 + 1;
@@ -120,13 +125,13 @@
assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), 10))));
assertArrayEquals(new int[] {-1},
- calculateBlockedUntilTerminalCount(List.of(
+ calculateBlockedUntilBeyondCount(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 0)), false));
assertArrayEquals(new int[] {-1},
- calculateBlockedUntilTerminalCount(List.of(
+ calculateBlockedUntilBeyondCount(List.of(
createResolveInfo(PACKAGE1, getAppId(1), -10)), false));
assertArrayEquals(new int[] {-1},
- calculateBlockedUntilTerminalCount(List.of(
+ calculateBlockedUntilBeyondCount(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 10)), false));
}
@@ -142,12 +147,12 @@
createResolveInfo(PACKAGE3, getAppId(3), 10))));
assertArrayEquals(new int[] {-1,-1,-1},
- calculateBlockedUntilTerminalCount(List.of(
+ calculateBlockedUntilBeyondCount(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 0),
createResolveInfo(PACKAGE2, getAppId(2), 0),
createResolveInfo(PACKAGE3, getAppId(3), 0)), false));
assertArrayEquals(new int[] {-1,-1,-1},
- calculateBlockedUntilTerminalCount(List.of(
+ calculateBlockedUntilBeyondCount(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 10),
createResolveInfo(PACKAGE2, getAppId(2), 10),
createResolveInfo(PACKAGE3, getAppId(3), 10)), false));
@@ -156,26 +161,176 @@
@Test
public void testIsPrioritized_Yes() {
assertTrue(isPrioritized(List.of(
- createResolveInfo(PACKAGE1, getAppId(1), -10),
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
createResolveInfo(PACKAGE2, getAppId(2), 0),
- createResolveInfo(PACKAGE3, getAppId(3), 10))));
+ createResolveInfo(PACKAGE3, getAppId(3), -10))));
assertTrue(isPrioritized(List.of(
- createResolveInfo(PACKAGE1, getAppId(1), 0),
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
createResolveInfo(PACKAGE2, getAppId(2), 0),
- createResolveInfo(PACKAGE3, getAppId(3), 10))));
+ createResolveInfo(PACKAGE3, getAppId(3), 0))));
assertArrayEquals(new int[] {0,1,2},
- calculateBlockedUntilTerminalCount(List.of(
- createResolveInfo(PACKAGE1, getAppId(1), -10),
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
createResolveInfo(PACKAGE2, getAppId(2), 0),
- createResolveInfo(PACKAGE3, getAppId(3), 10)), false));
+ createResolveInfo(PACKAGE3, getAppId(3), -10)), false));
assertArrayEquals(new int[] {0,0,2,3,3},
- calculateBlockedUntilTerminalCount(List.of(
- createResolveInfo(PACKAGE1, getAppId(1), 0),
- createResolveInfo(PACKAGE2, getAppId(2), 0),
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 20),
+ createResolveInfo(PACKAGE2, getAppId(2), 20),
createResolveInfo(PACKAGE3, getAppId(3), 10),
- createResolveInfo(PACKAGE3, getAppId(3), 20),
- createResolveInfo(PACKAGE3, getAppId(3), 20)), false));
+ createResolveInfo(PACKAGE3, getAppId(3), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0)), false));
+ }
+
+ @Test
+ public void testSetDeliveryState_Single() {
+ final BroadcastRecord r = createBroadcastRecord(
+ new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
+ createResolveInfoWithPriority(0)));
+ assertEquals(DELIVERY_PENDING, r.getDeliveryState(0));
+ assertBlocked(r, false);
+ assertTerminalDeferredBeyond(r, 0, 0, 0);
+
+ r.setDeliveryState(0, DELIVERY_DEFERRED, TAG);
+ assertEquals(DELIVERY_DEFERRED, r.getDeliveryState(0));
+ assertBlocked(r, false);
+ assertTerminalDeferredBeyond(r, 0, 1, 1);
+
+ // Identical state change has no effect
+ r.setDeliveryState(0, DELIVERY_DEFERRED, TAG);
+ assertEquals(DELIVERY_DEFERRED, r.getDeliveryState(0));
+ assertBlocked(r, false);
+ assertTerminalDeferredBeyond(r, 0, 1, 1);
+
+ // Moving to terminal state updates counters
+ r.setDeliveryState(0, DELIVERY_DELIVERED, TAG);
+ assertEquals(DELIVERY_DELIVERED, r.getDeliveryState(0));
+ assertBlocked(r, false);
+ assertTerminalDeferredBeyond(r, 1, 0, 1);
+
+ // Trying to change terminal state has no effect
+ r.setDeliveryState(0, DELIVERY_TIMEOUT, TAG);
+ assertEquals(DELIVERY_DELIVERED, r.getDeliveryState(0));
+ assertBlocked(r, false);
+ assertTerminalDeferredBeyond(r, 1, 0, 1);
+ }
+
+ @Test
+ public void testSetDeliveryState_Unordered() {
+ final BroadcastRecord r = createBroadcastRecord(
+ new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
+ createResolveInfoWithPriority(0),
+ createResolveInfoWithPriority(0),
+ createResolveInfoWithPriority(0)));
+ assertBlocked(r, false, false, false);
+ assertTerminalDeferredBeyond(r, 0, 0, 0);
+
+ // Even though we finish a middle item in the tranche, we're not
+ // "beyond" it because there is still unfinished work before it
+ r.setDeliveryState(1, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false);
+ assertTerminalDeferredBeyond(r, 1, 0, 0);
+
+ r.setDeliveryState(0, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false);
+ assertTerminalDeferredBeyond(r, 2, 0, 2);
+
+ r.setDeliveryState(2, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false);
+ assertTerminalDeferredBeyond(r, 3, 0, 3);
+ }
+
+ @Test
+ public void testSetDeliveryState_Ordered() {
+ final BroadcastRecord r = createOrderedBroadcastRecord(
+ new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
+ createResolveInfoWithPriority(0),
+ createResolveInfoWithPriority(0),
+ createResolveInfoWithPriority(0)));
+ assertBlocked(r, false, true, true);
+ assertTerminalDeferredBeyond(r, 0, 0, 0);
+
+ r.setDeliveryState(0, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, true);
+ assertTerminalDeferredBeyond(r, 1, 0, 1);
+
+ r.setDeliveryState(1, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false);
+ assertTerminalDeferredBeyond(r, 2, 0, 2);
+
+ r.setDeliveryState(2, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false);
+ assertTerminalDeferredBeyond(r, 3, 0, 3);
+ }
+
+ @Test
+ public void testSetDeliveryState_DeferUntilActive() {
+ final BroadcastRecord r = createBroadcastRecord(
+ new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
+ createResolveInfoWithPriority(10),
+ createResolveInfoWithPriority(10),
+ createResolveInfoWithPriority(10),
+ createResolveInfoWithPriority(0),
+ createResolveInfoWithPriority(0),
+ createResolveInfoWithPriority(0),
+ createResolveInfoWithPriority(-10),
+ createResolveInfoWithPriority(-10),
+ createResolveInfoWithPriority(-10)));
+ assertBlocked(r, false, false, false, true, true, true, true, true, true);
+ assertTerminalDeferredBeyond(r, 0, 0, 0);
+
+ r.setDeliveryState(0, DELIVERY_PENDING, TAG);
+ r.setDeliveryState(1, DELIVERY_DEFERRED, TAG);
+ r.setDeliveryState(2, DELIVERY_PENDING, TAG);
+ r.setDeliveryState(3, DELIVERY_DEFERRED, TAG);
+ r.setDeliveryState(4, DELIVERY_DEFERRED, TAG);
+ r.setDeliveryState(5, DELIVERY_DEFERRED, TAG);
+ r.setDeliveryState(6, DELIVERY_DEFERRED, TAG);
+ r.setDeliveryState(7, DELIVERY_PENDING, TAG);
+ r.setDeliveryState(8, DELIVERY_DEFERRED, TAG);
+
+ // Verify deferred counts ratchet up, but we're not "beyond" the first
+ // still-pending receiver
+ assertBlocked(r, false, false, false, true, true, true, true, true, true);
+ assertTerminalDeferredBeyond(r, 0, 6, 0);
+
+ // We're still not "beyond" the first still-pending receiver, even when
+ // we finish a receiver later in the first tranche
+ r.setDeliveryState(2, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false, true, true, true, true, true, true);
+ assertTerminalDeferredBeyond(r, 1, 6, 0);
+
+ // Completing that last item in first tranche means we now unblock the
+ // second tranche, and since it's entirely deferred, the third traunche
+ // is unblocked too
+ r.setDeliveryState(0, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false, false, false, false, false, false, false);
+ assertTerminalDeferredBeyond(r, 2, 6, 7);
+
+ // Moving a deferred item in an earlier tranche back to being pending
+ // doesn't change the fact that we've already moved beyond it
+ r.setDeliveryState(1, DELIVERY_PENDING, TAG);
+ assertBlocked(r, false, false, false, false, false, false, false, false, false);
+ assertTerminalDeferredBeyond(r, 2, 5, 7);
+ r.setDeliveryState(1, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false, false, false, false, false, false, false);
+ assertTerminalDeferredBeyond(r, 3, 5, 7);
+
+ // Completing middle pending item is enough to fast-forward to end
+ r.setDeliveryState(7, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false, false, false, false, false, false, false);
+ assertTerminalDeferredBeyond(r, 4, 5, 9);
+
+ // Moving everyone else directly into a finished state updates all the
+ // terminal counters
+ r.setDeliveryState(3, DELIVERY_SKIPPED, TAG);
+ r.setDeliveryState(4, DELIVERY_SKIPPED, TAG);
+ r.setDeliveryState(5, DELIVERY_SKIPPED, TAG);
+ r.setDeliveryState(6, DELIVERY_SKIPPED, TAG);
+ r.setDeliveryState(8, DELIVERY_SKIPPED, TAG);
+ assertBlocked(r, false, false, false, false, false, false, false, false, false);
+ assertTerminalDeferredBeyond(r, 9, 0, 9);
}
@Test
@@ -688,6 +843,10 @@
: errorMsg.insert(0, "Contains unexpected receiver: ").toString();
}
+ private static ResolveInfo createResolveInfoWithPriority(int priority) {
+ return createResolveInfo(PACKAGE1, getAppId(1), priority);
+ }
+
private static ResolveInfo createResolveInfo(String packageName, int uid) {
return createResolveInfo(packageName, uid, 0);
}
@@ -738,21 +897,40 @@
return excludedList;
}
+ private BroadcastRecord createBroadcastRecord(Intent intent,
+ List<ResolveInfo> receivers) {
+ return createBroadcastRecord(receivers, USER0, intent, null /* filterExtrasForReceiver */,
+ null /* options */, false);
+ }
+
+ private BroadcastRecord createOrderedBroadcastRecord(Intent intent,
+ List<ResolveInfo> receivers) {
+ return createBroadcastRecord(receivers, USER0, intent, null /* filterExtrasForReceiver */,
+ null /* options */, true);
+ }
+
private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
Intent intent) {
return createBroadcastRecord(receivers, userId, intent, null /* filterExtrasForReceiver */,
- null /* options */);
+ null /* options */, false);
}
private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
Intent intent, BroadcastOptions options) {
return createBroadcastRecord(receivers, userId, intent, null /* filterExtrasForReceiver */,
- options);
+ options, false);
}
private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
Intent intent, BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
BroadcastOptions options) {
+ return createBroadcastRecord(receivers, userId, intent, filterExtrasForReceiver,
+ options, false);
+ }
+
+ private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
+ Intent intent, BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+ BroadcastOptions options, boolean ordered) {
return new BroadcastRecord(
mQueue /* queue */,
intent,
@@ -774,7 +952,7 @@
0 /* resultCode */,
null /* resultData */,
null /* resultExtras */,
- false /* serialized */,
+ ordered /* serialized */,
false /* sticky */,
false /* initialSticky */,
userId,
@@ -789,6 +967,20 @@
private static boolean isPrioritized(List<Object> receivers) {
return BroadcastRecord.isPrioritized(
- calculateBlockedUntilTerminalCount(receivers, false), false);
+ calculateBlockedUntilBeyondCount(receivers, false), false);
+ }
+
+ private static void assertBlocked(BroadcastRecord r, boolean... blocked) {
+ assertEquals(r.receivers.size(), blocked.length);
+ for (int i = 0; i < blocked.length; i++) {
+ assertEquals("blocked " + i, blocked[i], r.isBlocked(i));
+ }
+ }
+
+ private static void assertTerminalDeferredBeyond(BroadcastRecord r,
+ int expectedTerminalCount, int expectedDeferredCount, int expectedBeyondCount) {
+ assertEquals("terminal", expectedTerminalCount, r.terminalCount);
+ assertEquals("deferred", expectedDeferredCount, r.deferredCount);
+ assertEquals("beyond", expectedBeyondCount, r.beyondCount);
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 485ce33..cda5456 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -38,11 +38,12 @@
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_ACTIVITY;
import static com.android.server.am.ProcessList.BACKUP_APP_ADJ;
import static com.android.server.am.ProcessList.CACHED_APP_MAX_ADJ;
import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ;
@@ -254,12 +255,13 @@
* - If there's only one process, then it calls updateOomAdjLocked(ProcessRecord, int).
* - Otherwise, sets the processes to the LRU and run updateOomAdjLocked(int).
*/
+ @SuppressWarnings("GuardedBy")
private void updateOomAdj(ProcessRecord... apps) {
if (apps.length == 1) {
- sService.mOomAdjuster.updateOomAdjLocked(apps[0], OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(apps[0], OOM_ADJ_REASON_NONE);
} else {
setProcessesToLru(apps);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
sService.mProcessList.getLruProcessesLOSP().clear();
}
}
@@ -658,7 +660,7 @@
ServiceRecord s = bindService(app, system,
null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND,
PERCEPTIBLE_APP_ADJ + 1, SCHED_GROUP_DEFAULT);
@@ -1226,7 +1228,7 @@
mock(IBinder.class));
client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj());
}
@@ -1243,7 +1245,7 @@
mock(IBinder.class));
client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
doReturn(false).when(wpc).isHeavyWeightProcess();
assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj());
@@ -1497,7 +1499,7 @@
client2.mServices.setHasForegroundServices(false, 0, /* hasNoneType=*/false);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(client2, OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(client2, OOM_ADJ_REASON_NONE);
assertEquals(PROCESS_STATE_CACHED_EMPTY, client2.mState.getSetProcState());
assertEquals(PROCESS_STATE_CACHED_EMPTY, client.mState.getSetProcState());
@@ -1919,7 +1921,7 @@
doReturn(client2).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(app2, OOM_ADJ_REASON_NONE);
assertProcStates(app2, PROCESS_STATE_BOUND_TOP, VISIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
}
@@ -2029,7 +2031,7 @@
setServiceMap(s3, MOCKAPP5_UID, cn3);
setServiceMap(c2s, MOCKAPP3_UID, cn4);
app2UidRecord.setIdle(false);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -2055,7 +2057,7 @@
anyInt(), anyBoolean(), anyBoolean(), anyBoolean());
doNothing().when(sService.mServices)
.scheduleServiceTimeoutLocked(any(ProcessRecord.class));
- sService.mOomAdjuster.updateOomAdjLocked(client1, OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(client1, OOM_ADJ_REASON_NONE);
assertEquals(PROCESS_STATE_CACHED_EMPTY, client1.mState.getSetProcState());
assertEquals(PROCESS_STATE_SERVICE, app1.mState.getSetProcState());
@@ -2427,7 +2429,7 @@
app2.mState.setHasShownUi(false);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-ui-services");
assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj2, "cch-started-services");
@@ -2436,7 +2438,7 @@
app.mState.setAdjType(null);
app.mState.setSetAdj(UNKNOWN_ADJ);
app.mState.setHasShownUi(false);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
@@ -2445,7 +2447,7 @@
app.mState.setAdjType(null);
app.mState.setSetAdj(UNKNOWN_ADJ);
s.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1;
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
@@ -2463,7 +2465,7 @@
s.lastActivity = now;
app.mServices.startService(s);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
@@ -2474,7 +2476,7 @@
app.mState.setSetAdj(UNKNOWN_ADJ);
app.mState.setHasShownUi(false);
s.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1;
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
@@ -2482,7 +2484,7 @@
doReturn(userOther).when(sService.mUserController).getCurrentUserId();
sService.mOomAdjuster.handleUserSwitchedLocked();
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
assertProcStates(app2, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssAntennaInfoProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssAntennaInfoProviderTest.java
new file mode 100644
index 0000000..e1fa8f52
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssAntennaInfoProviderTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 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.location.gnss;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GnssAntennaInfoProviderTest {
+ private @Mock Context mContext;
+ private @Mock LocationManagerInternal mInternal;
+ private @Mock GnssConfiguration mMockConfiguration;
+ private @Mock IBinder mBinder;
+ private GnssNative mGnssNative;
+
+ private GnssAntennaInfoProvider mTestProvider;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+ anyInt());
+ LocalServices.addService(LocationManagerInternal.class, mInternal);
+ FakeGnssHal fakeGnssHal = new FakeGnssHal();
+ GnssNative.setGnssHalForTest(fakeGnssHal);
+ Injector injector = new TestInjector(mContext);
+ mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
+ mTestProvider = new GnssAntennaInfoProvider(mGnssNative);
+ mGnssNative.register();
+ }
+
+ @After
+ public void tearDown() {
+ LocalServices.removeServiceForTest(LocationManagerInternal.class);
+ }
+
+ @Test
+ public void testOnHalStarted() {
+ verify(mGnssNative, times(1)).startAntennaInfoListening();
+ }
+
+ @Test
+ public void testOnHalRestarted() {
+ mTestProvider.onHalRestarted();
+ verify(mGnssNative, times(2)).startAntennaInfoListening();
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java
index fd9dfe8..bf96b1d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java
@@ -74,7 +74,6 @@
private @Mock Context mContext;
private @Mock LocationManagerInternal mInternal;
private @Mock GnssConfiguration mMockConfiguration;
- private @Mock GnssNative.GeofenceCallbacks mGeofenceCallbacks;
private @Mock IGnssMeasurementsListener mListener1;
private @Mock IGnssMeasurementsListener mListener2;
private @Mock IBinder mBinder1;
@@ -98,7 +97,6 @@
Injector injector = new TestInjector(mContext);
mGnssNative = spy(Objects.requireNonNull(
GnssNative.create(injector, mMockConfiguration)));
- mGnssNative.setGeofenceCallbacks(mGeofenceCallbacks);
mTestProvider = new GnssMeasurementsProvider(injector, mGnssNative);
mGnssNative.register();
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNavigationMessageProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNavigationMessageProviderTest.java
new file mode 100644
index 0000000..64aa4b3
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNavigationMessageProviderTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2023 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.location.gnss;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.IGnssNavigationMessageListener;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.location.util.identity.CallerIdentity;
+import android.os.IBinder;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.FakeUserInfoHelper;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+
+public class GnssNavigationMessageProviderTest {
+ private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
+ private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000,
+ "mypackage", "attribution", "listener");
+ private @Mock Context mContext;
+ private @Mock LocationManagerInternal mInternal;
+ private @Mock GnssConfiguration mMockConfiguration;
+ private @Mock IGnssNavigationMessageListener mListener;
+ private @Mock IBinder mBinder;
+
+ private GnssNative mGnssNative;
+
+ private GnssNavigationMessageProvider mTestProvider;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ doReturn(mBinder).when(mListener).asBinder();
+ doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+ anyInt());
+ LocalServices.addService(LocationManagerInternal.class, mInternal);
+ FakeGnssHal fakeGnssHal = new FakeGnssHal();
+ GnssNative.setGnssHalForTest(fakeGnssHal);
+ Injector injector = new TestInjector(mContext);
+ mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
+ mTestProvider = new GnssNavigationMessageProvider(injector, mGnssNative);
+ mGnssNative.register();
+ }
+
+ @After
+ public void tearDown() {
+ LocalServices.removeServiceForTest(LocationManagerInternal.class);
+ }
+
+ @Test
+ public void testAddListener() {
+ // add a request
+ mTestProvider.addListener(IDENTITY, mListener);
+ verify(mGnssNative, times(1)).startNavigationMessageCollection();
+
+ // remove a request
+ mTestProvider.removeListener(mListener);
+ verify(mGnssNative, times(1)).stopNavigationMessageCollection();
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNmeaProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNmeaProviderTest.java
new file mode 100644
index 0000000..49e5e69
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNmeaProviderTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2023 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.location.gnss;
+
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.IGnssNmeaListener;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.location.util.identity.CallerIdentity;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.FakeUserInfoHelper;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GnssNmeaProviderTest {
+
+ private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
+ private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000,
+ "mypackage", "attribution", "listener");
+ private @Mock Context mContext;
+ private @Mock LocationManagerInternal mInternal;
+ private @Mock GnssConfiguration mMockConfiguration;
+ private @Mock IGnssNmeaListener mListener;
+ private @Mock IBinder mBinder;
+
+ private GnssNative mGnssNative;
+
+ private GnssNmeaProvider mTestProvider;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ doReturn(mBinder).when(mListener).asBinder();
+ doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+ anyInt());
+ LocalServices.addService(LocationManagerInternal.class, mInternal);
+ FakeGnssHal fakeGnssHal = new FakeGnssHal();
+ GnssNative.setGnssHalForTest(fakeGnssHal);
+ Injector injector = new TestInjector(mContext);
+ mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
+ mTestProvider = new GnssNmeaProvider(injector, mGnssNative);
+ mGnssNative.register();
+ }
+
+ @After
+ public void tearDown() {
+ LocalServices.removeServiceForTest(LocationManagerInternal.class);
+ }
+
+ @Test
+ public void testAddListener() {
+ // add a request
+ mTestProvider.addListener(IDENTITY, mListener);
+ verify(mGnssNative, times(1)).startNmeaMessageCollection();
+
+ // remove a request
+ mTestProvider.removeListener(mListener);
+ verify(mGnssNative, times(1)).stopNmeaMessageCollection();
+ }
+
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssStatusProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssStatusProviderTest.java
new file mode 100644
index 0000000..ce2aec7f
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssStatusProviderTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2023 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.location.gnss;
+
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.IGnssStatusListener;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.location.util.identity.CallerIdentity;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.FakeUserInfoHelper;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GnssStatusProviderTest {
+ private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
+ private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000,
+ "mypackage", "attribution", "listener");
+ private @Mock Context mContext;
+ private @Mock LocationManagerInternal mInternal;
+ private @Mock GnssConfiguration mMockConfiguration;
+ private @Mock IGnssStatusListener mListener;
+ private @Mock IBinder mBinder;
+
+ private GnssNative mGnssNative;
+
+ private GnssStatusProvider mTestProvider;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ doReturn(mBinder).when(mListener).asBinder();
+ doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+ anyInt());
+ LocalServices.addService(LocationManagerInternal.class, mInternal);
+ FakeGnssHal fakeGnssHal = new FakeGnssHal();
+ GnssNative.setGnssHalForTest(fakeGnssHal);
+ Injector injector = new TestInjector(mContext);
+ mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
+ mTestProvider = new GnssStatusProvider(injector, mGnssNative);
+ mGnssNative.register();
+ }
+
+ @After
+ public void tearDown() {
+ LocalServices.removeServiceForTest(LocationManagerInternal.class);
+ }
+
+ @Test
+ public void testAddListener() {
+ // add a request
+ mTestProvider.addListener(IDENTITY, mListener);
+ verify(mGnssNative, times(1)).startSvStatusCollection();
+
+ // remove a request
+ mTestProvider.removeListener(mListener);
+ verify(mGnssNative, times(1)).stopSvStatusCollection();
+ }
+
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java
index b7ab6f80..2d962ac 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java
@@ -562,6 +562,26 @@
}
@Override
+ protected boolean startSvStatusCollection() {
+ return true;
+ }
+
+ @Override
+ protected boolean stopSvStatusCollection() {
+ return true;
+ }
+
+ @Override
+ public boolean startNmeaMessageCollection() {
+ return true;
+ }
+
+ @Override
+ public boolean stopNmeaMessageCollection() {
+ return true;
+ }
+
+ @Override
protected int getBatchSize() {
return mBatchSize;
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index dbf5021..26a3ae1 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -43,6 +43,7 @@
import android.app.admin.DevicePolicyManager;
import android.app.trust.ITrustManager;
import android.content.Context;
+import android.content.res.Resources;
import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricSensorReceiver;
@@ -52,6 +53,7 @@
import android.hardware.biometrics.SensorProperties;
import android.hardware.face.FaceSensorProperties;
import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Binder;
@@ -83,6 +85,7 @@
private static final long TEST_REQUEST_ID = 22;
@Mock private Context mContext;
+ @Mock private Resources mResources;
@Mock private BiometricContext mBiometricContext;
@Mock private ITrustManager mTrustManager;
@Mock private DevicePolicyManager mDevicePolicyManager;
@@ -104,6 +107,7 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ when(mContext.getResources()).thenReturn(mResources);
when(mClientReceiver.asBinder()).thenReturn(mock(Binder.class));
when(mBiometricContext.updateContext(any(), anyBoolean()))
.thenAnswer(invocation -> invocation.getArgument(0));
@@ -342,6 +346,33 @@
testInvokesCancel(session -> session.onDialogDismissed(DISMISSED_REASON_NEGATIVE, null));
}
+ @Test
+ public void testCallbackOnAcquired() throws RemoteException {
+ final String acquiredStr = "test_acquired_info_callback";
+ final String acquiredStrVendor = "test_acquired_info_callback_vendor";
+ setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_REAR);
+
+ final AuthSession session = createAuthSession(mSensors,
+ false /* checkDevicePolicyManager */,
+ Authenticators.BIOMETRIC_STRONG,
+ TEST_REQUEST_ID,
+ 0 /* operationId */,
+ 0 /* userId */);
+
+ when(mContext.getString(com.android.internal.R.string.fingerprint_acquired_partial))
+ .thenReturn(acquiredStr);
+ session.onAcquired(0, FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL, 0);
+ verify(mStatusBarService).onBiometricHelp(anyInt(), eq(acquiredStr));
+ verify(mClientReceiver).onAcquired(eq(1), eq(acquiredStr));
+
+ when(mResources.getStringArray(com.android.internal.R.array.fingerprint_acquired_vendor))
+ .thenReturn(new String[]{acquiredStrVendor});
+ session.onAcquired(0, FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR, 0);
+ verify(mStatusBarService).onBiometricHelp(anyInt(), eq(acquiredStrVendor));
+ verify(mClientReceiver).onAcquired(
+ eq(FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR_BASE), eq(acquiredStrVendor));
+ }
+
// TODO (b/208484275) : Enable these tests
// @Test
// public void testPreAuth_canAuthAndPrivacyDisabled() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
index 550204b..4cfbb95 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
@@ -105,6 +105,7 @@
mMockPackageManager = mock(PackageManager.class);
mMockPackageMonitor = mock(PackageMonitor.class);
+ doReturn(mMockContext).when(mMockContext).createContextAsUser(any(), anyInt());
// For unit tests, set the default installer info
doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mMockPackageManager)
.getInstallSourceInfo(anyString());
diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
index da9de25..e20f1e7 100644
--- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
@@ -131,6 +131,7 @@
doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
doReturn(InstrumentationRegistry.getContext().getContentResolver())
.when(mMockContext).getContentResolver();
+ doReturn(mMockContext).when(mMockContext).createContextAsUser(any(), anyInt());
mStoragefile = new AtomicFile(new File(
Environment.getExternalStorageDirectory(), "systemUpdateUnitTests.xml"));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
new file mode 100644
index 0000000..bcd807a
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2023 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 static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.provider.Settings;
+import android.service.notification.Condition;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeDiff;
+import android.service.notification.ZenPolicy;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.ArrayMap;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class ZenModeDiffTest extends UiServiceTestCase {
+ // version is not included in the diff; manual & automatic rules have special handling
+ public static final Set<String> ZEN_MODE_CONFIG_EXEMPT_FIELDS =
+ Set.of("version", "manualRule", "automaticRules");
+
+ @Test
+ public void testRuleDiff_addRemoveSame() {
+ // Test add, remove, and both sides same
+ ZenModeConfig.ZenRule r = makeRule();
+
+ // Both sides same rule
+ ZenModeDiff.RuleDiff dSame = new ZenModeDiff.RuleDiff(r, r);
+ assertFalse(dSame.hasDiff());
+
+ // from existent rule to null: expect deleted
+ ZenModeDiff.RuleDiff deleted = new ZenModeDiff.RuleDiff(r, null);
+ assertTrue(deleted.hasDiff());
+ assertTrue(deleted.wasRemoved());
+
+ // from null to new rule: expect added
+ ZenModeDiff.RuleDiff added = new ZenModeDiff.RuleDiff(null, r);
+ assertTrue(added.hasDiff());
+ assertTrue(added.wasAdded());
+ }
+
+ @Test
+ public void testRuleDiff_fieldDiffs() throws Exception {
+ // Start these the same
+ ZenModeConfig.ZenRule r1 = makeRule();
+ ZenModeConfig.ZenRule r2 = makeRule();
+
+ // maps mapping field name -> expected output value as we set diffs
+ ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+ ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+ List<Field> fieldsForDiff = getFieldsForDiffCheck(
+ ZenModeConfig.ZenRule.class, Set.of()); // actually no exempt fields for ZenRule
+ generateFieldDiffs(r1, r2, fieldsForDiff, expectedFrom, expectedTo);
+
+ ZenModeDiff.RuleDiff d = new ZenModeDiff.RuleDiff(r1, r2);
+ assertTrue(d.hasDiff());
+
+ // Now diff them and check that each of the fields has a diff
+ for (Field f : fieldsForDiff) {
+ String name = f.getName();
+ assertNotNull("diff not found for field: " + name, d.getDiffForField(name));
+ assertTrue(d.getDiffForField(name).hasDiff());
+ assertTrue("unexpected field: " + name, expectedFrom.containsKey(name));
+ assertTrue("unexpected field: " + name, expectedTo.containsKey(name));
+ assertEquals(expectedFrom.get(name), d.getDiffForField(name).from());
+ assertEquals(expectedTo.get(name), d.getDiffForField(name).to());
+ }
+ }
+
+ @Test
+ public void testConfigDiff_addRemoveSame() {
+ // Default config, will test add, remove, and no change
+ ZenModeConfig c = new ZenModeConfig();
+
+ ZenModeDiff.ConfigDiff dSame = new ZenModeDiff.ConfigDiff(c, c);
+ assertFalse(dSame.hasDiff());
+
+ ZenModeDiff.ConfigDiff added = new ZenModeDiff.ConfigDiff(null, c);
+ assertTrue(added.hasDiff());
+ assertTrue(added.wasAdded());
+
+ ZenModeDiff.ConfigDiff removed = new ZenModeDiff.ConfigDiff(c, null);
+ assertTrue(removed.hasDiff());
+ assertTrue(removed.wasRemoved());
+ }
+
+ @Test
+ public void testConfigDiff_fieldDiffs() throws Exception {
+ // these two start the same
+ ZenModeConfig c1 = new ZenModeConfig();
+ ZenModeConfig c2 = new ZenModeConfig();
+
+ // maps mapping field name -> expected output value as we set diffs
+ ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+ ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+ List<Field> fieldsForDiff = getFieldsForDiffCheck(
+ ZenModeConfig.class, ZEN_MODE_CONFIG_EXEMPT_FIELDS);
+ generateFieldDiffs(c1, c2, fieldsForDiff, expectedFrom, expectedTo);
+
+ ZenModeDiff.ConfigDiff d = new ZenModeDiff.ConfigDiff(c1, c2);
+ assertTrue(d.hasDiff());
+
+ // Now diff them and check that each of the fields has a diff
+ for (Field f : fieldsForDiff) {
+ String name = f.getName();
+ assertNotNull("diff not found for field: " + name, d.getDiffForField(name));
+ assertTrue(d.getDiffForField(name).hasDiff());
+ assertTrue("unexpected field: " + name, expectedFrom.containsKey(name));
+ assertTrue("unexpected field: " + name, expectedTo.containsKey(name));
+ assertEquals(expectedFrom.get(name), d.getDiffForField(name).from());
+ assertEquals(expectedTo.get(name), d.getDiffForField(name).to());
+ }
+ }
+
+ @Test
+ public void testConfigDiff_specialSenders() {
+ // these two start the same
+ ZenModeConfig c1 = new ZenModeConfig();
+ ZenModeConfig c2 = new ZenModeConfig();
+
+ // set c1 and c2 to have some different senders
+ c1.allowMessagesFrom = ZenModeConfig.SOURCE_STAR;
+ c2.allowMessagesFrom = ZenModeConfig.SOURCE_CONTACT;
+ c1.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
+ c2.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_NONE;
+
+ ZenModeDiff.ConfigDiff d = new ZenModeDiff.ConfigDiff(c1, c2);
+ assertTrue(d.hasDiff());
+
+ // Diff in top-level fields
+ assertTrue(d.getDiffForField("allowMessagesFrom").hasDiff());
+ assertTrue(d.getDiffForField("allowConversationsFrom").hasDiff());
+
+ // Bonus testing of stringification of people senders and conversation senders
+ assertTrue(d.toString().contains("allowMessagesFrom:stars->contacts"));
+ assertTrue(d.toString().contains("allowConversationsFrom:important->none"));
+ }
+
+ @Test
+ public void testConfigDiff_hasRuleDiffs() {
+ // two default configs
+ ZenModeConfig c1 = new ZenModeConfig();
+ ZenModeConfig c2 = new ZenModeConfig();
+
+ // two initially-identical rules
+ ZenModeConfig.ZenRule r1 = makeRule();
+ ZenModeConfig.ZenRule r2 = makeRule();
+
+ // one that will become a manual rule
+ ZenModeConfig.ZenRule m = makeRule();
+
+ // Add r1 to c1, but not r2 to c2 yet -- expect a rule to be deleted
+ c1.automaticRules.put(r1.id, r1);
+ ZenModeDiff.ConfigDiff deleteRule = new ZenModeDiff.ConfigDiff(c1, c2);
+ assertTrue(deleteRule.hasDiff());
+ assertNotNull(deleteRule.getAllAutomaticRuleDiffs());
+ assertTrue(deleteRule.getAllAutomaticRuleDiffs().containsKey("ruleId"));
+ assertTrue(deleteRule.getAllAutomaticRuleDiffs().get("ruleId").wasRemoved());
+
+ // Change r2 a little, add r2 to c2 as an automatic rule and m as a manual rule
+ r2.component = null;
+ r2.pkg = "different";
+ c2.manualRule = m;
+ c2.automaticRules.put(r2.id, r2);
+
+ // Expect diffs in both manual rule (added) and automatic rule (changed)
+ ZenModeDiff.ConfigDiff changed = new ZenModeDiff.ConfigDiff(c1, c2);
+ assertTrue(changed.hasDiff());
+ assertTrue(changed.getManualRuleDiff().hasDiff());
+
+ ArrayMap<String, ZenModeDiff.RuleDiff> automaticDiffs = changed.getAllAutomaticRuleDiffs();
+ assertNotNull(automaticDiffs);
+ assertTrue(automaticDiffs.containsKey("ruleId"));
+ assertNotNull(automaticDiffs.get("ruleId").getDiffForField("component"));
+ assertNull(automaticDiffs.get("ruleId").getDiffForField("component").to());
+ assertNotNull(automaticDiffs.get("ruleId").getDiffForField("pkg"));
+ assertEquals("different", automaticDiffs.get("ruleId").getDiffForField("pkg").to());
+ }
+
+ // Helper methods for working with configs, policies, rules
+ // Just makes a zen rule with fields filled in
+ private ZenModeConfig.ZenRule makeRule() {
+ ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
+ rule.configurationActivity = new ComponentName("a", "a");
+ rule.component = new ComponentName("b", "b");
+ rule.conditionId = new Uri.Builder().scheme("hello").build();
+ rule.condition = new Condition(rule.conditionId, "", Condition.STATE_TRUE);
+ rule.enabled = true;
+ rule.creationTime = 123;
+ rule.id = "ruleId";
+ rule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ rule.modified = false;
+ rule.name = "name";
+ rule.snoozing = true;
+ rule.pkg = "a";
+ return rule;
+ }
+
+ // Get the fields on which we would want to check a diff. The requirements are: not final or/
+ // static (as these should/can never change), and not in a specific list that's exempted.
+ private List<Field> getFieldsForDiffCheck(Class c, Set<String> exemptNames)
+ throws SecurityException {
+ Field[] fields = c.getDeclaredFields();
+ ArrayList<Field> out = new ArrayList<>();
+
+ for (Field field : fields) {
+ // Check for exempt reasons
+ int m = field.getModifiers();
+ if (Modifier.isFinal(m)
+ || Modifier.isStatic(m)
+ || exemptNames.contains(field.getName())) {
+ continue;
+ }
+ out.add(field);
+ }
+ return out;
+ }
+
+ // Generate a set of generic diffs for the specified two objects and the fields to generate
+ // diffs for, and store the results in the provided expectation maps to be able to check the
+ // output later.
+ private void generateFieldDiffs(Object a, Object b, List<Field> fields,
+ ArrayMap<String, Object> expectedA, ArrayMap<String, Object> expectedB)
+ throws Exception {
+ // different classes passed in means bad input
+ assertEquals(a.getClass(), b.getClass());
+
+ // Loop through fields for which we want to check diffs, set a diff and keep track of
+ // what we set.
+ for (Field f : fields) {
+ f.setAccessible(true);
+ // Just double-check also that the fields actually are for the class declared
+ assertEquals(f.getDeclaringClass(), a.getClass());
+ Class t = f.getType();
+ // handle the full set of primitive types first
+ if (boolean.class.equals(t)) {
+ f.setBoolean(a, true);
+ expectedA.put(f.getName(), true);
+ f.setBoolean(b, false);
+ expectedB.put(f.getName(), false);
+ } else if (int.class.equals(t)) {
+ // these are not actually valid going to be valid for arbitrary int enum fields, but
+ // we just put something in there regardless.
+ f.setInt(a, 2);
+ expectedA.put(f.getName(), 2);
+ f.setInt(b, 1);
+ expectedB.put(f.getName(), 1);
+ } else if (long.class.equals(t)) {
+ f.setLong(a, 200L);
+ expectedA.put(f.getName(), 200L);
+ f.setLong(b, 100L);
+ expectedB.put(f.getName(), 100L);
+ } else if (t.isPrimitive()) {
+ // This method doesn't yet handle other primitive types. If the relevant diff
+ // classes gain new fields of these types, please add another clause here.
+ fail("primitive type not handled by generateFieldDiffs: " + t.getName());
+ } else if (String.class.equals(t)) {
+ f.set(a, "string1");
+ expectedA.put(f.getName(), "string1");
+ f.set(b, "string2");
+ expectedB.put(f.getName(), "string2");
+ } else {
+ // catch-all for other types: have the field be "added"
+ f.set(a, null);
+ expectedA.put(f.getName(), null);
+ try {
+ f.set(b, t.getDeclaredConstructor().newInstance());
+ expectedB.put(f.getName(), t.getDeclaredConstructor().newInstance());
+ } catch (Exception e) {
+ // No default constructor, or blithely attempting to construct something doesn't
+ // work for some reason. If the default value isn't null, then keep it.
+ if (f.get(b) != null) {
+ expectedB.put(f.getName(), f.get(b));
+ } else {
+ // If we can't even rely on that, fail. Have the test-writer special case
+ // something, as this is not able to be genericized.
+ fail("could not generically construct value for field: " + f.getName());
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 6f9798e..b2a5401 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -69,8 +69,6 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AutomaticZenRule;
@@ -78,7 +76,6 @@
import android.app.NotificationManager.Policy;
import android.content.ComponentName;
import android.content.ContentResolver;
-import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -90,7 +87,6 @@
import android.media.AudioSystem;
import android.media.VolumePolicy;
import android.net.Uri;
-import android.os.Binder;
import android.os.Process;
import android.os.UserHandle;
import android.provider.Settings;
@@ -98,6 +94,7 @@
import android.service.notification.Condition;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig.ScheduleInfo;
+import android.service.notification.ZenModeDiff;
import android.service.notification.ZenPolicy;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
@@ -877,7 +874,8 @@
mZenModeHelperSpy.readXml(parser, false, UserHandle.USER_ALL);
assertEquals("Config mismatch: current vs expected: "
- + mZenModeHelperSpy.mConfig.diff(expected), expected, mZenModeHelperSpy.mConfig);
+ + new ZenModeDiff.ConfigDiff(mZenModeHelperSpy.mConfig, expected), expected,
+ mZenModeHelperSpy.mConfig);
}
@Test
@@ -1046,7 +1044,8 @@
ZenModeConfig actual = mZenModeHelperSpy.mConfigs.get(10);
assertEquals(
- "Config mismatch: current vs expected: " + actual.diff(config10), config10, actual);
+ "Config mismatch: current vs expected: "
+ + new ZenModeDiff.ConfigDiff(actual, config10), config10, actual);
assertNotEquals("Expected config mismatch", config11, mZenModeHelperSpy.mConfigs.get(11));
}
@@ -1062,7 +1061,8 @@
mZenModeHelperSpy.readXml(parser, true, UserHandle.USER_SYSTEM);
assertEquals("Config mismatch: current vs original: "
- + mZenModeHelperSpy.mConfig.diff(original), original, mZenModeHelperSpy.mConfig);
+ + new ZenModeDiff.ConfigDiff(mZenModeHelperSpy.mConfig, original),
+ original, mZenModeHelperSpy.mConfig);
assertEquals(original.hashCode(), mZenModeHelperSpy.mConfig.hashCode());
}
@@ -1083,8 +1083,9 @@
ZenModeConfig actual = mZenModeHelperSpy.mConfigs.get(10);
expected.user = 10;
- assertEquals(
- "Config mismatch: current vs original: " + actual.diff(expected), expected, actual);
+ assertEquals("Config mismatch: current vs original: "
+ + new ZenModeDiff.ConfigDiff(actual, expected),
+ expected, actual);
assertEquals(expected.hashCode(), actual.hashCode());
expected.user = 0;
assertNotEquals(expected, mZenModeHelperSpy.mConfig);
diff --git a/services/tests/voiceinteractiontests/Android.bp b/services/tests/voiceinteractiontests/Android.bp
index 986fb71..e704ebf 100644
--- a/services/tests/voiceinteractiontests/Android.bp
+++ b/services/tests/voiceinteractiontests/Android.bp
@@ -40,6 +40,7 @@
"platform-test-annotations",
"services.core",
"services.voiceinteraction",
+ "services.soundtrigger",
"servicestests-core-utils",
"servicestests-utils-mockito-extended",
"truth-prebuilt",
diff --git a/services/voiceinteraction/Android.bp b/services/voiceinteraction/Android.bp
index 7332d2d..de8d144 100644
--- a/services/voiceinteraction/Android.bp
+++ b/services/voiceinteraction/Android.bp
@@ -9,11 +9,60 @@
filegroup {
name: "services.voiceinteraction-sources",
- srcs: ["java/**/*.java"],
+ srcs: ["java/com/android/server/voiceinteraction/*.java"],
path: "java",
visibility: ["//frameworks/base/services"],
}
+filegroup {
+ name: "services.soundtrigger_middleware-sources",
+ srcs: ["java/com/android/server/soundtrigger_middleware/*.java"],
+ path: "java",
+ visibility: ["//visibility:private"],
+}
+
+filegroup {
+ name: "services.soundtrigger_service-sources",
+ srcs: ["java/com/android/server/soundtrigger/*.java"],
+ path: "java",
+ visibility: ["//visibility:private"],
+}
+
+filegroup {
+ name: "services.soundtrigger-sources",
+ srcs: [
+ ":services.soundtrigger_service-sources",
+ ":services.soundtrigger_middleware-sources",
+ ],
+ path: "java",
+ visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+ name: "services.soundtrigger_middleware",
+ defaults: ["platform_service_defaults"],
+ srcs: [":services.soundtrigger_middleware-sources"],
+ libs: [
+ "services.core",
+ ],
+ static_libs: [
+ "android.hardware.soundtrigger-V2.3-java",
+ ],
+ visibility: ["//visibility/base/services/tests/voiceinteraction"],
+}
+
+java_library_static {
+ name: "services.soundtrigger",
+ defaults: ["platform_service_defaults"],
+ srcs: [":services.soundtrigger_service-sources"],
+ libs: [
+ "services.core",
+ ],
+ static_libs: [
+ "services.soundtrigger_middleware",
+ ],
+}
+
java_library_static {
name: "services.voiceinteraction",
defaults: ["platform_service_defaults"],
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 04c1c04..203a3e7 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -39,12 +39,13 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.PermissionChecker;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.hardware.soundtrigger.ConversionUtil;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
import android.hardware.soundtrigger.ModelParams;
-import android.hardware.soundtrigger.ConversionUtil;
import android.hardware.soundtrigger.SoundTrigger;
import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
@@ -64,6 +65,7 @@
import android.media.soundtrigger.ISoundTriggerDetectionService;
import android.media.soundtrigger.ISoundTriggerDetectionServiceClient;
import android.media.soundtrigger.SoundTriggerDetectionService;
+import android.media.soundtrigger_middleware.ISoundTriggerInjection;
import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
import android.os.Binder;
import android.os.Bundle;
@@ -74,8 +76,8 @@
import android.os.ParcelUuid;
import android.os.PowerManager;
import android.os.RemoteException;
-import android.os.ServiceSpecificException;
import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
@@ -86,6 +88,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.ISoundTriggerService;
import com.android.internal.app.ISoundTriggerSession;
+import com.android.server.SoundTriggerInternal;
import com.android.server.SystemService;
import com.android.server.utils.EventLogger;
@@ -98,8 +101,8 @@
import java.util.Objects;
import java.util.TreeMap;
import java.util.UUID;
-import java.util.stream.Collectors;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
/**
* A single SystemService to manage all sound/voice-based sound models on the DSP.
@@ -296,6 +299,23 @@
return listUnderlyingModuleProperties(originatorIdentity);
}
}
+
+ @Override
+ public void attachInjection(@NonNull ISoundTriggerInjection injection) {
+ if (PermissionChecker.checkCallingPermissionForPreflight(mContext,
+ android.Manifest.permission.MANAGE_SOUND_TRIGGER, null)
+ != PermissionChecker.PERMISSION_GRANTED) {
+ throw new SecurityException();
+ }
+ try {
+ ISoundTriggerMiddlewareService.Stub
+ .asInterface(ServiceManager
+ .waitForService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE))
+ .attachFakeHalInjection(injection);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
class SoundTriggerSessionStub extends ISoundTriggerSession.Stub {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
index aaf7a9e..5846ff6 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
@@ -39,7 +39,7 @@
*
* @hide
*/
-public class DatabaseHelper extends SQLiteOpenHelper {
+public class DatabaseHelper extends SQLiteOpenHelper implements IEnrolledModelDb {
static final String TAG = "SoundModelDBHelper";
static final boolean DBG = false;
@@ -153,11 +153,7 @@
}
}
- /**
- * Updates the given keyphrase model, adds it, if it doesn't already exist.
- *
- * TODO: We only support one keyphrase currently.
- */
+ @Override
public boolean updateKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
synchronized(this) {
SQLiteDatabase db = getWritableDatabase();
@@ -193,9 +189,7 @@
}
}
- /**
- * Deletes the sound model and associated keyphrases.
- */
+ @Override
public boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale) {
// Normalize the locale to guard against SQL injection.
bcp47Locale = Locale.forLanguageTag(bcp47Locale).toLanguageTag();
@@ -218,12 +212,7 @@
}
}
- /**
- * Returns a matching {@link KeyphraseSoundModel} for the keyphrase ID.
- * Returns null if a match isn't found.
- *
- * TODO: We only support one keyphrase currently.
- */
+ @Override
public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, int userHandle,
String bcp47Locale) {
// Sanitize the locale to guard against SQL injection.
@@ -237,12 +226,7 @@
}
}
- /**
- * Returns a matching {@link KeyphraseSoundModel} for the keyphrase string.
- * Returns null if a match isn't found.
- *
- * TODO: We only support one keyphrase currently.
- */
+ @Override
public KeyphraseSoundModel getKeyphraseSoundModel(String keyphrase, int userHandle,
String bcp47Locale) {
// Sanitize the locale to guard against SQL injection.
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/IEnrolledModelDb.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/IEnrolledModelDb.java
new file mode 100644
index 0000000..f10c2f6
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/IEnrolledModelDb.java
@@ -0,0 +1,90 @@
+/**
+ * Copyright (C) 2023 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.voiceinteraction;
+
+import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
+import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+
+import java.io.PrintWriter;
+
+/**
+ * Interface for registering and querying the enrolled keyphrase model database for
+ * {@link VoiceInteractionManagerService}.
+ * This interface only supports one keyphrase per {@link KeyphraseSoundModel}.
+ * The non-update methods are uniquely keyed on fields of the first keyphrase
+ * {@link KeyphraseSoundModel#getKeyphrases()}.
+ * @hide
+ */
+public interface IEnrolledModelDb {
+
+ //TODO(273286174): We only support one keyphrase currently.
+ /**
+ * Register the given {@link KeyphraseSoundModel}, or updates it if it already exists.
+ *
+ * @param soundModel - The sound model to register in the database.
+ * Updates the sound model if the keyphrase id, users, locale match an existing entry.
+ * Must have one and only one associated {@link Keyphrase}.
+ * @return - {@code true} if successful, {@code false} if unsuccessful
+ */
+ boolean updateKeyphraseSoundModel(KeyphraseSoundModel soundModel);
+
+ /**
+ * Deletes the previously registered keyphrase sound model from the database.
+ *
+ * @param keyphraseId - The (first) keyphrase ID of the KeyphraseSoundModel to delete.
+ * @param userHandle - The user handle making this request. Must be included in the user
+ * list of the registered sound model.
+ * @param bcp47Locale - The locale of the (first) keyphrase associated with this model.
+ * @return - {@code true} if successful, {@code false} if unsuccessful
+ */
+ boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale);
+
+ //TODO(273286174): We only support one keyphrase currently.
+ /**
+ * Returns the first matching {@link KeyphraseSoundModel} for the keyphrase ID, locale pair,
+ * contingent on the userHandle existing in the user list for the model.
+ * Returns null if a match isn't found.
+ *
+ * @param keyphraseId - The (first) keyphrase ID of the KeyphraseSoundModel to query.
+ * @param userHandle - The user handle making this request. Must be included in the user
+ * list of the registered sound model.
+ * @param bcp47Locale - The locale of the (first) keyphrase associated with this model.
+ * @return - {@code true} if successful, {@code false} if unsuccessful
+ */
+ KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, int userHandle,
+ String bcp47Locale);
+
+ //TODO(273286174): We only support one keyphrase currently.
+ /**
+ * Returns the first matching {@link KeyphraseSoundModel} for the keyphrase ID, locale pair,
+ * contingent on the userHandle existing in the user list for the model.
+ * Returns null if a match isn't found.
+ *
+ * @param keyphrase - The text of (the first) keyphrase of the KeyphraseSoundModel to query.
+ * @param userHandle - The user handle making this request. Must be included in the user
+ * list of the registered sound model.
+ * @param bcp47Locale - The locale of the (first) keyphrase associated with this model.
+ * @return - {@code true} if successful, {@code false} if unsuccessful
+ */
+ KeyphraseSoundModel getKeyphraseSoundModel(String keyphrase, int userHandle,
+ String bcp47Locale);
+
+ /**
+ * Dumps contents of database for dumpsys
+ */
+ void dump(PrintWriter pw);
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/TestModelEnrollmentDatabase.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/TestModelEnrollmentDatabase.java
new file mode 100644
index 0000000..9bbaf8e
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/TestModelEnrollmentDatabase.java
@@ -0,0 +1,148 @@
+/**
+ * Copyright (C) 2023 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.voiceinteraction;
+
+import android.annotation.NonNull;
+import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
+import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.StringJoiner;
+
+/**
+ * In memory model enrollment database for testing purposes.
+ * @hide
+ */
+public class TestModelEnrollmentDatabase implements IEnrolledModelDb {
+
+ // Record representing the primary key used in the real model database.
+ private static final class EnrollmentKey {
+ private final int mKeyphraseId;
+ private final List<Integer> mUserIds;
+ private final String mLocale;
+
+ EnrollmentKey(int keyphraseId,
+ @NonNull List<Integer> userIds, @NonNull String locale) {
+ mKeyphraseId = keyphraseId;
+ mUserIds = Objects.requireNonNull(userIds);
+ mLocale = Objects.requireNonNull(locale);
+ }
+
+ int keyphraseId() {
+ return mKeyphraseId;
+ }
+
+ List<Integer> userIds() {
+ return mUserIds;
+ }
+
+ String locale() {
+ return mLocale;
+ }
+
+ @Override
+ public String toString() {
+ StringJoiner sj = new StringJoiner(", ", "{", "}");
+ sj.add("keyphraseId: " + mKeyphraseId);
+ sj.add("userIds: " + mUserIds.toString());
+ sj.add("locale: " + mLocale.toString());
+ return "EnrollmentKey: " + sj.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int res = 1;
+ res = prime * res + mKeyphraseId;
+ res = prime * res + mUserIds.hashCode();
+ res = prime * res + mLocale.hashCode();
+ return res;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (other == null) return false;
+ if (!(other instanceof EnrollmentKey)) return false;
+ EnrollmentKey that = (EnrollmentKey) other;
+ if (mKeyphraseId != that.mKeyphraseId) return false;
+ if (!mUserIds.equals(that.mUserIds)) return false;
+ if (!mLocale.equals(that.mLocale)) return false;
+ return true;
+ }
+
+ }
+
+ private final Map<EnrollmentKey, KeyphraseSoundModel> mModelMap = new HashMap<>();
+
+ @Override
+ public boolean updateKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
+ final Keyphrase keyphrase = soundModel.getKeyphrases()[0];
+ mModelMap.put(new EnrollmentKey(keyphrase.getId(),
+ Arrays.stream(keyphrase.getUsers()).boxed().toList(),
+ keyphrase.getLocale().toLanguageTag()),
+ soundModel);
+ return true;
+ }
+
+ @Override
+ public boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale) {
+ return mModelMap.keySet().removeIf(key -> (key.keyphraseId() == keyphraseId)
+ && key.locale().equals(bcp47Locale)
+ && key.userIds().contains(userHandle));
+ }
+
+ @Override
+ public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, int userHandle,
+ String bcp47Locale) {
+ return mModelMap.entrySet()
+ .stream()
+ .filter((entry) -> (entry.getKey().keyphraseId() == keyphraseId)
+ && entry.getKey().locale().equals(bcp47Locale)
+ && entry.getKey().userIds().contains(userHandle))
+ .findFirst()
+ .map((entry) -> entry.getValue())
+ .orElse(null);
+ }
+
+ @Override
+ public KeyphraseSoundModel getKeyphraseSoundModel(String keyphrase, int userHandle,
+ String bcp47Locale) {
+ return mModelMap.entrySet()
+ .stream()
+ .filter((entry) -> (entry.getValue().getKeyphrases()[0].getText().equals(keyphrase)
+ && entry.getKey().locale().equals(bcp47Locale)
+ && entry.getKey().userIds().contains(userHandle)))
+ .findFirst()
+ .map((entry) -> entry.getValue())
+ .orElse(null);
+ }
+
+
+ /**
+ * Dumps contents of database for dumpsys
+ */
+ public void dump(PrintWriter pw) {
+ pw.println("Using test enrollment database, with enrolled models:");
+ pw.println(mModelMap);
+ }
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index e1da2ca..1d7b966 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -99,11 +99,11 @@
import com.android.internal.util.DumpUtils;
import com.android.server.FgThread;
import com.android.server.LocalServices;
+import com.android.server.SoundTriggerInternal;
import com.android.server.SystemService;
import com.android.server.UiThread;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
-import com.android.server.soundtrigger.SoundTriggerInternal;
import com.android.server.utils.Slogf;
import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -125,7 +125,9 @@
final Context mContext;
final ContentResolver mResolver;
- final DatabaseHelper mDbHelper;
+ // Can be overridden for testing purposes
+ private IEnrolledModelDb mDbHelper;
+ private final IEnrolledModelDb mRealDbHelper;
final ActivityManagerInternal mAmInternal;
final ActivityTaskManagerInternal mAtmInternal;
final UserManagerInternal mUserManagerInternal;
@@ -143,7 +145,7 @@
mResolver = context.getContentResolver();
mUserManagerInternal = Objects.requireNonNull(
LocalServices.getService(UserManagerInternal.class));
- mDbHelper = new DatabaseHelper(context);
+ mDbHelper = mRealDbHelper = new DatabaseHelper(context);
mServiceStub = new VoiceInteractionManagerServiceStub();
mAmInternal = Objects.requireNonNull(
LocalServices.getService(ActivityManagerInternal.class));
@@ -1605,6 +1607,42 @@
}
}
+ @Override
+ @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_VOICE_KEYPHRASES)
+ public void setModelDatabaseForTestEnabled(boolean enabled, IBinder token) {
+ super.setModelDatabaseForTestEnabled_enforcePermission();
+ enforceCallerAllowedToEnrollVoiceModel();
+ synchronized (this) {
+ if (enabled) {
+ // Replace the dbhelper with a new test db
+ final var db = new TestModelEnrollmentDatabase();
+ try {
+ // Listen to our caller death, and make sure we revert to the real
+ // db if they left the model in a test state.
+ token.linkToDeath(() -> {
+ synchronized (this) {
+ if (mDbHelper == db) {
+ mDbHelper = mRealDbHelper;
+ mImpl.notifySoundModelsChangedLocked();
+ }
+ }
+ }, 0);
+ } catch (RemoteException e) {
+ // If the caller is already dead, nothing to do.
+ return;
+ }
+ mDbHelper = db;
+ mImpl.notifySoundModelsChangedLocked();
+ } else {
+ // Nothing to do if the db is already set to the real impl.
+ if (mDbHelper != mRealDbHelper) {
+ mDbHelper = mRealDbHelper;
+ mImpl.notifySoundModelsChangedLocked();
+ }
+ }
+ }
+ }
+
//----------------- SoundTrigger APIs --------------------------------//
@Override
public boolean isEnrolledForKeyphrase(int keyphraseId, String bcp47Locale) {
@@ -1712,28 +1750,27 @@
final long caller = Binder.clearCallingIdentity();
try {
KeyphraseSoundModel soundModel =
- mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUserId, bcp47Locale);
+ mDbHelper.getKeyphraseSoundModel(keyphraseId,
+ callingUserId, bcp47Locale);
if (soundModel == null
|| soundModel.getUuid() == null
|| soundModel.getKeyphrases() == null) {
Slog.w(TAG, "No matching sound model found in startRecognition");
return SoundTriggerInternal.STATUS_ERROR;
- } else {
- // Regardless of the status of the start recognition, we need to make sure
- // that we unload this model if needed later.
- synchronized (VoiceInteractionManagerServiceStub.this) {
- mLoadedKeyphraseIds.put(keyphraseId, this);
- if (mSessionExternalCallback == null
- || mSessionInternalCallback == null
- || callback.asBinder() != mSessionExternalCallback.asBinder()) {
- mSessionInternalCallback = createSoundTriggerCallbackLocked(
- callback);
- mSessionExternalCallback = callback;
- }
- }
- return mSession.startRecognition(keyphraseId, soundModel,
- mSessionInternalCallback, recognitionConfig, runInBatterySaverMode);
}
+ // Regardless of the status of the start recognition, we need to make sure
+ // that we unload this model if needed later.
+ synchronized (VoiceInteractionManagerServiceStub.this) {
+ mLoadedKeyphraseIds.put(keyphraseId, this);
+ if (mSessionExternalCallback == null
+ || mSessionInternalCallback == null
+ || callback.asBinder() != mSessionExternalCallback.asBinder()) {
+ mSessionInternalCallback = createSoundTriggerCallbackLocked(callback);
+ mSessionExternalCallback = callback;
+ }
+ }
+ return mSession.startRecognition(keyphraseId, soundModel,
+ mSessionInternalCallback, recognitionConfig, runInBatterySaverMode);
} finally {
Binder.restoreCallingIdentity(caller);
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 62be2a55..0ad86c1 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -71,7 +71,6 @@
import android.util.Slog;
import android.view.IWindowManager;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.app.IVisualQueryDetectionAttentionListener;
import com.android.internal.app.IVoiceActionCheckCallback;
@@ -248,7 +247,6 @@
Context.RECEIVER_EXPORTED);
}
- @GuardedBy("this")
public void grantImplicitAccessLocked(int grantRecipientUid, @Nullable Intent intent) {
final int grantRecipientAppId = UserHandle.getAppId(grantRecipientUid);
final int grantRecipientUserId = UserHandle.getUserId(grantRecipientUid);
@@ -258,7 +256,6 @@
/* direct= */ true);
}
- @GuardedBy("this")
public boolean showSessionLocked(@Nullable Bundle args, int flags,
@Nullable String attributionTag,
@Nullable IVoiceInteractionSessionShowCallback showCallback,
@@ -331,7 +328,6 @@
}
}
- @GuardedBy("this")
public boolean hideSessionLocked() {
if (mActiveSession != null) {
return mActiveSession.hideLocked();
@@ -339,7 +335,6 @@
return false;
}
- @GuardedBy("this")
public boolean deliverNewSessionLocked(IBinder token,
IVoiceInteractionSession session, IVoiceInteractor interactor) {
if (mActiveSession == null || token != mActiveSession.mToken) {
@@ -350,7 +345,6 @@
return true;
}
- @GuardedBy("this")
public int startVoiceActivityLocked(@Nullable String callingFeatureId, int callingPid,
int callingUid, IBinder token, Intent intent, String resolvedType) {
try {
@@ -373,7 +367,6 @@
}
}
- @GuardedBy("this")
public int startAssistantActivityLocked(@Nullable String callingFeatureId, int callingPid,
int callingUid, IBinder token, Intent intent, String resolvedType,
@NonNull Bundle bundle) {
@@ -397,7 +390,6 @@
}
}
- @GuardedBy("this")
public void requestDirectActionsLocked(@NonNull IBinder token, int taskId,
@NonNull IBinder assistToken, @Nullable RemoteCallback cancellationCallback,
@NonNull RemoteCallback callback) {
@@ -453,7 +445,6 @@
}
}
- @GuardedBy("this")
void performDirectActionLocked(@NonNull IBinder token, @NonNull String actionId,
@Nullable Bundle arguments, int taskId, IBinder assistToken,
@Nullable RemoteCallback cancellationCallback,
@@ -480,7 +471,6 @@
}
}
- @GuardedBy("this")
public void setKeepAwakeLocked(IBinder token, boolean keepAwake) {
try {
if (mActiveSession == null || token != mActiveSession.mToken) {
@@ -493,7 +483,6 @@
}
}
- @GuardedBy("this")
public void closeSystemDialogsLocked(IBinder token) {
try {
if (mActiveSession == null || token != mActiveSession.mToken) {
@@ -506,7 +495,6 @@
}
}
- @GuardedBy("this")
public void finishLocked(IBinder token, boolean finishTask) {
if (mActiveSession == null || (!finishTask && token != mActiveSession.mToken)) {
Slog.w(TAG, "finish does not match active session");
@@ -516,7 +504,6 @@
mActiveSession = null;
}
- @GuardedBy("this")
public void setDisabledShowContextLocked(int callingUid, int flags) {
int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
if (callingUid != activeUid) {
@@ -526,7 +513,6 @@
mDisabledShowContext = flags;
}
- @GuardedBy("this")
public int getDisabledShowContextLocked(int callingUid) {
int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
if (callingUid != activeUid) {
@@ -536,7 +522,6 @@
return mDisabledShowContext;
}
- @GuardedBy("this")
public int getUserDisabledShowContextLocked(int callingUid) {
int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
if (callingUid != activeUid) {
@@ -550,7 +535,6 @@
return mInfo.getSupportsLocalInteraction();
}
- @GuardedBy("this")
public void startListeningVisibleActivityChangedLocked(@NonNull IBinder token) {
if (DEBUG) {
Slog.d(TAG, "startListeningVisibleActivityChangedLocked: token=" + token);
@@ -563,7 +547,6 @@
mActiveSession.startListeningVisibleActivityChangedLocked();
}
- @GuardedBy("this")
public void stopListeningVisibleActivityChangedLocked(@NonNull IBinder token) {
if (DEBUG) {
Slog.d(TAG, "stopListeningVisibleActivityChangedLocked: token=" + token);
@@ -576,7 +559,6 @@
mActiveSession.stopListeningVisibleActivityChangedLocked();
}
- @GuardedBy("this")
public void notifyActivityDestroyedLocked(@NonNull IBinder activityToken) {
if (DEBUG) {
Slog.d(TAG, "notifyActivityDestroyedLocked activityToken=" + activityToken);
@@ -591,7 +573,6 @@
mActiveSession.notifyActivityDestroyedLocked(activityToken);
}
- @GuardedBy("this")
public void notifyActivityEventChangedLocked(@NonNull IBinder activityToken, int type) {
if (DEBUG) {
Slog.d(TAG, "notifyActivityEventChangedLocked type=" + type);
@@ -606,7 +587,6 @@
mActiveSession.notifyActivityEventChangedLocked(activityToken, type);
}
- @GuardedBy("this")
public void updateStateLocked(
@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory,
@@ -627,7 +607,6 @@
}
}
- @GuardedBy("this")
private void verifyDetectorForHotwordDetectionLocked(
@Nullable SharedMemory sharedMemory,
IHotwordRecognitionStatusCallback callback,
@@ -685,7 +664,6 @@
voiceInteractionServiceUid);
}
- @GuardedBy("this")
private void verifyDetectorForVisualQueryDetectionLocked(@Nullable SharedMemory sharedMemory) {
Slog.v(TAG, "verifyDetectorForVisualQueryDetectionLocked");
@@ -724,7 +702,6 @@
}
}
- @GuardedBy("this")
public void initAndVerifyDetectorLocked(
@NonNull Identity voiceInteractorIdentity,
@Nullable PersistableBundle options,
@@ -769,7 +746,6 @@
detectorType);
}
- @GuardedBy("this")
public void destroyDetectorLocked(IBinder token) {
Slog.v(TAG, "destroyDetectorLocked");
@@ -788,7 +764,6 @@
}
}
- @GuardedBy("this")
public void shutdownHotwordDetectionServiceLocked() {
if (DEBUG) {
Slog.d(TAG, "shutdownHotwordDetectionServiceLocked");
@@ -801,7 +776,6 @@
mHotwordDetectionConnection = null;
}
- @GuardedBy("this")
public void setVisualQueryDetectionAttentionListenerLocked(
@Nullable IVisualQueryDetectionAttentionListener listener) {
if (mHotwordDetectionConnection == null) {
@@ -810,7 +784,6 @@
mHotwordDetectionConnection.setVisualQueryDetectionAttentionListenerLocked(listener);
}
- @GuardedBy("this")
public void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
if (DEBUG) {
Slog.d(TAG, "startPerceivingLocked");
@@ -824,7 +797,6 @@
mHotwordDetectionConnection.startPerceivingLocked(callback);
}
- @GuardedBy("this")
public void stopPerceivingLocked() {
if (DEBUG) {
Slog.d(TAG, "stopPerceivingLocked");
@@ -838,7 +810,6 @@
mHotwordDetectionConnection.stopPerceivingLocked();
}
- @GuardedBy("this")
public void startListeningFromMicLocked(
AudioFormat audioFormat,
IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
@@ -854,7 +825,6 @@
mHotwordDetectionConnection.startListeningFromMicLocked(audioFormat, callback);
}
- @GuardedBy("this")
public void startListeningFromExternalSourceLocked(
ParcelFileDescriptor audioStream,
AudioFormat audioFormat,
@@ -879,7 +849,6 @@
options, token, callback);
}
- @GuardedBy("this")
public void stopListeningFromMicLocked() {
if (DEBUG) {
Slog.d(TAG, "stopListeningFromMicLocked");
@@ -893,7 +862,6 @@
mHotwordDetectionConnection.stopListeningFromMicLocked();
}
- @GuardedBy("this")
public void triggerHardwareRecognitionEventForTestLocked(
SoundTrigger.KeyphraseRecognitionEvent event,
IHotwordRecognitionStatusCallback callback) {
@@ -908,7 +876,6 @@
mHotwordDetectionConnection.triggerHardwareRecognitionEventForTestLocked(event, callback);
}
- @GuardedBy("this")
public IRecognitionStatusCallback createSoundTriggerCallbackLocked(
IHotwordRecognitionStatusCallback callback) {
if (DEBUG) {
@@ -933,12 +900,11 @@
return null;
}
- @GuardedBy("this")
boolean isIsolatedProcessLocked(@NonNull ServiceInfo serviceInfo) {
return (serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
&& (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0;
}
- @GuardedBy("this")
+
boolean verifyProcessSharingLocked() {
// only check this if both VQDS and HDS are declared in the app
ServiceInfo hotwordInfo = getServiceInfoLocked(mHotwordDetectionComponentName, mUser);
@@ -960,7 +926,6 @@
mHotwordDetectionConnection.forceRestart();
}
- @GuardedBy("this")
void setDebugHotwordLoggingLocked(boolean logging) {
if (mHotwordDetectionConnection == null) {
Slog.w(TAG, "Failed to set temporary debug logging: no hotword detection active");
@@ -969,7 +934,6 @@
mHotwordDetectionConnection.setDebugHotwordLoggingLocked(logging);
}
- @GuardedBy("this")
void resetHotwordDetectionConnectionLocked() {
if (DEBUG) {
Slog.d(TAG, "resetHotwordDetectionConnectionLocked");
@@ -984,7 +948,6 @@
mHotwordDetectionConnection = null;
}
- @GuardedBy("this")
public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!mValid) {
pw.print(" NOT VALID: ");
@@ -1023,7 +986,6 @@
}
}
- @GuardedBy("this")
void startLocked() {
Intent intent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
intent.setComponent(mComponent);
@@ -1048,7 +1010,6 @@
}
}
- @GuardedBy("this")
void shutdownLocked() {
// If there is an active session, cancel it to allow it to clean up its window and other
// state.
@@ -1076,7 +1037,6 @@
}
}
- @GuardedBy("this")
void notifySoundModelsChangedLocked() {
if (mService == null) {
Slog.w(TAG, "Not bound to voice interaction service " + mComponent);
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index e39af5a..9dd2a61 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -2682,71 +2682,76 @@
}
/**
- * Reports a new call with the specified {@link CallAttributes} to the telecom service. This
- * method can be used to report both incoming and outgoing calls. By reporting the call, the
- * system is aware of the call and can provide updates on services (ex. Another device wants to
- * disconnect the call) or events (ex. a new Bluetooth route became available).
- *
+ * Add a call to the Android system service Telecom. This allows the system to start tracking an
+ * incoming or outgoing call with the specified {@link CallAttributes}. Once the call is ready
+ * to be disconnected, use the {@link CallControl#disconnect(DisconnectCause, Executor,
+ * OutcomeReceiver)} which is provided by the {@code pendingControl#onResult(CallControl)}.
* <p>
- * The difference between this API call and {@link TelecomManager#placeCall(Uri, Bundle)} or
- * {@link TelecomManager#addNewIncomingCall(PhoneAccountHandle, Bundle)} is that this API
- * will asynchronously provide an update on whether the new call was added successfully via
- * an {@link OutcomeReceiver}. Additionally, callbacks will run on the executor thread that was
- * passed in.
- *
* <p>
- * Note: Only packages that register with
+ * <p>
+ * <b>Call Lifecycle</b>: Your app is given foreground execution priority as long as you have a
+ * valid call and are posting a {@link android.app.Notification.CallStyle} notification.
+ * When your application is given foreground execution priority, your app is treated as a
+ * foreground service. Foreground execution priority will prevent the
+ * {@link android.app.ActivityManager} from killing your application when it is placed the
+ * background. Foreground execution priority is removed from your app when all of your app's
+ * calls terminate or your app no longer posts a valid notification.
+ * <p>
+ * <p>
+ * <p>
+ * <b>Note</b>: Only packages that register with
* {@link PhoneAccount#CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS}
* can utilize this API. {@link PhoneAccount}s that set the capabilities
* {@link PhoneAccount#CAPABILITY_SIM_SUBSCRIPTION},
* {@link PhoneAccount#CAPABILITY_CALL_PROVIDER},
* {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}
* are not supported and will cause an exception to be thrown.
- *
* <p>
- * Usage example:
+ * <p>
+ * <p>
+ * <b>Usage example:</b>
* <pre>
- *
- * // An app should first define their own construct of a Call that overrides all the
- * // {@link CallControlCallback}s and {@link CallEventCallback}s
- * private class MyVoipCall {
- * public String callId = "";
- *
- * public CallControlCallEventCallback handshakes = new
- * CallControlCallEventCallback() {
- * // override/ implement all {@link CallControlCallback}s
+ * // Its up to your app on how you want to wrap the objects. One such implementation can be:
+ * class MyVoipCall {
+ * ...
+ * public CallControlCallEventCallback handshakes = new CallControlCallback() {
+ * ...
* }
- * public CallEventCallback events = new
- * CallEventCallback() {
- * // override/ implement all {@link CallEventCallback}s
+ *
+ * public CallEventCallback events = new CallEventCallback() {
+ * ...
* }
- * public MyVoipCall(String id){
- * callId = id;
+ *
+ * public MyVoipCall(String id){
+ * ...
+ * }
* }
*
- * PhoneAccountHandle handle = new PhoneAccountHandle(
- * new ComponentName("com.example.voip.app",
- * "com.example.voip.app.NewCallActivity"), "123");
- *
- * CallAttributes callAttributes = new CallAttributes.Builder(handle,
- * CallAttributes.DIRECTION_OUTGOING,
- * "John Smith", Uri.fromParts("tel", "123", null))
- * .build();
- *
* MyVoipCall myFirstOutgoingCall = new MyVoipCall("1");
*
- * telecomManager.addCall(callAttributes, Runnable::run, new OutcomeReceiver() {
+ * telecomManager.addCall(callAttributes,
+ * Runnable::run,
+ * new OutcomeReceiver() {
* public void onResult(CallControl callControl) {
- * // The call has been added successfully
+ * // The call has been added successfully. For demonstration
+ * // purposes, the call is disconnected immediately ...
+ * callControl.disconnect(
+ * new DisconnectCause(DisconnectCause.LOCAL) )
* }
- * }, myFirstOutgoingCall.handshakes, myFirstOutgoingCall.events);
+ * },
+ * myFirstOutgoingCall.handshakes,
+ * myFirstOutgoingCall.events);
* </pre>
*
- * @param callAttributes attributes of the new call (incoming or outgoing, address, etc. )
- * @param executor thread to run background CallEventCallback updates on
- * @param pendingControl OutcomeReceiver that receives the result of addCall transaction
- * @param handshakes object that overrides {@link CallControlCallback}s
- * @param events object that overrides {@link CallEventCallback}s
+ * @param callAttributes attributes of the new call (incoming or outgoing, address, etc.)
+ * @param executor execution context to run {@link CallControlCallback} updates on
+ * @param pendingControl Receives the result of addCall transaction. Upon success, a
+ * CallControl object is provided which can be used to do things like
+ * disconnect the call that was added.
+ * @param handshakes callback that receives <b>actionable</b> updates that originate from
+ * Telecom.
+ * @param events callback that receives <b>non</b>-actionable updates that originate
+ * from Telecom.
*/
@RequiresPermission(android.Manifest.permission.MANAGE_OWN_CALLS)
@SuppressLint("SamShouldBeLast")
diff --git a/telephony/java/android/telephony/satellite/AntennaDirection.aidl b/telephony/java/android/telephony/satellite/AntennaDirection.aidl
new file mode 100644
index 0000000..c838f6f
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/AntennaDirection.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+parcelable AntennaDirection;
diff --git a/telephony/java/android/telephony/satellite/AntennaDirection.java b/telephony/java/android/telephony/satellite/AntennaDirection.java
new file mode 100644
index 0000000..02b0bc7
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/AntennaDirection.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Antenna direction is provided as X/Y/Z values corresponding to the direction of the antenna
+ * main lobe as a unit vector in CTIA coordinate system (as specified in Appendix A of Wireless
+ * device CTIA OTAn test plan). CTIA coordinate system is defined relative to device’s screen
+ * when the device is held in default portrait mode with screen facing the user:
+ *
+ * Z axis is vertical along the plane of the device with positive Z pointing up and negative z
+ * pointing towards bottom of the device
+ * Y axis is horizontal along the plane of the device with positive Y pointing towards right of
+ * the phone screen and negative Y pointing towards left
+ * X axis is orthogonal to the Y-Z plane (phone screen), pointing away from the phone screen for
+ * positive X and pointing away from back of the phone for negative X.
+ * @hide
+ */
+public final class AntennaDirection implements Parcelable {
+ /** Antenna x axis direction. */
+ private float mX;
+
+ /** Antenna y axis direction. */
+ private float mY;
+
+ /** Antenna z axis direction. */
+ private float mZ;
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public AntennaDirection(float x, float y, float z) {
+ mX = x;
+ mY = y;
+ mZ = z;
+ }
+
+ private AntennaDirection(Parcel in) {
+ readFromParcel(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeFloat(mX);
+ out.writeFloat(mY);
+ out.writeFloat(mZ);
+ }
+
+ @NonNull
+ public static final Creator<AntennaDirection> CREATOR =
+ new Creator<>() {
+ @Override
+ public AntennaDirection createFromParcel(Parcel in) {
+ return new AntennaDirection(in);
+ }
+
+ @Override
+ public AntennaDirection[] newArray(int size) {
+ return new AntennaDirection[size];
+ }
+ };
+
+ @Override
+ @NonNull public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("X:");
+ sb.append(mX);
+ sb.append(",");
+
+ sb.append("Y:");
+ sb.append(mY);
+ sb.append(",");
+
+ sb.append("Z:");
+ sb.append(mZ);
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ AntennaDirection that = (AntennaDirection) o;
+ return mX == that.mX
+ && mY == that.mY
+ && mZ == that.mZ;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mX, mY, mZ);
+ }
+
+ public float getX() {
+ return mX;
+ }
+
+ public float getY() {
+ return mY;
+ }
+
+ public float getZ() {
+ return mZ;
+ }
+
+ private void readFromParcel(Parcel in) {
+ mX = in.readFloat();
+ mY = in.readFloat();
+ mZ = in.readFloat();
+ }
+}
diff --git a/telephony/java/android/telephony/satellite/AntennaPosition.aidl b/telephony/java/android/telephony/satellite/AntennaPosition.aidl
new file mode 100644
index 0000000..0052562
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/AntennaPosition.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+parcelable AntennaPosition;
diff --git a/telephony/java/android/telephony/satellite/AntennaPosition.java b/telephony/java/android/telephony/satellite/AntennaPosition.java
new file mode 100644
index 0000000..eefc8b0
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/AntennaPosition.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Antenna Position received from satellite modem which gives information about antenna
+ * direction to be used with satellite communication and suggested device hold positions.
+ * @hide
+ */
+public final class AntennaPosition implements Parcelable {
+ /** Antenna direction used for satellite communication. */
+ @NonNull AntennaDirection mAntennaDirection;
+
+ /** Enum corresponding to device hold position to be used by the end user. */
+ @SatelliteManager.DeviceHoldPosition int mSuggestedHoldPosition;
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public AntennaPosition(@NonNull AntennaDirection antennaDirection, int suggestedHoldPosition) {
+ mAntennaDirection = antennaDirection;
+ mSuggestedHoldPosition = suggestedHoldPosition;
+ }
+
+ private AntennaPosition(Parcel in) {
+ readFromParcel(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeParcelable(mAntennaDirection, flags);
+ out.writeInt(mSuggestedHoldPosition);
+ }
+
+ @NonNull
+ public static final Creator<AntennaPosition> CREATOR =
+ new Creator<>() {
+ @Override
+ public AntennaPosition createFromParcel(Parcel in) {
+ return new AntennaPosition(in);
+ }
+
+ @Override
+ public AntennaPosition[] newArray(int size) {
+ return new AntennaPosition[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ AntennaPosition that = (AntennaPosition) o;
+ return Objects.equals(mAntennaDirection, that.mAntennaDirection)
+ && mSuggestedHoldPosition == that.mSuggestedHoldPosition;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAntennaDirection, mSuggestedHoldPosition);
+ }
+
+ @Override
+ @NonNull public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("antennaDirection:");
+ sb.append(mAntennaDirection);
+ sb.append(",");
+
+ sb.append("suggestedHoldPosition:");
+ sb.append(mSuggestedHoldPosition);
+ return sb.toString();
+ }
+
+ @NonNull
+ public AntennaDirection getAntennaDirection() {
+ return mAntennaDirection;
+ }
+
+ @SatelliteManager.DeviceHoldPosition
+ public int getSuggestedHoldPosition() {
+ return mSuggestedHoldPosition;
+ }
+
+ private void readFromParcel(Parcel in) {
+ mAntennaDirection = in.readParcelable(AntennaDirection.class.getClassLoader(),
+ AntennaDirection.class);
+ mSuggestedHoldPosition = in.readInt();
+ }
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
index 87c8db3..0092890 100644
--- a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
+++ b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
@@ -21,7 +21,10 @@
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
import java.util.Set;
/**
@@ -44,15 +47,25 @@
private int mMaxBytesPerOutgoingDatagram;
/**
+ * Antenna Position received from satellite modem which gives information about antenna
+ * direction to be used with satellite communication and suggested device hold positions.
+ * Map key: {@link SatelliteManager.DeviceHoldPosition} value: AntennaPosition
+ */
+ @NonNull
+ private Map<Integer, AntennaPosition> mAntennaPositionMap;
+
+ /**
* @hide
*/
@UnsupportedAppUsage
public SatelliteCapabilities(Set<Integer> supportedRadioTechnologies,
- boolean isPointingRequired, int maxBytesPerOutgoingDatagram) {
+ boolean isPointingRequired, int maxBytesPerOutgoingDatagram,
+ @NonNull Map<Integer, AntennaPosition> antennaPositionMap) {
mSupportedRadioTechnologies = supportedRadioTechnologies == null
? new HashSet<>() : supportedRadioTechnologies;
mIsPointingRequired = isPointingRequired;
mMaxBytesPerOutgoingDatagram = maxBytesPerOutgoingDatagram;
+ mAntennaPositionMap = antennaPositionMap;
}
private SatelliteCapabilities(Parcel in) {
@@ -77,6 +90,17 @@
out.writeBoolean(mIsPointingRequired);
out.writeInt(mMaxBytesPerOutgoingDatagram);
+
+ if (mAntennaPositionMap != null && !mAntennaPositionMap.isEmpty()) {
+ int size = mAntennaPositionMap.size();
+ out.writeInt(size);
+ for (Map.Entry<Integer, AntennaPosition> entry : mAntennaPositionMap.entrySet()) {
+ out.writeInt(entry.getKey());
+ out.writeParcelable(entry.getValue(), flags);
+ }
+ } else {
+ out.writeInt(0);
+ }
}
@NonNull public static final Creator<SatelliteCapabilities> CREATOR = new Creator<>() {
@@ -109,11 +133,32 @@
sb.append(mIsPointingRequired);
sb.append(",");
- sb.append("maxBytesPerOutgoingDatagram");
+ sb.append("maxBytesPerOutgoingDatagram:");
sb.append(mMaxBytesPerOutgoingDatagram);
+ sb.append(",");
+
+ sb.append("antennaPositionMap:");
+ sb.append(mAntennaPositionMap);
return sb.toString();
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ SatelliteCapabilities that = (SatelliteCapabilities) o;
+ return Objects.equals(mSupportedRadioTechnologies, that.mSupportedRadioTechnologies)
+ && mIsPointingRequired == that.mIsPointingRequired
+ && mMaxBytesPerOutgoingDatagram == that.mMaxBytesPerOutgoingDatagram
+ && Objects.equals(mAntennaPositionMap, that.mAntennaPositionMap);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSupportedRadioTechnologies, mIsPointingRequired,
+ mMaxBytesPerOutgoingDatagram, mAntennaPositionMap);
+ }
+
/**
* @return The list of technologies supported by the satellite modem.
*/
@@ -141,6 +186,16 @@
return mMaxBytesPerOutgoingDatagram;
}
+ /**
+ * Antenna Position received from satellite modem which gives information about antenna
+ * direction to be used with satellite communication and suggested device hold positions.
+ * @return Map key: {@link SatelliteManager.DeviceHoldPosition} value: AntennaPosition
+ */
+ @NonNull
+ public Map<Integer, AntennaPosition> getAntennaPositionMap() {
+ return mAntennaPositionMap;
+ }
+
private void readFromParcel(Parcel in) {
mSupportedRadioTechnologies = new HashSet<>();
int numSupportedRadioTechnologies = in.readInt();
@@ -152,5 +207,14 @@
mIsPointingRequired = in.readBoolean();
mMaxBytesPerOutgoingDatagram = in.readInt();
+
+ mAntennaPositionMap = new HashMap<>();
+ int antennaPositionMapSize = in.readInt();
+ for (int i = 0; i < antennaPositionMapSize; i++) {
+ int key = in.readInt();
+ AntennaPosition antennaPosition = in.readParcelable(
+ AntennaPosition.class.getClassLoader(), AntennaPosition.class);
+ mAntennaPositionMap.put(key, antennaPosition);
+ }
}
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 20f9bc8b..5681ab2 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -334,6 +334,46 @@
@Retention(RetentionPolicy.SOURCE)
public @interface NTRadioTechnology {}
+ /** Suggested device hold position is unknown. */
+ public static final int DEVICE_HOLD_POSITION_UNKNOWN = 0;
+ /** User is suggested to hold the device in portrait mode. */
+ public static final int DEVICE_HOLD_POSITION_PORTRAIT = 1;
+ /** User is suggested to hold the device in landscape mode with left hand. */
+ public static final int DEVICE_HOLD_POSITION_LANDSCAPE_LEFT = 2;
+ /** User is suggested to hold the device in landscape mode with right hand. */
+ public static final int DEVICE_HOLD_POSITION_LANDSCAPE_RIGHT = 3;
+
+ /** @hide */
+ @IntDef(prefix = {"DEVICE_HOLD_POSITION_"}, value = {
+ DEVICE_HOLD_POSITION_UNKNOWN,
+ DEVICE_HOLD_POSITION_PORTRAIT,
+ DEVICE_HOLD_POSITION_LANDSCAPE_LEFT,
+ DEVICE_HOLD_POSITION_LANDSCAPE_RIGHT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DeviceHoldPosition {}
+
+ /** Display mode is unknown. */
+ public static final int DISPLAY_MODE_UNKNOWN = 0;
+ /** Display mode of the device used for satellite communication for non-foldable phones. */
+ public static final int DISPLAY_MODE_FIXED = 1;
+ /** Display mode of the device used for satellite communication for foldabale phones when the
+ * device is opened. */
+ public static final int DISPLAY_MODE_OPENED = 2;
+ /** Display mode of the device used for satellite communication for foldabable phones when the
+ * device is closed. */
+ public static final int DISPLAY_MODE_CLOSED = 3;
+
+ /** @hide */
+ @IntDef(prefix = {"ANTENNA_POSITION_"}, value = {
+ DISPLAY_MODE_UNKNOWN,
+ DISPLAY_MODE_FIXED,
+ DISPLAY_MODE_OPENED,
+ DISPLAY_MODE_CLOSED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DisplayMode {}
+
/**
* Request to enable or disable the satellite modem and demo mode. If the satellite modem is
* enabled, this may also disable the cellular modem, and if the satellite modem is disabled,
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl b/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl
index cd69da1..eaf96ab 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl
@@ -17,7 +17,7 @@
package android.telephony.satellite.stub;
import android.telephony.satellite.stub.NTRadioTechnology;
-
+import android.telephony.satellite.AntennaPosition;
/**
* {@hide}
*/
@@ -36,4 +36,14 @@
* The maximum number of bytes per datagram that can be sent over satellite.
*/
int maxBytesPerOutgoingDatagram;
+
+ /**
+ * Keys which are used to fill mAntennaPositionMap.
+ */
+ int[] antennaPositionKeys;
+
+ /**
+ * Antenna Position for different display modes received from satellite modem.
+ */
+ AntennaPosition[] antennaPositionValues;
}
diff --git a/tools/aapt/SdkConstants.h b/tools/aapt/SdkConstants.h
index a146466..e2c1614 100644
--- a/tools/aapt/SdkConstants.h
+++ b/tools/aapt/SdkConstants.h
@@ -49,6 +49,7 @@
SDK_S = 31,
SDK_S_V2 = 32,
SDK_TIRAMISU = 33,
+ SDK_UPSIDE_DOWN_CAKE = 34,
SDK_CUR_DEVELOPMENT = 10000,
};
diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h
index 40bcef7..e47704ea 100644
--- a/tools/aapt2/SdkConstants.h
+++ b/tools/aapt2/SdkConstants.h
@@ -59,6 +59,7 @@
SDK_S = 31,
SDK_S_V2 = 32,
SDK_TIRAMISU = 33,
+ SDK_UPSIDE_DOWN_CAKE = 34,
SDK_CUR_DEVELOPMENT = 10000,
};
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
index 25fbabc..166fbdd 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
@@ -163,11 +163,11 @@
/**
* Sets the displayed connection strength of the remote device to the internet.
*
- * @param connectionStrength Connection strength in range 0 to 3.
+ * @param connectionStrength Connection strength in range 0 to 4.
* @return Returns the Builder object.
*/
@NonNull
- public Builder setConnectionStrength(@IntRange(from = 0, to = 3) int connectionStrength) {
+ public Builder setConnectionStrength(@IntRange(from = 0, to = 4) int connectionStrength) {
mConnectionStrength = connectionStrength;
return this;
}
@@ -205,8 +205,8 @@
if (batteryPercentage < 0 || batteryPercentage > 100) {
throw new IllegalArgumentException("BatteryPercentage must be in range 0-100");
}
- if (connectionStrength < 0 || connectionStrength > 3) {
- throw new IllegalArgumentException("ConnectionStrength must be in range 0-3");
+ if (connectionStrength < 0 || connectionStrength > 4) {
+ throw new IllegalArgumentException("ConnectionStrength must be in range 0-4");
}
}
@@ -265,9 +265,9 @@
/**
* Gets the displayed connection strength of the remote device to the internet.
*
- * @return Returns the connection strength in range 0 to 3.
+ * @return Returns the connection strength in range 0 to 4.
*/
- @IntRange(from = 0, to = 3)
+ @IntRange(from = 0, to = 4)
public int getConnectionStrength() {
return mConnectionStrength;
}