Merge "Use static mocking in `DesktopModeLaunchParamsModifierTests`." into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index fa11278..a42adad 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -173,6 +173,11 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+cc_aconfig_library {
+ name: "com.android.window.flags.window-aconfig_flags_c_lib",
+ aconfig_declarations: "com.android.window.flags.window-aconfig",
+}
+
// DeviceStateManager
aconfig_declarations {
name: "android.hardware.devicestate.feature.flags-aconfig",
diff --git a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
index e649485..e82df12 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
@@ -41,10 +41,10 @@
]
},
{
- "name": "CtsHostsideNetworkTests",
+ "name": "CtsHostsideNetworkPolicyTests",
"options": [
- {"include-filter": "com.android.cts.net.HostsideRestrictBackgroundNetworkTests#testMeteredNetworkAccess_expeditedJob"},
- {"include-filter": "com.android.cts.net.HostsideRestrictBackgroundNetworkTests#testNonMeteredNetworkAccess_expeditedJob"}
+ {"include-filter": "com.android.cts.netpolicy.HostsideRestrictBackgroundNetworkTests#testMeteredNetworkAccess_expeditedJob"},
+ {"include-filter": "com.android.cts.netpolicy.HostsideRestrictBackgroundNetworkTests#testNonMeteredNetworkAccess_expeditedJob"}
]
},
{
diff --git a/core/api/current.txt b/core/api/current.txt
index 2f2a765..53cf7d5 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -451,12 +451,12 @@
field public static final int alertDialogTheme = 16843529; // 0x1010309
field public static final int alignmentMode = 16843642; // 0x101037a
field public static final int allContactsName = 16843468; // 0x10102cc
- field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int allow;
+ field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int allow = 16844430; // 0x101068e
field public static final int allowAudioPlaybackCapture = 16844289; // 0x1010601
field public static final int allowBackup = 16843392; // 0x1010280
field public static final int allowClearUserData = 16842757; // 0x1010005
field public static final int allowClickWhenDisabled = 16844312; // 0x1010618
- field @FlaggedApi("android.security.asm_restrictions_enabled") public static final int allowCrossUidActivitySwitchFromBelow;
+ field @FlaggedApi("android.security.asm_restrictions_enabled") public static final int allowCrossUidActivitySwitchFromBelow = 16844449; // 0x10106a1
field public static final int allowEmbedded = 16843765; // 0x10103f5
field public static final int allowGameAngleDriver = 16844376; // 0x1010658
field public static final int allowGameDownscaling = 16844377; // 0x1010659
@@ -511,7 +511,7 @@
field public static final int autoSizeTextType = 16844085; // 0x1010535
field public static final int autoStart = 16843445; // 0x10102b5
field @Deprecated public static final int autoText = 16843114; // 0x101016a
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final int autoTransact;
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final int autoTransact = 16844441; // 0x1010699
field public static final int autoUrlDetect = 16843404; // 0x101028c
field public static final int autoVerify = 16844014; // 0x10104ee
field public static final int autofillHints = 16844118; // 0x1010556
@@ -658,7 +658,7 @@
field public static final int contentInsetRight = 16843862; // 0x1010456
field public static final int contentInsetStart = 16843859; // 0x1010453
field public static final int contentInsetStartWithNavigation = 16844066; // 0x1010522
- field @FlaggedApi("android.view.flags.sensitive_content_app_protection_api") public static final int contentSensitivity;
+ field @FlaggedApi("android.view.flags.sensitive_content_app_protection_api") public static final int contentSensitivity = 16844446; // 0x101069e
field public static final int contextClickable = 16844007; // 0x10104e7
field public static final int contextDescription = 16844078; // 0x101052e
field public static final int contextPopupMenuStyle = 16844033; // 0x1010501
@@ -688,7 +688,7 @@
field public static final int debuggable = 16842767; // 0x101000f
field public static final int defaultFocusHighlightEnabled = 16844130; // 0x1010562
field public static final int defaultHeight = 16844021; // 0x10104f5
- field @FlaggedApi("android.content.res.default_locale") public static final int defaultLocale;
+ field @FlaggedApi("android.content.res.default_locale") public static final int defaultLocale = 16844424; // 0x1010688
field public static final int defaultToDeviceProtectedStorage = 16844036; // 0x1010504
field public static final int defaultValue = 16843245; // 0x10101ed
field public static final int defaultWidth = 16844020; // 0x10104f4
@@ -858,7 +858,7 @@
field public static final int format24Hour = 16843723; // 0x10103cb
field public static final int fraction = 16843992; // 0x10104d8
field public static final int fragment = 16843491; // 0x10102e3
- field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int fragmentAdvancedPattern;
+ field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int fragmentAdvancedPattern = 16844438; // 0x1010696
field public static final int fragmentAllowEnterTransitionOverlap = 16843976; // 0x10104c8
field public static final int fragmentAllowReturnTransitionOverlap = 16843977; // 0x10104c9
field public static final int fragmentCloseEnterAnimation = 16843495; // 0x10102e7
@@ -869,13 +869,13 @@
field public static final int fragmentFadeExitAnimation = 16843498; // 0x10102ea
field public static final int fragmentOpenEnterAnimation = 16843493; // 0x10102e5
field public static final int fragmentOpenExitAnimation = 16843494; // 0x10102e6
- field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int fragmentPattern;
- field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int fragmentPrefix;
+ field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int fragmentPattern = 16844437; // 0x1010695
+ field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int fragmentPrefix = 16844436; // 0x1010694
field public static final int fragmentReenterTransition = 16843975; // 0x10104c7
field public static final int fragmentReturnTransition = 16843973; // 0x10104c5
field public static final int fragmentSharedElementEnterTransition = 16843972; // 0x10104c4
field public static final int fragmentSharedElementReturnTransition = 16843974; // 0x10104c6
- field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int fragmentSuffix;
+ field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int fragmentSuffix = 16844439; // 0x1010697
field public static final int freezesText = 16843116; // 0x101016c
field public static final int fromAlpha = 16843210; // 0x10101ca
field public static final int fromDegrees = 16843187; // 0x10101b3
@@ -1345,15 +1345,15 @@
field public static final int propertyYName = 16843893; // 0x1010475
field public static final int protectionLevel = 16842761; // 0x1010009
field public static final int publicKey = 16843686; // 0x10103a6
- field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int query;
+ field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int query = 16844431; // 0x101068f
field public static final int queryActionMsg = 16843227; // 0x10101db
- field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int queryAdvancedPattern;
+ field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int queryAdvancedPattern = 16844434; // 0x1010692
field public static final int queryAfterZeroResults = 16843394; // 0x1010282
field public static final int queryBackground = 16843911; // 0x1010487
field public static final int queryHint = 16843608; // 0x1010358
- field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int queryPattern;
- field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int queryPrefix;
- field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int querySuffix;
+ field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int queryPattern = 16844433; // 0x1010691
+ field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int queryPrefix = 16844432; // 0x1010690
+ field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int querySuffix = 16844435; // 0x1010693
field public static final int quickContactBadgeStyleSmallWindowLarge = 16843443; // 0x10102b3
field public static final int quickContactBadgeStyleSmallWindowMedium = 16843442; // 0x10102b2
field public static final int quickContactBadgeStyleSmallWindowSmall = 16843441; // 0x10102b1
@@ -1382,7 +1382,7 @@
field public static final int reqTouchScreen = 16843303; // 0x1010227
field public static final int requestLegacyExternalStorage = 16844291; // 0x1010603
field public static final int requestRawExternalStorageAccess = 16844357; // 0x1010645
- field @FlaggedApi("android.security.content_uri_permission_apis") public static final int requireContentUriPermissionFromCaller;
+ field @FlaggedApi("android.security.content_uri_permission_apis") public static final int requireContentUriPermissionFromCaller = 16844443; // 0x101069b
field public static final int requireDeviceScreenOn = 16844317; // 0x101061d
field public static final int requireDeviceUnlock = 16843756; // 0x10103ec
field public static final int required = 16843406; // 0x101028e
@@ -1494,12 +1494,12 @@
field @Deprecated public static final int sharedUserLabel = 16843361; // 0x1010261
field public static final int sharedUserMaxSdkVersion = 16844365; // 0x101064d
field public static final int shell = 16844180; // 0x1010594
- field @FlaggedApi("com.android.text.flags.use_bounds_for_width") public static final int shiftDrawingOffsetForStartOverhang;
+ field @FlaggedApi("com.android.text.flags.use_bounds_for_width") public static final int shiftDrawingOffsetForStartOverhang = 16844450; // 0x10106a2
field public static final int shortcutDisabledMessage = 16844075; // 0x101052b
field public static final int shortcutId = 16844072; // 0x1010528
field public static final int shortcutLongLabel = 16844074; // 0x101052a
field public static final int shortcutShortLabel = 16844073; // 0x1010529
- field @FlaggedApi("android.nfc.nfc_observe_mode") public static final int shouldDefaultToObserveMode;
+ field @FlaggedApi("android.nfc.nfc_observe_mode") public static final int shouldDefaultToObserveMode = 16844448; // 0x10106a0
field public static final int shouldDisableView = 16843246; // 0x10101ee
field public static final int shouldUseDefaultUnfoldTransition = 16844364; // 0x101064c
field public static final int showAsAction = 16843481; // 0x10102d9
@@ -1610,7 +1610,7 @@
field public static final int supportedTypes = 16844369; // 0x1010651
field public static final int supportsAssist = 16844016; // 0x10104f0
field public static final int supportsBatteryGameMode = 16844374; // 0x1010656
- field @FlaggedApi("android.view.inputmethod.connectionless_handwriting") public static final int supportsConnectionlessStylusHandwriting;
+ field @FlaggedApi("android.view.inputmethod.connectionless_handwriting") public static final int supportsConnectionlessStylusHandwriting = 16844447; // 0x101069f
field public static final int supportsInlineSuggestions = 16844301; // 0x101060d
field public static final int supportsInlineSuggestionsWithTouchExploration = 16844397; // 0x101066d
field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1
@@ -1631,7 +1631,7 @@
field public static final int switchTextOff = 16843628; // 0x101036c
field public static final int switchTextOn = 16843627; // 0x101036b
field public static final int syncable = 16842777; // 0x1010019
- field @FlaggedApi("android.multiuser.enable_system_user_only_for_services_and_providers") public static final int systemUserOnly;
+ field @FlaggedApi("android.multiuser.enable_system_user_only_for_services_and_providers") public static final int systemUserOnly = 16844429; // 0x101068d
field public static final int tabStripEnabled = 16843453; // 0x10102bd
field public static final int tabStripLeft = 16843451; // 0x10102bb
field public static final int tabStripRight = 16843452; // 0x10102bc
@@ -1808,12 +1808,12 @@
field public static final int updatePeriodMillis = 16843344; // 0x1010250
field public static final int use32bitAbi = 16844053; // 0x1010515
field public static final int useAppZygote = 16844183; // 0x1010597
- field @FlaggedApi("com.android.text.flags.use_bounds_for_width") public static final int useBoundsForWidth;
+ field @FlaggedApi("com.android.text.flags.use_bounds_for_width") public static final int useBoundsForWidth = 16844440; // 0x1010698
field public static final int useDefaultMargins = 16843641; // 0x1010379
field public static final int useEmbeddedDex = 16844190; // 0x101059e
field public static final int useIntrinsicSizeAsMinimum = 16843536; // 0x1010310
field public static final int useLevel = 16843167; // 0x101019f
- field @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") public static final int useLocalePreferredLineHeightForMinimum;
+ field @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") public static final int useLocalePreferredLineHeightForMinimum = 16844445; // 0x101069d
field public static final int userVisible = 16843409; // 0x1010291
field public static final int usesCleartextTraffic = 16844012; // 0x10104ec
field public static final int usesPermissionFlags = 16844356; // 0x1010644
@@ -1892,7 +1892,7 @@
field public static final int windowFullscreen = 16843277; // 0x101020d
field public static final int windowHideAnimation = 16842935; // 0x10100b7
field public static final int windowIsFloating = 16842839; // 0x1010057
- field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final int windowIsFrameRatePowerSavingsBalanced;
+ field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final int windowIsFrameRatePowerSavingsBalanced = 16844451; // 0x10106a3
field public static final int windowIsTranslucent = 16842840; // 0x1010058
field public static final int windowLayoutAffinity = 16844313; // 0x1010619
field public static final int windowLayoutInDisplayCutoutMode = 16844166; // 0x1010586
@@ -1903,7 +1903,7 @@
field public static final int windowNoDisplay = 16843294; // 0x101021e
field public static final int windowNoMoveAnimation = 16844421; // 0x1010685
field public static final int windowNoTitle = 16842838; // 0x1010056
- field @FlaggedApi("com.android.window.flags.enforce_edge_to_edge") public static final int windowOptOutEdgeToEdgeEnforcement;
+ field @FlaggedApi("com.android.window.flags.enforce_edge_to_edge") public static final int windowOptOutEdgeToEdgeEnforcement = 16844442; // 0x101069a
field @Deprecated public static final int windowOverscan = 16843727; // 0x10103cf
field public static final int windowReenterTransition = 16843951; // 0x10104af
field public static final int windowReturnTransition = 16843950; // 0x10104ae
@@ -2015,19 +2015,19 @@
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_0;
- field public static final int system_error_10;
- field public static final int system_error_100;
- field public static final int system_error_1000;
- field public static final int system_error_200;
- field public static final int system_error_300;
- field public static final int system_error_400;
- field public static final int system_error_50;
- field public static final int system_error_500;
- field public static final int system_error_600;
- field public static final int system_error_700;
- field public static final int system_error_800;
- field public static final int system_error_900;
+ field public static final int system_error_0 = 17170629; // 0x10600c5
+ field public static final int system_error_10 = 17170630; // 0x10600c6
+ field public static final int system_error_100 = 17170632; // 0x10600c8
+ field public static final int system_error_1000 = 17170641; // 0x10600d1
+ field public static final int system_error_200 = 17170633; // 0x10600c9
+ field public static final int system_error_300 = 17170634; // 0x10600ca
+ field public static final int system_error_400 = 17170635; // 0x10600cb
+ field public static final int system_error_50 = 17170631; // 0x10600c7
+ field public static final int system_error_500 = 17170636; // 0x10600cc
+ field public static final int system_error_600 = 17170637; // 0x10600cd
+ field public static final int system_error_700 = 17170638; // 0x10600ce
+ field public static final int system_error_800 = 17170639; // 0x10600cf
+ field public static final int system_error_900 = 17170640; // 0x10600d0
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
@@ -2077,7 +2077,7 @@
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_disabled;
+ field public static final int system_on_surface_disabled = 17170627; // 0x10600c3
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
@@ -2088,7 +2088,7 @@
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_disabled;
+ field public static final int system_outline_disabled = 17170628; // 0x10600c4
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
@@ -2129,7 +2129,7 @@
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_disabled;
+ field public static final int system_surface_disabled = 17170626; // 0x10600c2
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
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index e12da63..f10c0fc 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -437,10 +437,10 @@
public static final class R.attr {
field public static final int allowClearUserDataOnFailedRestore = 16844288; // 0x1010600
- field @FlaggedApi("android.content.res.manifest_flagging") public static final int featureFlag;
+ field @FlaggedApi("android.content.res.manifest_flagging") public static final int featureFlag = 16844428; // 0x101068c
field public static final int gameSessionService = 16844373; // 0x1010655
field public static final int hotwordDetectionService = 16844326; // 0x1010626
- field @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") public static final int isVirtualDeviceOnly;
+ field @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") public static final int isVirtualDeviceOnly = 16844425; // 0x1010689
field public static final int isVrOnly = 16844152; // 0x1010578
field public static final int minExtensionVersion = 16844305; // 0x1010611
field public static final int playHomeTransitionSound = 16844358; // 0x1010646
@@ -492,9 +492,9 @@
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 = 17039429; // 0x1040045
- field @FlaggedApi("android.permission.flags.retail_demo_role_enabled") public static final int config_defaultRetailDemo;
+ field @FlaggedApi("android.permission.flags.retail_demo_role_enabled") public static final int config_defaultRetailDemo = 17039432; // 0x1040048
field public static final int config_defaultSms = 17039396; // 0x1040024
- field @FlaggedApi("android.permission.flags.wallet_role_enabled") public static final int config_defaultWallet;
+ field @FlaggedApi("android.permission.flags.wallet_role_enabled") public static final int config_defaultWallet = 17039433; // 0x1040049
field public static final int config_devicePolicyManagement = 17039421; // 0x104003d
field public static final int config_feedbackIntentExtraKey = 17039391; // 0x104001f
field public static final int config_feedbackIntentNameKey = 17039392; // 0x1040020
diff --git a/core/java/android/app/AppOpInfo.java b/core/java/android/app/AppOpInfo.java
index 5268ec4..a0f0cca 100644
--- a/core/java/android/app/AppOpInfo.java
+++ b/core/java/android/app/AppOpInfo.java
@@ -88,7 +88,7 @@
/**
* This specifies whether each option is only allowed to be read
- * by apps with manage appops permission.
+ * by apps with privileged appops permission.
*/
public final boolean restrictRead;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 8e766c9..20b2357 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -3265,7 +3265,7 @@
}
/**
- * Retrieve whether the op can be read by apps with manage appops permission.
+ * Retrieve whether the op can be read by apps with privileged appops permission.
* @hide
*/
public static boolean opRestrictsRead(int op) {
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index f1e44cc..1df8f63 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -1104,6 +1104,10 @@
return true;
}
+ if (mDataDir == null) {
+ return false;
+ }
+
// Temporarily disable logging of disk reads on the Looper thread as this is necessary -
// and the loader will access the directory anyway if we don't check it.
StrictMode.ThreadPolicy oldThreadPolicy = allowThreadDiskReads();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 4d7e29b..05a2aec 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -9770,6 +9770,12 @@
* You can opt-out of this behavior by using {@link Notification.Builder#setColorized(boolean)}.
* <p>
*
+ * <p>
+ * Starting at {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM Android V} the
+ * {@link Notification#FLAG_NO_CLEAR NO_CLEAR flag} will be set for valid MediaStyle
+ * notifications.
+ * <p>
+ *
* To use this style with your Notification, feed it to
* {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
* <pre class="prettyprint">
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index f092945..9b3fb5c 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -1197,6 +1197,24 @@
/**
* Callback called when a particular foreground service type has timed out.
*
+ * <p>This callback is meant to give the app a small grace period of a few seconds to finish
+ * the foreground service of the offending type - if it fails to do so, the app will be
+ * declared an ANR.
+ *
+ * <p>The foreground service of the offending type can be stopped within the time limit by
+ * {@link android.app.Service#stopSelf()},
+ * {@link android.content.Context#stopService(android.content.Intent)} or their overloads.
+ * {@link android.app.Service#stopForeground(int)} can be used as well, which demotes the
+ * service to a "background" service, which will soon be stopped by the system.
+ *
+ * <p>The specific time limit for each type (if one exists) is mentioned in the documentation
+ * for that foreground service type.
+ *
+ * <p>Note: time limits are restricted to a rolling 24-hour window - for example, if a
+ * foreground service type has a time limit of 6 hours, that time counter begins as soon as the
+ * foreground service starts. This time limit will only be reset once every 24 hours or if the
+ * app comes into the foreground state.
+ *
* @param startId the startId passed to {@link #onStartCommand(Intent, int, int)} when
* the service started.
* @param fgsType the {@link ServiceInfo.ForegroundServiceType foreground service type} which
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index c6a8762..342479b 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -5062,21 +5062,29 @@
/**
* <p>The version of the session configuration query
* {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }
- * API</p>
+ * and {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#getSessionCharacteristics }
+ * APIs.</p>
* <p>The possible values in this key correspond to the values defined in
* android.os.Build.VERSION_CODES. Each version defines a set of feature combinations the
* camera device must reliably report whether they are supported via
- * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }
- * API. And the version is always less or equal to android.os.Build.VERSION.SDK_INT.</p>
- * <p>If set to UPSIDE_DOWN_CAKE, this camera device doesn't support
* {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }.
- * Calling the method for this camera ID throws an UnsupportedOperationException.</p>
- * <p>If set to VANILLA_ICE_CREAM, the application can call
+ * It also defines the set of session specific keys in CameraCharacteristics when returned from
+ * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#getSessionCharacteristics }.
+ * The version is always less or equal to android.os.Build.VERSION.SDK_INT.</p>
+ * <p>If set to UPSIDE_DOWN_CAKE, this camera device doesn't support the
+ * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup } API.
+ * Trying to create a CameraDeviceSetup instance throws an UnsupportedOperationException.</p>
+ * <p>From VANILLA_ICE_CREAM onwards, the camera compliance tests verify a set of
+ * commonly used SessionConfigurations to ensure that the outputs of
* {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }
- * to check if the combinations of below features are supported.</p>
+ * and {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#getSessionCharacteristics }
+ * are accurate. The application is encouraged to use these SessionConfigurations when turning on
+ * multiple features at the same time.</p>
+ * <p>When set to VANILLA_ICE_CREAM, the combinations of the following configurations are verified
+ * by the compliance tests:</p>
* <ul>
- * <li>A subset of LIMITED-level device stream combinations.</li>
- * </ul>
+ * <li>
+ * <p>A set of commonly used stream combinations:</p>
* <table>
* <thead>
* <tr>
@@ -5084,257 +5092,108 @@
* <th style="text-align: center;">Size</th>
* <th style="text-align: center;">Target 2</th>
* <th style="text-align: center;">Size</th>
- * <th style="text-align: center;">Sample use case(s)</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;">Simple preview, GPU video processing, or no-preview video recording.</td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
* <td style="text-align: center;">S1080P</td>
* <td style="text-align: center;"></td>
* <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
* </tr>
* <tr>
* <td style="text-align: center;">PRIV</td>
* <td style="text-align: center;">S720P</td>
* <td style="text-align: center;"></td>
* <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;">In-application video/image processing.</td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;">Standard still imaging.</td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
* </tr>
* <tr>
* <td style="text-align: center;">PRIV</td>
* <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">MAXIMUM_16_9</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center;">PRIV</td>
+ * <td style="text-align: center;">S1080P</td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">UHD</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center;">PRIV</td>
+ * <td style="text-align: center;">S1080P</td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">S1440P</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center;">PRIV</td>
+ * <td style="text-align: center;">S1080P</td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">S1080P</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center;">PRIV</td>
+ * <td style="text-align: center;">S1080P</td>
+ * <td style="text-align: center;">PRIV</td>
+ * <td style="text-align: center;">UHD</td>
* </tr>
* <tr>
* <td style="text-align: center;">PRIV</td>
* <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;"></td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">MAXIMUM_16_9</td>
* </tr>
* <tr>
* <td style="text-align: center;">PRIV</td>
* <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;">In-app processing plus still capture.</td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;">Standard recording.</td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;"></td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">UHD</td>
* </tr>
* <tr>
* <td style="text-align: center;">PRIV</td>
* <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;">Preview plus in-app processing.</td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
* <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;"></td>
* </tr>
* <tr>
* <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;"></td>
+ * <td style="text-align: center;">XVGA</td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">MAXIMUM_4_3</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center;">PRIV</td>
+ * <td style="text-align: center;">S1080P_4_3</td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">MAXIMUM_4_3</td>
* </tr>
* </tbody>
* </table>
- * <pre><code>- {@code MAXIMUM} size refers to the camera device's maximum output resolution for
- * that format from {@code StreamConfigurationMap#getOutputSizes}. {@code PREVIEW} size
- * refers to the best size match to the device's screen resolution, or to 1080p
- * (@code 1920x1080}, whichever is smaller. Both sizes are guaranteed to be supported.
- *
- * - {@code S1440P} refers to {@code 1920x1440 (4:3)} and {@code 2560x1440 (16:9)}.
- * {@code S1080P} refers to {@code 1440x1080 (4:3)} and {@code 1920x1080 (16:9)}.
- * And {@code S720P} refers to {@code 960x720 (4:3)} and {@code 1280x720 (16:9)}.
- *
- * - If a combination contains a S1440P, S1080P, or S720P stream,
- * both 4:3 and 16:9 aspect ratio sizes can be queried. For example, for the
- * stream combination of {PRIV, S1440P, JPEG, MAXIMUM}, and if MAXIMUM ==
- * 4032 x 3024, the application will be able to query both
- * {PRIV, 1920 x 1440, JPEG, 4032 x 3024} and {PRIV, 2560 x 1440, JPEG, 4032 x 2268}
- * without an exception being thrown.
- * </code></pre>
* <ul>
- * <li>VIDEO_STABILIZATION_MODES: {OFF, PREVIEW}</li>
- * <li>AE_TARGET_FPS_RANGE: { {<em>, 30}, {</em>, 60} }</li>
- * <li>DYNAMIC_RANGE_PROFILE: {STANDARD, HLG10}</li>
+ * <li>{@code MAXIMUM_4_3} refers to the camera device's maximum output resolution with
+ * 4:3 aspect ratio for that format from {@code StreamConfigurationMap#getOutputSizes}.</li>
+ * <li>{@code MAXIMUM_16_9} is the maximum output resolution with 16:9 aspect ratio.</li>
+ * <li>{@code S1440P} refers to {@code 2560x1440 (16:9)}.</li>
+ * <li>{@code S1080P} refers to {@code 1920x1080 (16:9)}.</li>
+ * <li>{@code S720P} refers to {@code 1280x720 (16:9)}.</li>
+ * <li>{@code UHD} refers to {@code 3840x2160 (16:9)}.</li>
+ * <li>{@code XVGA} refers to {@code 1024x768 (4:3)}.</li>
+ * <li>{@code S1080P_43} refers to {@code 1440x1080 (4:3)}.</li>
* </ul>
+ * </li>
+ * <li>
+ * <p>VIDEO_STABILIZATION_MODE: {OFF, PREVIEW}</p>
+ * </li>
+ * <li>
+ * <p>AE_TARGET_FPS_RANGE: { {*, 30}, {*, 60} }</p>
+ * </li>
+ * <li>
+ * <p>DYNAMIC_RANGE_PROFILE: {STANDARD, HLG10}</p>
+ * </li>
+ * </ul>
+ * <p>All of the above configurations can be set up with a SessionConfiguration. The list of
+ * OutputConfiguration contains the stream configurations and DYNAMIC_RANGE_PROFILE, and
+ * the AE_TARGET_FPS_RANGE and VIDEO_STABILIZATION_MODE are set as session parameters.</p>
* <p>This key is available on all devices.</p>
*/
@PublicKey
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index eb644e8..dfbf06b 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -917,8 +917,11 @@
* image. For example, it can be used as a temporary placeholder for the requested capture
* while the final image is being processed. The supported sizes for a still capture's postview
* can be retrieved using
- * {@link CameraExtensionCharacteristics#getPostviewSupportedSizes(int, Size, int)}.
- * The formats of the still capture and postview should be equivalent upon capture request.</p>
+ * {@link CameraExtensionCharacteristics#getPostviewSupportedSizes(int, Size, int)}.</p>
+ *
+ * <p>Starting with Android {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM},
+ * the formats of the still capture and postview are not required to be equivalent upon capture
+ * request.</p>
*
* @param extension the extension type
* @return {@code true} in case postview is supported, {@code false} otherwise
@@ -976,8 +979,7 @@
*
* @param extension the extension type
* @param captureSize size of the still capture for which the postview is requested
- * @param format device-specific extension output format of the still capture and
- * postview
+ * @param format device-specific extension output format of the postview
* @return non-modifiable list of available sizes or an empty list if the format and
* size is not supported.
* @throws IllegalArgumentException in case of unsupported extension or if postview
@@ -1018,8 +1020,8 @@
}
IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
extender.init(mCameraId, mCharacteristicsMapNative);
- return generateSupportedSizes(extender.getSupportedPostviewResolutions(
- sz), format, streamMap);
+ return getSupportedSizes(extender.getSupportedPostviewResolutions(sz),
+ format);
} else {
Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
initializeExtension(extension);
@@ -1034,15 +1036,13 @@
}
if (format == ImageFormat.YUV_420_888) {
- return generateSupportedSizes(
- extenders.second.getSupportedPostviewResolutions(sz),
- format, streamMap);
+ return getSupportedSizes(
+ extenders.second.getSupportedPostviewResolutions(sz), format);
} else if (format == ImageFormat.JPEG) {
// The framework will perform the additional encoding pass on the
// processed YUV_420 buffers.
- return generateJpegSupportedSizes(
- extenders.second.getSupportedPostviewResolutions(sz),
- streamMap);
+ return getSupportedSizes(
+ extenders.second.getSupportedPostviewResolutions(sz), format);
} else if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) {
// Jpeg_R/UltraHDR + YCBCR_P010 is currently not supported in the basic
// extension case
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java b/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
index 875550a..a10e250 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
@@ -36,6 +36,8 @@
import android.util.Log;
import android.view.Surface;
+import com.android.internal.camera.flags.Flags;
+
import java.nio.ByteBuffer;
import java.util.HashSet;
import java.util.Iterator;
@@ -57,6 +59,8 @@
private android.hardware.camera2.extension.Size mResolution = null;
private android.hardware.camera2.extension.Size mPostviewResolution = null;
private int mFormat = -1;
+ private int mPostviewFormat = -1;
+ private int mCaptureFormat = -1;
private Surface mOutputSurface = null;
private ImageWriter mOutputWriter = null;
private Surface mPostviewOutputSurface = null;
@@ -204,10 +208,12 @@
}
public void onOutputSurface(Surface surface, int format) throws RemoteException {
- if (format != ImageFormat.JPEG) {
+ if (!Flags.extension10Bit() && format != ImageFormat.JPEG) {
Log.e(TAG, "Unsupported output format: " + format);
return;
}
+ CameraExtensionUtils.SurfaceInfo surfaceInfo = CameraExtensionUtils.querySurface(surface);
+ mCaptureFormat = surfaceInfo.mFormat;
mOutputSurface = surface;
initializePipeline();
}
@@ -215,10 +221,11 @@
public void onPostviewOutputSurface(Surface surface) throws RemoteException {
CameraExtensionUtils.SurfaceInfo postviewSurfaceInfo =
CameraExtensionUtils.querySurface(surface);
- if (postviewSurfaceInfo.mFormat != ImageFormat.JPEG) {
+ if (!Flags.extension10Bit() && postviewSurfaceInfo.mFormat != ImageFormat.JPEG) {
Log.e(TAG, "Unsupported output format: " + postviewSurfaceInfo.mFormat);
return;
}
+ mPostviewFormat = postviewSurfaceInfo.mFormat;
mPostviewOutputSurface = surface;
initializePostviewPipeline();
}
@@ -233,7 +240,7 @@
}
public void onImageFormatUpdate(int format) throws RemoteException {
- if (format != ImageFormat.YUV_420_888) {
+ if (!Flags.extension10Bit() && format != ImageFormat.YUV_420_888) {
Log.e(TAG, "Unsupported input format: " + format);
return;
}
@@ -244,33 +251,45 @@
private void initializePipeline() throws RemoteException {
if ((mFormat != -1) && (mOutputSurface != null) && (mResolution != null) &&
(mYuvReader == null)) {
- // Jpeg/blobs are expected to be configured with (w*h)x1.5 + 64k Jpeg APP1 segment
- mOutputWriter = ImageWriter.newInstance(mOutputSurface, 1 /*maxImages*/,
- ImageFormat.JPEG,
- (mResolution.width * mResolution.height * 3)/2 + JPEG_APP_SEGMENT_SIZE, 1);
- mYuvReader = ImageReader.newInstance(mResolution.width, mResolution.height, mFormat,
- JPEG_QUEUE_SIZE);
- mYuvReader.setOnImageAvailableListener(
- new YuvCallback(mYuvReader, mOutputWriter), mHandler);
- mProcessor.onOutputSurface(mYuvReader.getSurface(), mFormat);
+ if (Flags.extension10Bit() && mCaptureFormat == ImageFormat.YUV_420_888) {
+ // For the case when postview is JPEG and capture is YUV
+ mProcessor.onOutputSurface(mOutputSurface, mCaptureFormat);
+ } else {
+ // Jpeg/blobs are expected to be configured with (w*h)x1.5 + 64k Jpeg APP1 segment
+ mOutputWriter = ImageWriter.newInstance(mOutputSurface, 1 /*maxImages*/,
+ ImageFormat.JPEG,
+ (mResolution.width * mResolution.height * 3) / 2
+ + JPEG_APP_SEGMENT_SIZE, 1);
+ mYuvReader = ImageReader.newInstance(mResolution.width, mResolution.height,
+ mFormat, JPEG_QUEUE_SIZE);
+ mYuvReader.setOnImageAvailableListener(
+ new YuvCallback(mYuvReader, mOutputWriter), mHandler);
+ mProcessor.onOutputSurface(mYuvReader.getSurface(), mFormat);
+ }
mProcessor.onResolutionUpdate(mResolution, mPostviewResolution);
- mProcessor.onImageFormatUpdate(mFormat);
+ mProcessor.onImageFormatUpdate(ImageFormat.YUV_420_888);
}
}
private void initializePostviewPipeline() throws RemoteException {
if ((mFormat != -1) && (mPostviewOutputSurface != null) && (mPostviewResolution != null)
&& (mPostviewYuvReader == null)) {
- // Jpeg/blobs are expected to be configured with (w*h)x1
- mPostviewOutputWriter = ImageWriter.newInstance(mPostviewOutputSurface, 1/*maxImages*/,
- ImageFormat.JPEG, mPostviewResolution.width * mPostviewResolution.height, 1);
- mPostviewYuvReader = ImageReader.newInstance(mPostviewResolution.width,
- mPostviewResolution.height, mFormat, JPEG_QUEUE_SIZE);
- mPostviewYuvReader.setOnImageAvailableListener(
- new YuvCallback(mPostviewYuvReader, mPostviewOutputWriter), mHandler);
- mProcessor.onPostviewOutputSurface(mPostviewYuvReader.getSurface());
+ if (Flags.extension10Bit() && mPostviewFormat == ImageFormat.YUV_420_888) {
+ // For the case when postview is YUV and capture is JPEG
+ mProcessor.onPostviewOutputSurface(mPostviewOutputSurface);
+ } else {
+ // Jpeg/blobs are expected to be configured with (w*h)x1
+ mPostviewOutputWriter = ImageWriter.newInstance(mPostviewOutputSurface,
+ 1/*maxImages*/, ImageFormat.JPEG,
+ mPostviewResolution.width * mPostviewResolution.height, 1);
+ mPostviewYuvReader = ImageReader.newInstance(mPostviewResolution.width,
+ mPostviewResolution.height, mFormat, JPEG_QUEUE_SIZE);
+ mPostviewYuvReader.setOnImageAvailableListener(
+ new YuvCallback(mPostviewYuvReader, mPostviewOutputWriter), mHandler);
+ mProcessor.onPostviewOutputSurface(mPostviewYuvReader.getSurface());
+ }
mProcessor.onResolutionUpdate(mResolution, mPostviewResolution);
- mProcessor.onImageFormatUpdate(mFormat);
+ mProcessor.onImageFormatUpdate(ImageFormat.YUV_420_888);
}
}
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index c00e610..3ae3199 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -390,7 +390,16 @@
if (surfaceInfo.mFormat == ImageFormat.JPEG) {
mImageJpegProcessor = new CameraExtensionJpegProcessor(mImageProcessor);
mImageProcessor = mImageJpegProcessor;
+ } else if (Flags.extension10Bit() && mClientPostviewSurface != null) {
+ // Handles case when postview is JPEG and capture is YUV
+ CameraExtensionUtils.SurfaceInfo postviewSurfaceInfo =
+ CameraExtensionUtils.querySurface(mClientPostviewSurface);
+ if (postviewSurfaceInfo.mFormat == ImageFormat.JPEG) {
+ mImageJpegProcessor = new CameraExtensionJpegProcessor(mImageProcessor);
+ mImageProcessor = mImageJpegProcessor;
+ }
}
+
mBurstCaptureImageReader = ImageReader.newInstance(surfaceInfo.mWidth,
surfaceInfo.mHeight, CameraExtensionCharacteristics.PROCESSING_INPUT_FORMAT,
mImageExtender.getMaxCaptureStage());
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
index f0c6e2e..40f0477 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
@@ -112,19 +112,30 @@
if (outputConfig == null) return null;
SurfaceInfo surfaceInfo = querySurface(outputConfig.getSurface());
- if (surfaceInfo.mFormat == captureFormat) {
- if (supportedPostviewSizes.containsKey(captureFormat)) {
- Size postviewSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight);
- if (supportedPostviewSizes.get(surfaceInfo.mFormat)
- .contains(postviewSize)) {
- return outputConfig.getSurface();
- } else {
- throw new IllegalArgumentException("Postview size not supported!");
- }
+
+ if (Flags.extension10Bit()) {
+ Size postviewSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight);
+ if (supportedPostviewSizes.get(surfaceInfo.mFormat)
+ .contains(postviewSize)) {
+ return outputConfig.getSurface();
+ } else {
+ throw new IllegalArgumentException("Postview size not supported!");
}
} else {
- throw new IllegalArgumentException("Postview format should be equivalent to " +
- " the capture format!");
+ if (surfaceInfo.mFormat == captureFormat) {
+ if (supportedPostviewSizes.containsKey(captureFormat)) {
+ Size postviewSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight);
+ if (supportedPostviewSizes.get(surfaceInfo.mFormat)
+ .contains(postviewSize)) {
+ return outputConfig.getSurface();
+ } else {
+ throw new IllegalArgumentException("Postview size not supported!");
+ }
+ }
+ } else {
+ throw new IllegalArgumentException("Postview format should be equivalent to "
+ + " the capture format!");
+ }
}
return null;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 72ab970..e6ddf35 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1736,6 +1736,24 @@
"android.settings.NETWORK_OPERATOR_SETTINGS";
/**
+ * Activity Action: Show settings for selecting the network provider.
+ * <p>
+ * In some cases, a matching Activity may not be provided, so ensure you
+ * safeguard against this.
+ * <p>
+ * Access to this preference can be customized via Settings' app.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_NETWORK_PROVIDER_SETTINGS =
+ "android.settings.NETWORK_PROVIDER_SETTINGS";
+
+ /**
* Activity Action: Show settings for selection of 2G/3G.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 997c9581..5f6bdbf 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1383,16 +1383,22 @@
DreamService.DREAM_META_DATA, DREAM_META_DATA_ROOT_TAG,
com.android.internal.R.styleable.Dream)) {
if (rawMetadata == null) return null;
- return new DreamMetadata(
- convertToComponentName(
- rawMetadata.getString(
- com.android.internal.R.styleable.Dream_settingsActivity), serviceInfo),
- rawMetadata.getDrawable(
- com.android.internal.R.styleable.Dream_previewImage),
- rawMetadata.getBoolean(R.styleable.Dream_showClockAndComplications,
- DEFAULT_SHOW_COMPLICATIONS),
- rawMetadata.getInt(R.styleable.Dream_dreamCategory, DREAM_CATEGORY_DEFAULT)
- );
+ try {
+ return new DreamMetadata(
+ convertToComponentName(
+ rawMetadata.getString(
+ com.android.internal.R.styleable.Dream_settingsActivity),
+ serviceInfo),
+ rawMetadata.getDrawable(
+ com.android.internal.R.styleable.Dream_previewImage),
+ rawMetadata.getBoolean(R.styleable.Dream_showClockAndComplications,
+ DEFAULT_SHOW_COMPLICATIONS),
+ rawMetadata.getInt(R.styleable.Dream_dreamCategory, DREAM_CATEGORY_DEFAULT)
+ );
+ } catch (Exception exception) {
+ Log.e(TAG, "Failed to create read metadata", exception);
+ return null;
+ }
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3bc6829..f0d27da 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -4276,6 +4276,10 @@
mPreferredFrameRate = -1;
mIsFrameRateConflicted = false;
mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_UNKNOWN;
+ } else if (mPreferredFrameRate == 0) {
+ // From MSG_FRAME_RATE_SETTING, where mPreferredFrameRate is set to 0
+ setPreferredFrameRate(0);
+ mPreferredFrameRate = -1;
}
}
@@ -12565,6 +12569,13 @@
case FRAME_RATE_CATEGORY_HIGH ->
mFrameRateCategoryHighCount = FRAME_RATE_CATEGORY_COUNT;
}
+
+ // If it's currently an intermittent update,
+ // we should keep mPreferredFrameRateCategory as NORMAL
+ if (intermittentUpdateState() == INTERMITTENT_STATE_INTERMITTENT) {
+ return;
+ }
+
if (mFrameRateCategoryHighCount > 0) {
mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH;
} else if (mFrameRateCategoryHighHintCount > 0) {
diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java
index 7c7c7b8..9f9aae5 100644
--- a/core/java/com/android/internal/os/PowerStats.java
+++ b/core/java/com/android/internal/os/PowerStats.java
@@ -473,7 +473,14 @@
} finally {
// Unconditionally skip to the end of the written data, even if the actual parcel
// format is incompatible
- parcel.setDataPosition(endPos);
+ if (endPos > parcel.dataPosition()) {
+ if (endPos >= parcel.dataSize()) {
+ throw new IndexOutOfBoundsException(
+ "PowerStats end position: " + endPos + " is outside the parcel bounds: "
+ + parcel.dataSize());
+ }
+ parcel.setDataPosition(endPos);
+ }
}
}
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index 69d3d6a..c21a43e 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -53,8 +53,6 @@
public ScreenshotHelper(Context context) {
mContext = context;
- IntentFilter filter = new IntentFilter(ACTION_USER_SWITCHED);
- mContext.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED);
}
/**
@@ -108,6 +106,8 @@
public void takeScreenshotInternal(ScreenshotRequest request, @NonNull Handler handler,
@Nullable Consumer<Uri> completionConsumer, long timeoutMs) {
synchronized (mScreenshotLock) {
+ mContext.registerReceiver(mBroadcastReceiver,
+ new IntentFilter(ACTION_USER_SWITCHED), Context.RECEIVER_EXPORTED);
final Runnable mScreenshotTimeout = () -> {
synchronized (mScreenshotLock) {
@@ -223,6 +223,11 @@
mScreenshotConnection = null;
mScreenshotService = null;
}
+ try {
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Attempted to remove broadcast receiver twice");
+ }
}
/**
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 91ef324..0d1be38 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3656,6 +3656,7 @@
"emergency" = Launch emergency dialer
"lockdown" = Lock down device until the user authenticates
"logout" = Logout the current user
+ "system_update" = Launch System Update screen
-->
<string-array translatable="false" name="config_globalActionsList">
<item>emergency</item>
diff --git a/core/res/res/values/public-final.xml b/core/res/res/values/public-final.xml
index daa0f553..d421944 100644
--- a/core/res/res/values/public-final.xml
+++ b/core/res/res/values/public-final.xml
@@ -3741,4 +3741,185 @@
<!-- @hide @SystemApi -->
<public type="bool" name="config_enableDefaultNotesForWorkProfile" id="0x0111000b" />
+ <!-- ===============================================================
+ 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-final type="attr" first-id="0x01bd0000">
+ <!-- @FlaggedApi("android.content.res.default_locale") -->
+ <public name="defaultLocale"/>
+ <!-- @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime")
+ @hide @SystemApi -->
+ <public name="isVirtualDeviceOnly"/>
+ <!-- Marking this entry as removed since it's not being finalized -->
+ <public name="removed_optional" />
+ <!-- Marking this entry as removed since it's not being finalized -->
+ <public name="removed_adServiceTypes" />
+ <!-- @hide @SystemApi @FlaggedApi("android.content.res.manifest_flagging") -->
+ <public name="featureFlag"/>
+ <!-- @FlaggedApi("android.multiuser.enable_system_user_only_for_services_and_providers") -->
+ <public name="systemUserOnly"/>
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public name="allow"/>
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public name="query"/>
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public name="queryPrefix"/>
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public name="queryPattern"/>
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public name="queryAdvancedPattern"/>
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public name="querySuffix"/>
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public name="fragmentPrefix"/>
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public name="fragmentPattern"/>
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public name="fragmentAdvancedPattern"/>
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public name="fragmentSuffix"/>
+ <!-- @FlaggedApi("com.android.text.flags.use_bounds_for_width") -->
+ <public name="useBoundsForWidth"/>
+ <!-- @FlaggedApi("android.nfc.nfc_read_polling_loop") -->
+ <public name="autoTransact"/>
+ <!-- @FlaggedApi("com.android.window.flags.enforce_edge_to_edge") -->
+ <public name="windowOptOutEdgeToEdgeEnforcement"/>
+ <!-- @FlaggedApi("android.security.content_uri_permission_apis") -->
+ <public name="requireContentUriPermissionFromCaller" />
+ <!-- Marking this entry as removed since it's not being finalized -->
+ <public name="removed_languageSettingsActivity" />
+ <!-- @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") -->
+ <public name="useLocalePreferredLineHeightForMinimum"/>
+ <!-- @FlaggedApi("android.view.flags.sensitive_content_app_protection_api") -->
+ <public name="contentSensitivity" />
+ <!-- @FlaggedApi("android.view.inputmethod.connectionless_handwriting") -->
+ <public name="supportsConnectionlessStylusHandwriting" />
+ <!-- @FlaggedApi("android.nfc.nfc_observe_mode") -->
+ <public name="shouldDefaultToObserveMode"/>
+ <!-- @FlaggedApi("android.security.asm_restrictions_enabled") -->
+ <public name="allowCrossUidActivitySwitchFromBelow"/>
+ <!-- @FlaggedApi("com.android.text.flags.use_bounds_for_width") -->
+ <public name="shiftDrawingOffsetForStartOverhang" />
+ <!-- @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") -->
+ <public name="windowIsFrameRatePowerSavingsBalanced"/>
+ <!-- Marking this entry as removed since it's not being finalized -->
+ <public name="removed_dreamCategory" />
+ </staging-public-group-final>
+
+ <!-- @FlaggedApi("android.content.res.default_locale") -->
+ <public type="attr" name="defaultLocale" id="0x01010688" />
+ <!-- @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime")
+ @hide @SystemApi -->
+ <public type="attr" name="isVirtualDeviceOnly" id="0x01010689" />
+ <!-- @hide @SystemApi @FlaggedApi("android.content.res.manifest_flagging") -->
+ <public type="attr" name="featureFlag" id="0x0101068c" />
+ <!-- @FlaggedApi("android.multiuser.enable_system_user_only_for_services_and_providers") -->
+ <public type="attr" name="systemUserOnly" id="0x0101068d" />
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public type="attr" name="allow" id="0x0101068e" />
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public type="attr" name="query" id="0x0101068f" />
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public type="attr" name="queryPrefix" id="0x01010690" />
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public type="attr" name="queryPattern" id="0x01010691" />
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public type="attr" name="queryAdvancedPattern" id="0x01010692" />
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public type="attr" name="querySuffix" id="0x01010693" />
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public type="attr" name="fragmentPrefix" id="0x01010694" />
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public type="attr" name="fragmentPattern" id="0x01010695" />
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public type="attr" name="fragmentAdvancedPattern" id="0x01010696" />
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public type="attr" name="fragmentSuffix" id="0x01010697" />
+ <!-- @FlaggedApi("com.android.text.flags.use_bounds_for_width") -->
+ <public type="attr" name="useBoundsForWidth" id="0x01010698" />
+ <!-- @FlaggedApi("android.nfc.nfc_read_polling_loop") -->
+ <public type="attr" name="autoTransact" id="0x01010699" />
+ <!-- @FlaggedApi("com.android.window.flags.enforce_edge_to_edge") -->
+ <public type="attr" name="windowOptOutEdgeToEdgeEnforcement" id="0x0101069a" />
+ <!-- @FlaggedApi("android.security.content_uri_permission_apis") -->
+ <public type="attr" name="requireContentUriPermissionFromCaller" id="0x0101069b" />
+ <!-- @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") -->
+ <public type="attr" name="useLocalePreferredLineHeightForMinimum" id="0x0101069d" />
+ <!-- @FlaggedApi("android.view.flags.sensitive_content_app_protection_api") -->
+ <public type="attr" name="contentSensitivity" id="0x0101069e" />
+ <!-- @FlaggedApi("android.view.inputmethod.connectionless_handwriting") -->
+ <public type="attr" name="supportsConnectionlessStylusHandwriting" id="0x0101069f" />
+ <!-- @FlaggedApi("android.nfc.nfc_observe_mode") -->
+ <public type="attr" name="shouldDefaultToObserveMode" id="0x010106a0" />
+ <!-- @FlaggedApi("android.security.asm_restrictions_enabled") -->
+ <public type="attr" name="allowCrossUidActivitySwitchFromBelow" id="0x010106a1" />
+ <!-- @FlaggedApi("com.android.text.flags.use_bounds_for_width") -->
+ <public type="attr" name="shiftDrawingOffsetForStartOverhang" id="0x010106a2" />
+ <!-- @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") -->
+ <public type="attr" name="windowIsFrameRatePowerSavingsBalanced" id="0x010106a3" />
+
+ <staging-public-group-final type="string" first-id="0x01ba0000">
+ <!-- @hide @SystemApi @FlaggedApi("android.permission.flags.retail_demo_role_enabled") -->
+ <public name="config_defaultRetailDemo" />
+ <!-- @hide @SystemApi @FlaggedApi("android.permission.flags.wallet_role_enabled") -->
+ <public name="config_defaultWallet" />
+ </staging-public-group-final>
+
+ <!-- @hide @SystemApi @FlaggedApi("android.permission.flags.retail_demo_role_enabled") -->
+ <public type="string" name="config_defaultRetailDemo" id="0x01040048" />
+ <!-- @hide @SystemApi @FlaggedApi("android.permission.flags.wallet_role_enabled") -->
+ <public type="string" name="config_defaultWallet" id="0x01040049" />
+
+ <staging-public-group-final type="dimen" first-id="0x01b90000">
+ <!-- System corner radius baseline sizes. Used by Material styling of rounded corner shapes-->
+ <public name="removed_system_corner_radius_xsmall" />
+ <public name="removed_system_corner_radius_small" />
+ <public name="removed_system_corner_radius_medium" />
+ <public name="removed_system_corner_radius_large" />
+ <public name="removed_system_corner_radius_xlarge" />
+ </staging-public-group-final>
+
+ <!-- System corner radius baseline sizes. Used by Material styling of rounded corner shapes-->
+
+ <staging-public-group-final type="color" first-id="0x01b80000">
+ <public name="system_surface_disabled"/>
+ <public name="system_on_surface_disabled"/>
+ <public name="system_outline_disabled"/>
+ <public name="system_error_0"/>
+ <public name="system_error_10"/>
+ <public name="system_error_50"/>
+ <public name="system_error_100"/>
+ <public name="system_error_200"/>
+ <public name="system_error_300"/>
+ <public name="system_error_400"/>
+ <public name="system_error_500"/>
+ <public name="system_error_600"/>
+ <public name="system_error_700"/>
+ <public name="system_error_800"/>
+ <public name="system_error_900"/>
+ <public name="system_error_1000"/>
+ </staging-public-group-final>
+
+ <public type="color" name="system_surface_disabled" id="0x010600c2" />
+ <public type="color" name="system_on_surface_disabled" id="0x010600c3" />
+ <public type="color" name="system_outline_disabled" id="0x010600c4" />
+ <public type="color" name="system_error_0" id="0x010600c5" />
+ <public type="color" name="system_error_10" id="0x010600c6" />
+ <public type="color" name="system_error_50" id="0x010600c7" />
+ <public type="color" name="system_error_100" id="0x010600c8" />
+ <public type="color" name="system_error_200" id="0x010600c9" />
+ <public type="color" name="system_error_300" id="0x010600ca" />
+ <public type="color" name="system_error_400" id="0x010600cb" />
+ <public type="color" name="system_error_500" id="0x010600cc" />
+ <public type="color" name="system_error_600" id="0x010600cd" />
+ <public type="color" name="system_error_700" id="0x010600ce" />
+ <public type="color" name="system_error_800" id="0x010600cf" />
+ <public type="color" name="system_error_900" id="0x010600d0" />
+ <public type="color" name="system_error_1000" id="0x010600d1" />
+
</resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index c84f781..b64334f 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -109,143 +109,66 @@
=============================================================== -->
<eat-comment/>
- <staging-public-group type="attr" first-id="0x01bd0000">
- <!-- @FlaggedApi("android.content.res.default_locale") -->
- <public name="defaultLocale"/>
- <!-- @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime")
- @hide @SystemApi -->
- <public name="isVirtualDeviceOnly"/>
- <!-- @FlaggedApi("android.content.pm.sdk_lib_independence") -->
+ <staging-public-group type="attr" first-id="0x01b70000">
+ <!-- @FlaggedApi("android.content.pm.sdk_lib_independence") -->
<public name="optional"/>
<!-- @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") -->
<public name="adServiceTypes" />
- <!-- @hide @SystemApi @FlaggedApi("android.content.res.manifest_flagging") -->
- <public name="featureFlag"/>
- <!-- @FlaggedApi("android.multiuser.enable_system_user_only_for_services_and_providers") -->
- <public name="systemUserOnly"/>
- <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
- <public name="allow"/>
- <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
- <public name="query"/>
- <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
- <public name="queryPrefix"/>
- <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
- <public name="queryPattern"/>
- <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
- <public name="queryAdvancedPattern"/>
- <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
- <public name="querySuffix"/>
- <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
- <public name="fragmentPrefix"/>
- <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
- <public name="fragmentPattern"/>
- <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
- <public name="fragmentAdvancedPattern"/>
- <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
- <public name="fragmentSuffix"/>
- <!-- @FlaggedApi("com.android.text.flags.use_bounds_for_width") -->
- <public name="useBoundsForWidth"/>
- <!-- @FlaggedApi("android.nfc.nfc_read_polling_loop") -->
- <public name="autoTransact"/>
- <!-- @FlaggedApi("com.android.window.flags.enforce_edge_to_edge") -->
- <public name="windowOptOutEdgeToEdgeEnforcement"/>
- <!-- @FlaggedApi("android.security.content_uri_permission_apis") -->
- <public name="requireContentUriPermissionFromCaller" />
<!-- @FlaggedApi("android.view.inputmethod.ime_switcher_revamp") -->
<public name="languageSettingsActivity"/>
- <!-- @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") -->
- <public name="useLocalePreferredLineHeightForMinimum"/>
- <!-- @FlaggedApi("android.view.flags.sensitive_content_app_protection_api") -->
- <public name="contentSensitivity" />
- <!-- @FlaggedApi("android.view.inputmethod.connectionless_handwriting") -->
- <public name="supportsConnectionlessStylusHandwriting" />
- <!-- @FlaggedApi("android.nfc.nfc_observe_mode") -->
- <public name="shouldDefaultToObserveMode"/>
- <!-- @FlaggedApi("android.security.asm_restrictions_enabled") -->
- <public name="allowCrossUidActivitySwitchFromBelow"/>
- <!-- @FlaggedApi("com.android.text.flags.use_bounds_for_width") -->
- <public name="shiftDrawingOffsetForStartOverhang" />
- <!-- @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") -->
- <public name="windowIsFrameRatePowerSavingsBalanced"/>
<!-- @FlaggedApi("android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM") -->
<public name="dreamCategory"/>
</staging-public-group>
- <staging-public-group type="id" first-id="0x01bc0000">
+ <staging-public-group type="id" first-id="0x01b60000">
</staging-public-group>
- <staging-public-group type="style" first-id="0x01bb0000">
+ <staging-public-group type="style" first-id="0x01b50000">
</staging-public-group>
- <staging-public-group type="string" first-id="0x01ba0000">
- <!-- @hide @SystemApi @FlaggedApi("android.permission.flags.retail_demo_role_enabled") -->
- <public name="config_defaultRetailDemo" />
- <!-- @hide @SystemApi @FlaggedApi("android.permission.flags.wallet_role_enabled") -->
- <public name="config_defaultWallet" />
+ <staging-public-group type="string" first-id="0x01b40000">
</staging-public-group>
- <staging-public-group type="dimen" first-id="0x01b90000">
- <!-- System corner radius baseline sizes. Used by Material styling of rounded corner shapes-->
- <public name="removed_system_corner_radius_xsmall" />
- <public name="removed_system_corner_radius_small" />
- <public name="removed_system_corner_radius_medium" />
- <public name="removed_system_corner_radius_large" />
- <public name="removed_system_corner_radius_xlarge" />
+ <staging-public-group type="dimen" first-id="0x01b30000">
</staging-public-group>
- <staging-public-group type="color" first-id="0x01b80000">
- <public name="system_surface_disabled"/>
- <public name="system_on_surface_disabled"/>
- <public name="system_outline_disabled"/>
- <public name="system_error_0"/>
- <public name="system_error_10"/>
- <public name="system_error_50"/>
- <public name="system_error_100"/>
- <public name="system_error_200"/>
- <public name="system_error_300"/>
- <public name="system_error_400"/>
- <public name="system_error_500"/>
- <public name="system_error_600"/>
- <public name="system_error_700"/>
- <public name="system_error_800"/>
- <public name="system_error_900"/>
- <public name="system_error_1000"/>
+ <staging-public-group type="color" first-id="0x01b20000">
</staging-public-group>
- <staging-public-group type="array" first-id="0x01b70000">
+ <staging-public-group type="array" first-id="0x01b10000">
</staging-public-group>
- <staging-public-group type="drawable" first-id="0x01b60000">
+ <staging-public-group type="drawable" first-id="0x01b00000">
</staging-public-group>
- <staging-public-group type="layout" first-id="0x01b50000">
+ <staging-public-group type="layout" first-id="0x01af0000">
</staging-public-group>
- <staging-public-group type="anim" first-id="0x01b40000">
+ <staging-public-group type="anim" first-id="0x01ae0000">
</staging-public-group>
- <staging-public-group type="animator" first-id="0x01b30000">
+ <staging-public-group type="animator" first-id="0x01ad0000">
</staging-public-group>
- <staging-public-group type="interpolator" first-id="0x01b20000">
+ <staging-public-group type="interpolator" first-id="0x01ac0000">
</staging-public-group>
- <staging-public-group type="mipmap" first-id="0x01b10000">
+ <staging-public-group type="mipmap" first-id="0x01ab0000">
</staging-public-group>
- <staging-public-group type="integer" first-id="0x01b00000">
+ <staging-public-group type="integer" first-id="0x01aa0000">
</staging-public-group>
- <staging-public-group type="transition" first-id="0x01af0000">
+ <staging-public-group type="transition" first-id="0x01a90000">
</staging-public-group>
- <staging-public-group type="raw" first-id="0x01ae0000">
+ <staging-public-group type="raw" first-id="0x01a80000">
</staging-public-group>
- <staging-public-group type="bool" first-id="0x01ad0000">
+ <staging-public-group type="bool" first-id="0x01a70000">
</staging-public-group>
- <staging-public-group type="fraction" first-id="0x01ac0000">
+ <staging-public-group type="fraction" first-id="0x01a60000">
</staging-public-group>
</resources>
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
index f885e31..32aec1a 100644
--- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -36,6 +36,7 @@
import android.annotation.NonNull;
import android.app.Activity;
+import android.app.Instrumentation;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
@@ -47,6 +48,7 @@
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
@@ -537,6 +539,28 @@
});
waitForAfterDraw();
}
+
+ @Test
+ @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY
+ })
+ public void frameRateReset() throws Throwable {
+ mMovingView.setRequestedFrameRate(120f);
+ waitForFrameRateCategoryToSettle();
+ mActivityRule.runOnUiThread(() -> mMovingView.setVisibility(View.INVISIBLE));
+
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+
+ for (int i = 0; i < 120; i++) {
+ mActivityRule.runOnUiThread(() -> {
+ mMovingView.getParent().onDescendantInvalidated(mMovingView, mMovingView);
+ });
+ instrumentation.waitForIdleSync();
+ }
+
+ assertEquals(0f, mViewRoot.getLastPreferredFrameRate(), 0f);
+ }
+
private void runAfterDraw(@NonNull Runnable runnable) {
Handler handler = new Handler(Looper.getMainLooper());
mAfterDrawLatch = new CountDownLatch(1);
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 9afc4be..5caf77d 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -28,7 +28,6 @@
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
-import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
@@ -1165,34 +1164,15 @@
});
waitForAfterDraw();
- // reset the frame rate category counts
- for (int i = 0; i < 5; i++) {
- sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
- mView.invalidate();
- });
- sInstrumentation.waitForIdleSync();
- }
-
// In transition from frequent update to infrequent update
Thread.sleep(delay);
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
mView.invalidate();
- runAfterDraw(() -> {
- assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE,
- mViewRootImpl.getLastPreferredFrameRateCategory());
- });
- });
- waitForAfterDraw();
- Thread.sleep(delay);
- sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT);
- mView.invalidate();
- runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE,
+ int expected = toolkitFrameRateDefaultNormalReadOnly()
+ ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
+ runAfterDraw(() -> assertEquals(expected,
mViewRootImpl.getLastPreferredFrameRateCategory()));
});
- waitForAfterDraw();
// Infrequent update
Thread.sleep(delay);
diff --git a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
index 6402206..baab3b2 100644
--- a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
@@ -168,6 +168,20 @@
assertThat(end).isEqualTo("END");
}
+ @Test
+ public void parceling_corruptParcel() {
+ PowerStats stats = new PowerStats(mDescriptor);
+ Parcel parcel = Parcel.obtain();
+ stats.writeToParcel(parcel);
+
+ Parcel newParcel = marshallAndUnmarshall(parcel);
+ newParcel.writeInt(-42); // Negative section length
+ newParcel.setDataPosition(0);
+
+ PowerStats newStats = PowerStats.readFromParcel(newParcel, mRegistry);
+ assertThat(newStats).isNull();
+ }
+
private static Parcel marshallAndUnmarshall(Parcel parcel) {
byte[] bytes = parcel.marshall();
parcel.recycle();
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
index e1bf40c..6110133 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
@@ -105,18 +105,21 @@
}
@Test
- fun onDragUpdate_stayOnSameSide() {
+ fun drag_stayOnSameSide() {
runOnMainSync {
controller.onDragStart(initialLocationOnLeft = false)
controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
+ controller.onDragEnd()
}
waitForAnimateIn()
assertThat(dropTargetView).isNull()
assertThat(testListener.locationChanges).isEmpty()
+ assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.RIGHT)
}
@Test
- fun onDragUpdate_toLeft() {
+ fun drag_toLeft() {
+ // Drag to left, but don't finish
runOnMainSync {
controller.onDragStart(initialLocationOnLeft = false)
controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
@@ -132,10 +135,16 @@
.isEqualTo(expectedDropTargetBounds.height())
assertThat(testListener.locationChanges).containsExactly(BubbleBarLocation.LEFT)
+ assertThat(testListener.locationReleases).isEmpty()
+
+ // Finish the drag
+ runOnMainSync { controller.onDragEnd() }
+ assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.LEFT)
}
@Test
- fun onDragUpdate_toLeftAndBackToRight() {
+ fun drag_toLeftAndBackToRight() {
+ // Drag to left
runOnMainSync {
controller.onDragStart(initialLocationOnLeft = false)
controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
@@ -143,6 +152,7 @@
waitForAnimateIn()
assertThat(dropTargetView).isNotNull()
+ // Drag to right
runOnMainSync { controller.onDragUpdate(pointOnRight.x, pointOnRight.y) }
// We have to wait for existing drop target to animate out and new to animate in
waitForAnimateOut()
@@ -158,10 +168,15 @@
assertThat(testListener.locationChanges)
.containsExactly(BubbleBarLocation.LEFT, BubbleBarLocation.RIGHT)
+ assertThat(testListener.locationReleases).isEmpty()
+
+ // Release the view
+ runOnMainSync { controller.onDragEnd() }
+ assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.RIGHT)
}
@Test
- fun onDragUpdate_toLeftInExclusionRect() {
+ fun drag_toLeftInExclusionRect() {
runOnMainSync {
controller.onDragStart(initialLocationOnLeft = false)
// Exclusion rect is around the bottom center area of the screen
@@ -170,6 +185,10 @@
waitForAnimateIn()
assertThat(dropTargetView).isNull()
assertThat(testListener.locationChanges).isEmpty()
+ assertThat(testListener.locationReleases).isEmpty()
+
+ runOnMainSync { controller.onDragEnd() }
+ assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.RIGHT)
}
@Test
@@ -256,8 +275,13 @@
internal class TestLocationChangeListener : BaseBubblePinController.LocationChangeListener {
val locationChanges = mutableListOf<BubbleBarLocation>()
+ val locationReleases = mutableListOf<BubbleBarLocation>()
override fun onChange(location: BubbleBarLocation) {
locationChanges.add(location)
}
+
+ override fun onRelease(location: BubbleBarLocation) {
+ locationReleases.add(location)
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
index 0297901..f9a1d94 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
@@ -82,8 +82,8 @@
private BubbleViewProvider mBubble;
private BubblePositioner mPositioner;
- private boolean mOnLeft;
-
+ private boolean mBadgeOnLeft;
+ private boolean mDotOnLeft;
private DotRenderer mDotRenderer;
private DotRenderer.DrawParams mDrawParams;
private int mDotColor;
@@ -153,7 +153,8 @@
public void hideDotAndBadge(boolean onLeft) {
addDotSuppressionFlag(BadgedImageView.SuppressionFlag.BEHIND_STACK);
- mOnLeft = onLeft;
+ mBadgeOnLeft = onLeft;
+ mDotOnLeft = onLeft;
hideBadge();
}
@@ -185,7 +186,7 @@
mDrawParams.dotColor = mDotColor;
mDrawParams.iconBounds = mTempBounds;
- mDrawParams.leftAlign = mOnLeft;
+ mDrawParams.leftAlign = mDotOnLeft;
mDrawParams.scale = mDotScale;
mDotRenderer.draw(canvas, mDrawParams);
@@ -255,7 +256,7 @@
* Whether decorations (badges or dots) are on the left.
*/
boolean getDotOnLeft() {
- return mOnLeft;
+ return mDotOnLeft;
}
/**
@@ -263,7 +264,7 @@
*/
float[] getDotCenter() {
float[] dotPosition;
- if (mOnLeft) {
+ if (mDotOnLeft) {
dotPosition = mDotRenderer.getLeftDotPosition();
} else {
dotPosition = mDotRenderer.getRightDotPosition();
@@ -291,22 +292,23 @@
if (onLeft != getDotOnLeft()) {
if (shouldDrawDot()) {
animateDotScale(0f /* showDot */, () -> {
- mOnLeft = onLeft;
+ mDotOnLeft = onLeft;
invalidate();
animateDotScale(1.0f, null /* after */);
});
} else {
- mOnLeft = onLeft;
+ mDotOnLeft = onLeft;
}
}
+ mBadgeOnLeft = onLeft;
// TODO animate badge
showBadge();
-
}
/** Sets the position of the dot and badge. */
void setDotBadgeOnLeft(boolean onLeft) {
- mOnLeft = onLeft;
+ mBadgeOnLeft = onLeft;
+ mDotOnLeft = onLeft;
invalidate();
showBadge();
}
@@ -361,7 +363,7 @@
}
int translationX;
- if (mOnLeft) {
+ if (mBadgeOnLeft) {
translationX = -(mBubble.getBubbleIcon().getWidth() - appBadgeBitmap.getWidth());
} else {
translationX = 0;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index d295877..edd5935 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -474,11 +474,16 @@
mDisplayController.addDisplayChangingController(
(displayId, fromRotation, toRotation, newDisplayAreaInfo, t) -> {
- // This is triggered right before the rotation is applied
- if (fromRotation != toRotation) {
+ Rect newScreenBounds = new Rect();
+ if (newDisplayAreaInfo != null) {
+ newScreenBounds =
+ newDisplayAreaInfo.configuration.windowConfiguration.getBounds();
+ }
+ // This is triggered right before the rotation or new screen size is applied
+ if (fromRotation != toRotation || !newScreenBounds.equals(mScreenBounds)) {
if (mStackView != null) {
// Layout listener set on stackView will update the positioner
- // once the rotation is applied
+ // once the rotation or screen change is applied
mStackView.onOrientationChanged();
}
}
@@ -725,6 +730,17 @@
}
}
+ /**
+ * Animate bubble bar to the given location. The location change is transient. It does not
+ * update the state of the bubble bar.
+ * To update bubble bar pinned location, use {@link #setBubbleBarLocation(BubbleBarLocation)}.
+ */
+ public void animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
+ if (canShowAsBubbleBar()) {
+ mBubbleStateListener.animateBubbleBarLocation(bubbleBarLocation);
+ }
+ }
+
/** Whether this userId belongs to the current user. */
private boolean isCurrentProfile(int userId) {
return userId == UserHandle.USER_ALL
@@ -2250,15 +2266,19 @@
private final SingleInstanceRemoteListener<BubbleController, IBubblesListener> mListener;
private final Bubbles.BubbleStateListener mBubbleListener =
new Bubbles.BubbleStateListener() {
+ @Override
+ public void onBubbleStateChange(BubbleBarUpdate update) {
+ Bundle b = new Bundle();
+ b.setClassLoader(BubbleBarUpdate.class.getClassLoader());
+ b.putParcelable(BubbleBarUpdate.BUNDLE_KEY, update);
+ mListener.call(l -> l.onBubbleStateChange(b));
+ }
- @Override
- public void onBubbleStateChange(BubbleBarUpdate update) {
- Bundle b = new Bundle();
- b.setClassLoader(BubbleBarUpdate.class.getClassLoader());
- b.putParcelable(BubbleBarUpdate.BUNDLE_KEY, update);
- mListener.call(l -> l.onBubbleStateChange(b));
- }
- };
+ @Override
+ public void animateBubbleBarLocation(BubbleBarLocation location) {
+ mListener.call(l -> l.animateBubbleBarLocation(location));
+ }
+ };
IBubblesImpl(BubbleController controller) {
mController = controller;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 127a49f..322088b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -37,6 +37,7 @@
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
import com.android.wm.shell.shared.annotations.ExternalThread;
@@ -304,6 +305,12 @@
* Called when the bubbles state changes.
*/
void onBubbleStateChange(BubbleBarUpdate update);
+
+ /**
+ * Called when bubble bar should temporarily be animated to a new location.
+ * Does not result in a state change.
+ */
+ void animateBubbleBarLocation(BubbleBarLocation location);
}
/** Listener to find out about stack expansion / collapse events. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl
index e48f8d5..14d29cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl
@@ -15,8 +15,9 @@
*/
package com.android.wm.shell.bubbles;
-import android.os.Bundle;
+import android.os.Bundle;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
/**
* Listener interface that Launcher attaches to SystemUI to get bubbles callbacks.
*/
@@ -26,4 +27,10 @@
* Called when the bubbles state changes.
*/
void onBubbleStateChange(in Bundle update);
+
+ /**
+ * Called when bubble bar should temporarily be animated to a new location.
+ * Does not result in a state change.
+ */
+ void animateBubbleBarLocation(in BubbleBarLocation location);
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
index fe9c4d4..a51ac63 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -135,9 +135,9 @@
private fun finishDrag() {
if (!isStuckToDismiss) {
- animationHelper.animateToRestPosition()
pinController.onDragEnd()
dragListener.onReleased(inDismiss = false)
+ animationHelper.animateToRestPosition()
dismissView.hide()
}
isMoving = false
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 62cc4da..a351cef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -33,6 +33,8 @@
import android.view.WindowManager;
import android.widget.FrameLayout;
+import androidx.annotation.NonNull;
+
import com.android.wm.shell.bubbles.Bubble;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.BubbleData;
@@ -42,6 +44,8 @@
import com.android.wm.shell.bubbles.DeviceConfig;
import com.android.wm.shell.bubbles.DismissViewUtils;
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedViewDragController.DragListener;
+import com.android.wm.shell.common.bubbles.BaseBubblePinController;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
import com.android.wm.shell.common.bubbles.DismissView;
import kotlin.Unit;
@@ -115,7 +119,18 @@
mBubbleExpandedViewPinController = new BubbleExpandedViewPinController(
context, this, mPositioner);
- mBubbleExpandedViewPinController.setListener(mBubbleController::setBubbleBarLocation);
+ mBubbleExpandedViewPinController.setListener(
+ new BaseBubblePinController.LocationChangeListener() {
+ @Override
+ public void onChange(@NonNull BubbleBarLocation bubbleBarLocation) {
+ mBubbleController.animateBubbleBarLocation(bubbleBarLocation);
+ }
+
+ @Override
+ public void onRelease(@NonNull BubbleBarLocation location) {
+ mBubbleController.setBubbleBarLocation(location);
+ }
+ });
setOnClickListener(view -> hideMenuOrCollapse());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt
index a008045..e514f9d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt
@@ -82,6 +82,7 @@
fun onDragEnd() {
getDropTargetView()?.let { view -> view.animateOut { removeDropTargetView(view) } }
dismissZone = null
+ listener?.onRelease(if (onLeft) LEFT else RIGHT)
}
/**
@@ -170,14 +171,22 @@
/** Receive updates on location changes */
interface LocationChangeListener {
/**
- * Bubble bar [BubbleBarLocation] has changed as a result of dragging
+ * Bubble bar has been dragged to a new [BubbleBarLocation]. And the drag is still in
+ * progress.
*
* Triggered when drag gesture passes the middle of the screen and before touch up. Can be
* triggered multiple times per gesture.
*
* @param location new location as a result of the ongoing drag operation
*/
- fun onChange(location: BubbleBarLocation)
+ fun onChange(location: BubbleBarLocation) {}
+
+ /**
+ * Bubble bar has been released in the [BubbleBarLocation].
+ *
+ * @param location final location of the bubble bar once drag is released
+ */
+ fun onRelease(location: BubbleBarLocation)
}
companion object {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 0a9e5d0..08b7c01 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -158,6 +158,10 @@
com.android.wm.shell.R.dimen.desktop_mode_transition_area_width
)
+ /** Task id of the task currently being dragged from fullscreen/split. */
+ val draggingTaskId
+ get() = dragToDesktopTransitionHandler.draggingTaskId
+
private var recentsAnimationRunning = false
private lateinit var splitScreenController: SplitScreenController
@@ -406,6 +410,7 @@
fun onDesktopSplitSelectAnimComplete(taskInfo: RunningTaskInfo) {
val wct = WindowContainerTransaction()
wct.setBounds(taskInfo.token, Rect())
+ wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED)
shellTaskOrganizer.applyTransaction(wct)
}
@@ -447,7 +452,9 @@
)
val wct = WindowContainerTransaction()
wct.setBounds(task.token, Rect())
- addMoveToSplitChanges(wct, task)
+ // Rather than set windowing mode to multi-window at task level, set it to
+ // undefined and inherit from split stage.
+ wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
} else {
@@ -458,10 +465,12 @@
private fun exitSplitIfApplicable(wct: WindowContainerTransaction, taskInfo: RunningTaskInfo) {
if (splitScreenController.isTaskInSplitScreen(taskInfo.taskId)) {
splitScreenController.prepareExitSplitScreen(
- wct,
- splitScreenController.getStageOfTask(taskInfo.taskId),
- EXIT_REASON_DESKTOP_MODE
+ wct,
+ splitScreenController.getStageOfTask(taskInfo.taskId),
+ EXIT_REASON_DESKTOP_MODE
)
+ splitScreenController.transitionHandler
+ ?.onSplitToDesktop()
}
}
@@ -1044,9 +1053,11 @@
wct: WindowContainerTransaction,
taskInfo: RunningTaskInfo
) {
- // Explicitly setting multi-window at task level interferes with animations.
- // Let task inherit windowing mode once transition is complete instead.
- wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED)
+ // This windowing mode is to get the transition animation started; once we complete
+ // split select, we will change windowing mode to undefined and inherit from split stage.
+ // Going to undefined here causes task to flicker to the top left.
+ // Cancelling the split select flow will revert it to fullscreen.
+ wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_MULTI_WINDOW)
// The task's density may have been overridden in freeform; revert it here as we don't
// want it overridden in multi-window.
wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
@@ -1237,7 +1248,7 @@
finalizeDragToDesktop(taskInfo, getDefaultDesktopTaskBounds(displayLayout))
}
DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR,
- DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> {
+ DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> {
cancelDragToDesktop(taskInfo)
}
DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index e341f2d..e5e435d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -6,6 +6,7 @@
import android.animation.ValueAnimator
import android.app.ActivityOptions
import android.app.ActivityOptions.SourceInfo
+import android.app.ActivityTaskManager.INVALID_TASK_ID
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT
import android.app.PendingIntent.FLAG_MUTABLE
@@ -26,6 +27,9 @@
import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
+import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
+import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED
import com.android.wm.shell.protolog.ShellProtoLogGroup
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.splitscreen.SplitScreenController
@@ -68,7 +72,7 @@
.addCategory(Intent.CATEGORY_HOME)
private var dragToDesktopStateListener: DragToDesktopStateListener? = null
- private var splitScreenController: SplitScreenController? = null
+ private lateinit var splitScreenController: SplitScreenController
private var transitionState: TransitionState? = null
private lateinit var onTaskResizeAnimationListener: OnTaskResizeAnimationListener
@@ -76,6 +80,9 @@
val inProgress: Boolean
get() = transitionState != null
+ /** The task id of the task currently being dragged from fullscreen/split. */
+ val draggingTaskId: Int
+ get() = transitionState?.draggedTaskId ?: INVALID_TASK_ID
/** Sets a listener to receive callback about events during the transition animation. */
fun setDragToDesktopStateListener(listener: DragToDesktopStateListener) {
dragToDesktopStateListener = listener
@@ -130,10 +137,14 @@
.startTransition(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, wct, this)
transitionState = if (isSplitTask(taskId)) {
+ val otherTask = getOtherSplitTask(taskId) ?: throw IllegalStateException(
+ "Expected split task to have a counterpart."
+ )
TransitionState.FromSplit(
draggedTaskId = taskId,
dragAnimator = dragToDesktopAnimator,
- startTransitionToken = startTransitionToken
+ startTransitionToken = startTransitionToken,
+ otherSplitTask = otherTask
)
} else {
TransitionState.FromFullscreen(
@@ -347,6 +358,12 @@
?: error("Start transition expected to be waiting for merge but wasn't")
if (isEndTransition) {
info.changes.withIndex().forEach { (i, change) ->
+ // If we're exiting split, hide the remaining split task.
+ if (state is TransitionState.FromSplit &&
+ change.taskInfo?.taskId == state.otherSplitTask) {
+ t.hide(change.leash)
+ startTransactionFinishT.hide(change.leash)
+ }
if (change.mode == TRANSIT_CLOSE) {
t.hide(change.leash)
startTransactionFinishT.hide(change.leash)
@@ -392,7 +409,6 @@
onTaskResizeAnimationListener.onAnimationStart(state.draggedTaskId, t,
unscaledStartBounds)
finishCallback.onTransitionFinished(null /* wct */)
-
val tx: SurfaceControl.Transaction = transactionSupplier.get()
ValueAnimator.ofObject(rectEvaluator, unscaledStartBounds, endBounds)
.setDuration(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS)
@@ -549,7 +565,18 @@
}
private fun isSplitTask(taskId: Int): Boolean {
- return splitScreenController?.isTaskInSplitScreen(taskId) ?: false
+ return splitScreenController.isTaskInSplitScreen(taskId)
+ }
+
+ private fun getOtherSplitTask(taskId: Int): Int? {
+ val splitPos = splitScreenController.getSplitPosition(taskId)
+ if (splitPos == SPLIT_POSITION_UNDEFINED) return null
+ val otherTaskPos = if (splitPos == SPLIT_POSITION_BOTTOM_OR_RIGHT) {
+ SPLIT_POSITION_TOP_OR_LEFT
+ } else {
+ SPLIT_POSITION_BOTTOM_OR_RIGHT
+ }
+ return splitScreenController.getTaskInfo(otherTaskPos)?.taskId
}
private fun requireTransitionState(): TransitionState {
@@ -598,6 +625,7 @@
override var cancelled: Boolean = false,
override var startAborted: Boolean = false,
var splitRootChange: Change? = null,
+ var otherSplitTask: Int
) : TransitionState()
}
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 4c68106..2a50b19 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
@@ -3351,6 +3351,11 @@
true /* reparentLeafTaskIfRelaunch */);
}
+ /** Call this when the animation from split screen to desktop is started. */
+ public void onSplitToDesktop() {
+ setSplitsVisible(false);
+ }
+
/** Call this when the recents animation finishes by doing pair-to-pair switch. */
public void onRecentsPairToPairAnimationFinish(WindowContainerTransaction finishWct) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRecentsPairToPairAnimationFinish");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 922c55f..01175f5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.windowdecor;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
@@ -34,6 +35,7 @@
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.compatui.AppCompatUtils.isSingleTopActivityTranslucent;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import android.annotation.NonNull;
import android.app.ActivityManager;
@@ -272,10 +274,9 @@
mSplitScreenController.registerSplitScreenListener(new SplitScreen.SplitScreenListener() {
@Override
public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
- if (visible) {
+ if (visible && stage != STAGE_TYPE_UNDEFINED) {
DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId);
- if (decor != null && DesktopModeStatus.isEnabled()
- && decor.mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ if (decor != null && DesktopModeStatus.isEnabled()) {
mDesktopTasksController.moveToSplit(decor.mTaskInfo);
}
}
@@ -915,6 +916,11 @@
@Nullable
private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) {
+ // If we are mid-transition, dragged task's decor is always relevant.
+ final int draggedTaskId = mDesktopTasksController.getDraggingTaskId();
+ if (draggedTaskId != INVALID_TASK_ID) {
+ return mWindowDecorByTaskId.get(draggedTaskId);
+ }
final DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
if (focusedDecor == null) {
return null;
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index 4981029..5f84862 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -80,10 +80,13 @@
}
flag {
- name: "subscriptions_listener_thread"
+ name: "subscriptions_changed_listener_thread"
namespace: "location"
- description: "Flag for running onSubscriptionsChangeListener on FgThread"
+ description: "Flag for running onSubscriptionsChangedListener on FgThread"
bug: "332451908"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 1515811..adbfc72 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1745,4 +1745,7 @@
<string name="feminine">Feminine</string>
<!-- List entry in developer settings to set the grammatical gender to Masculine [CHAR LIMIT=30]-->
<string name="masculine">Masculine</string>
+
+ <!-- The name of the screen for seeing and installing system updates. [CHAR LIMIT=40]-->
+ <string name="system_update_settings_list_item_title">System Updates</string>
</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp
index 64dcf6e..395354e 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp
@@ -34,6 +34,7 @@
"compatibility-device-util-axt",
"platform-test-annotations",
"truth",
+ "uiautomator-helpers",
],
srcs: [
"src/**/*.java",
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
index 0ab99fa..66943d4 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
@@ -30,6 +30,8 @@
import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_TOGGLE_MENU;
import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.PACKAGE_NAME;
+import static com.google.common.truth.Truth.assertWithMessage;
+
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Instrumentation;
import android.app.KeyguardManager;
@@ -43,6 +45,8 @@
import android.hardware.display.DisplayManager;
import android.media.AudioManager;
import android.os.PowerManager;
+import android.os.RemoteException;
+import android.platform.uiautomator_helpers.WaitUtils;
import android.provider.Settings;
import android.util.Log;
import android.view.Display;
@@ -51,6 +55,8 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.Configurator;
+import androidx.test.uiautomator.UiDevice;
import com.android.compatibility.common.util.TestUtils;
import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut.ShortcutId;
@@ -60,7 +66,6 @@
import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -76,11 +81,13 @@
private static final int TIMEOUT_SERVICE_STATUS_CHANGE_S = 5;
private static final int TIMEOUT_UI_CHANGE_S = 5;
private static final int NO_GLOBAL_ACTION = -1;
- private static final Intent INTENT_OPEN_MENU = new Intent(INTENT_TOGGLE_MENU)
- .setPackage(PACKAGE_NAME);
+ private static final Intent INTENT_OPEN_MENU =
+ new Intent(INTENT_TOGGLE_MENU).setPackage(PACKAGE_NAME);
+ private static final String SERVICE_NAME = PACKAGE_NAME + "/.AccessibilityMenuService";
private static Instrumentation sInstrumentation;
private static UiAutomation sUiAutomation;
+ private static UiDevice sUiDevice;
private static final AtomicInteger sLastGlobalAction = new AtomicInteger(NO_GLOBAL_ACTION);
private static final AtomicBoolean sOpenBlocked = new AtomicBoolean(false);
@@ -91,12 +98,14 @@
@BeforeClass
public static void classSetup() throws Throwable {
- final String serviceName = PACKAGE_NAME + "/.AccessibilityMenuService";
+ Configurator.getInstance()
+ .setUiAutomationFlags(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
sInstrumentation = InstrumentationRegistry.getInstrumentation();
sUiAutomation = sInstrumentation.getUiAutomation(
UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
sUiAutomation.adoptShellPermissionIdentity(
UiAutomation.ALL_PERMISSIONS.toArray(new String[0]));
+ sUiDevice = UiDevice.getInstance(sInstrumentation);
final Context context = sInstrumentation.getTargetContext();
sAccessibilityManager = context.getSystemService(AccessibilityManager.class);
@@ -117,13 +126,13 @@
// Enable a11yMenu service.
Settings.Secure.putString(context.getContentResolver(),
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, serviceName);
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, SERVICE_NAME);
TestUtils.waitUntil("Failed to enable service",
TIMEOUT_SERVICE_STATUS_CHANGE_S,
() -> sAccessibilityManager.getEnabledAccessibilityServiceList(
AccessibilityServiceInfo.FEEDBACK_ALL_MASK).stream().filter(
- info -> info.getId().contains(serviceName)).count() == 1);
+ info -> info.getId().contains(SERVICE_NAME)).count() == 1);
context.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -159,8 +168,11 @@
public void tearDown() throws Throwable {
closeMenu();
sLastGlobalAction.set(NO_GLOBAL_ACTION);
+ // Leave the device in clean state when the test finished
+ unlockSignal();
// dismisses screenshot popup if present.
- sUiAutomation.executeShellCommand("input keyevent KEYCODE_BACK");
+ sUiDevice.pressBack();
+ sUiDevice.pressHome();
}
private static boolean isMenuVisible() {
@@ -168,38 +180,25 @@
return root != null && root.getPackageName().toString().equals(PACKAGE_NAME);
}
- private static void wakeUpScreen() throws Throwable {
- sUiAutomation.executeShellCommand("input keyevent KEYCODE_WAKEUP");
- TestUtils.waitUntil("Screen did not wake up.",
- TIMEOUT_UI_CHANGE_S,
- () -> sPowerManager.isInteractive());
+ private static void wakeUpScreen() throws RemoteException {
+ sUiDevice.wakeUp();
+ WaitUtils.waitForValueToSettle("Screen On", AccessibilityMenuServiceTest::isScreenOn);
+ assertWithMessage("Screen is on").that(isScreenOn()).isTrue();
}
private static void closeScreen() throws Throwable {
- Display display = sDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
sUiAutomation.performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN);
- TestUtils.waitUntil("Screen did not close.",
- TIMEOUT_UI_CHANGE_S,
- () -> !sPowerManager.isInteractive()
- && display.getState() == Display.STATE_OFF
- );
+ WaitUtils.waitForValueToSettle("Screen Off", AccessibilityMenuServiceTest::isScreenOff);
+ assertWithMessage("Screen is off").that(isScreenOff()).isTrue();
}
private static void openMenu() throws Throwable {
unlockSignal();
- sInstrumentation.getContext().sendBroadcast(INTENT_OPEN_MENU);
-
- TestUtils.waitUntil("Timed out before menu could appear.",
- TIMEOUT_UI_CHANGE_S,
- () -> {
- if (isMenuVisible()) {
- return true;
- } else {
- unlockSignal();
- sInstrumentation.getContext().sendBroadcast(INTENT_OPEN_MENU);
- return false;
- }
- });
+ if (!isMenuVisible()) {
+ sInstrumentation.getTargetContext().sendBroadcast(INTENT_OPEN_MENU);
+ sUiDevice.waitForIdle();
+ WaitUtils.ensureThat("Accessibility Menu is visible", () -> isMenuVisible());
+ }
}
private static void closeMenu() throws Throwable {
@@ -342,7 +341,9 @@
sUiAutomation.executeAndWaitForEvent(
() -> assistantButton.performAction(CLICK_ID),
- (event) -> expectedPackage.contains(event.getPackageName()),
+ (event) ->
+ event.getPackageName() != null
+ && expectedPackage.contains(event.getPackageName()),
TIMEOUT_UI_CHANGE_S * 1000
);
}
@@ -358,7 +359,9 @@
sUiAutomation.executeAndWaitForEvent(
() -> settingsButton.performAction(CLICK_ID),
- (event) -> expectedPackage.contains(event.getPackageName()),
+ (event) ->
+ event.getPackageName() != null
+ && expectedPackage.contains(event.getPackageName()),
TIMEOUT_UI_CHANGE_S * 1000
);
}
@@ -454,24 +457,40 @@
}
@Test
- @Ignore("Test failure in pre/postsubmit cannot be replicated on local devices. "
- + "Coverage is low-impact.")
public void testOnScreenLock_cannotOpenMenu() throws Throwable {
closeScreen();
wakeUpScreen();
+ sInstrumentation.getContext().sendBroadcast(INTENT_OPEN_MENU);
+ sUiDevice.waitForIdle();
TestUtils.waitUntil("Did not receive signal that menu cannot open",
TIMEOUT_UI_CHANGE_S,
- () -> {
- sInstrumentation.getContext().sendBroadcast(INTENT_OPEN_MENU);
- return sOpenBlocked.get();
- });
+ sOpenBlocked::get);
}
- private static void unlockSignal() {
- // MENU unlocks screen,
- // BACK closes any menu that may appear if the screen wasn't locked.
- sUiAutomation.executeShellCommand("input keyevent KEYCODE_MENU");
- sUiAutomation.executeShellCommand("input keyevent KEYCODE_BACK");
+ private static void unlockSignal() throws RemoteException {
+ if (!sKeyguardManager.isKeyguardLocked()) {
+ return;
+ }
+ // go/adb-cheats#unlock-screen
+ wakeUpScreen();
+ if (sKeyguardManager.isKeyguardLocked()) {
+ sUiDevice.pressMenu();
+ }
+ WaitUtils.ensureThat(
+ "Device unlocked & isInteractive",
+ () -> isScreenOn() && !sKeyguardManager.isKeyguardLocked());
+ }
+
+ private static boolean isScreenOn() {
+ int display = Display.DEFAULT_DISPLAY;
+ return sPowerManager.isInteractive(display)
+ && sDisplayManager.getDisplay(display).getState() == Display.STATE_ON;
+ }
+
+ private static boolean isScreenOff() {
+ int display = Display.DEFAULT_DISPLAY;
+ return !sPowerManager.isInteractive(display)
+ && sDisplayManager.getDisplay(display).getState() == Display.STATE_OFF;
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 338987a..7d56a67 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -20,6 +20,8 @@
import android.graphics.drawable.Icon
import android.os.Bundle
import android.util.SizeF
+import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
import android.widget.FrameLayout
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
@@ -115,8 +117,6 @@
import androidx.compose.ui.window.Popup
import androidx.core.view.setPadding
import androidx.window.layout.WindowMetricsCalculator
-import com.android.compose.modifiers.height
-import com.android.compose.modifiers.padding
import com.android.compose.modifiers.thenIf
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.compose.ui.graphics.painter.rememberDrawablePainter
@@ -300,7 +300,7 @@
viewModel.onHidePopup()
viewModel.onOpenWidgetEditor(selectedKey.value)
},
- onHide = { viewModel.onHidePopup()}
+ onHide = { viewModel.onHidePopup() }
)
}
null -> {}
@@ -374,7 +374,7 @@
liveContentKeys.indexOfFirst { !prevLiveContentKeys.contains(it) }
// Scroll if current position is behind the first updated content
- if (indexOfFirstUpdatedContent in 0..<gridState.firstVisibleItemIndex) {
+ if (indexOfFirstUpdatedContent in 0 until gridState.firstVisibleItemIndex) {
// Launching with a scope to prevent the job from being canceled in the case of a
// recomposition during scrolling
coroutineScope.launch { gridState.animateScrollToItem(indexOfFirstUpdatedContent) }
@@ -841,6 +841,8 @@
widgetConfigurator: WidgetConfigurator?,
modifier: Modifier = Modifier,
) {
+ val isFocusable by viewModel.isFocusable.collectAsState(initial = false)
+
Box(
modifier =
modifier.thenIf(!viewModel.isEditMode && model.inQuietMode) {
@@ -865,6 +867,16 @@
setPadding(0)
}
},
+ update = {
+ it.apply {
+ importantForAccessibility =
+ if (isFocusable) {
+ IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ } else {
+ IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ }
+ }
+ },
// For reusing composition in lazy lists.
onReset = {},
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt
index f354b80..74af3ca 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt
@@ -98,10 +98,10 @@
}
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
- return onClick != null || super.onInterceptTouchEvent(ev)
+ return (isSliceViewClickable && onClick != null) || super.onInterceptTouchEvent(ev)
}
override fun onClick(v: View?) {
- onClick?.let { it() } ?: super.onClick(v)
+ onClick?.takeIf { isSliceViewClickable }?.let { it() } ?: super.onClick(v)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
index 4f3a6c8..874c0a2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -38,6 +38,8 @@
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.toggleableState
+import androidx.compose.ui.state.ToggleableState
import androidx.compose.ui.unit.dp
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
@@ -79,6 +81,12 @@
modifier =
Modifier.fillMaxSize().padding(8.dp).semantics {
role = Role.Switch
+ toggleableState =
+ if (viewModel.isActive) {
+ ToggleableState.On
+ } else {
+ ToggleableState.Off
+ }
contentDescription = label
},
onClick = { onCheckedChange(!viewModel.isActive) },
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 4273b4f..ca64323 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -217,18 +217,18 @@
maybePruneMaps(layoutImpl, prevElement, prevSceneState)
}
- override fun isMeasurementApproachComplete(lookaheadSize: IntSize): Boolean {
+ override fun isMeasurementApproachInProgress(lookaheadSize: IntSize): Boolean {
// TODO(b/324191441): Investigate whether making this check more complex (checking if this
// element is shared or transformed) would lead to better performance.
- return layoutImpl.state.currentTransitions.isEmpty()
+ return layoutImpl.state.isTransitioning()
}
- override fun Placeable.PlacementScope.isPlacementApproachComplete(
+ override fun Placeable.PlacementScope.isPlacementApproachInProgress(
lookaheadCoordinates: LayoutCoordinates
): Boolean {
// TODO(b/324191441): Investigate whether making this check more complex (checking if this
// element is shared or transformed) would lead to better performance.
- return layoutImpl.state.currentTransitions.isEmpty()
+ return layoutImpl.state.isTransitioning()
}
@ExperimentalComposeUiApi
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index 7fb5a4d..339868c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -26,7 +26,7 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.layout.approachLayout
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.zIndex
@@ -74,7 +74,9 @@
Box(
modifier
.zIndex(zIndex)
- .intermediateLayout { measurable, constraints ->
+ .approachLayout(
+ isMeasurementApproachInProgress = { scope.layoutState.isTransitioning() }
+ ) { measurable, constraints ->
targetSize = lookaheadSize
val placeable = measurable.measure(constraints)
layout(placeable.width, placeable.height) { placeable.place(0, 0) }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index ad691ba..d383cec 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -251,8 +251,8 @@
private class LayoutNode(var layoutImpl: SceneTransitionLayoutImpl) :
Modifier.Node(), ApproachLayoutModifierNode {
- override fun isMeasurementApproachComplete(lookaheadSize: IntSize): Boolean {
- return layoutImpl.state.currentTransition == null
+ override fun isMeasurementApproachInProgress(lookaheadSize: IntSize): Boolean {
+ return layoutImpl.state.isTransitioning()
}
@ExperimentalComposeUiApi
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/Size.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/Size.kt
index bd36cb8..b392c67 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/Size.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/Size.kt
@@ -18,15 +18,17 @@
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.layout.approachLayout
import androidx.compose.ui.unit.Constraints
import com.android.compose.animation.scene.SceneTransitionLayoutState
@OptIn(ExperimentalComposeUiApi::class)
internal fun Modifier.noResizeDuringTransitions(layoutState: SceneTransitionLayoutState): Modifier {
- return intermediateLayout { measurable, constraints ->
+ return approachLayout(isMeasurementApproachInProgress = { layoutState.isTransitioning() }) {
+ measurable,
+ constraints ->
if (layoutState.currentTransition == null) {
- return@intermediateLayout measurable.measure(constraints).run {
+ return@approachLayout measurable.measure(constraints).run {
layout(width, height) { place(0, 0) }
}
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index b1d7055..92e1b2c 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -46,7 +46,7 @@
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.layout.approachLayout
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertPositionInRootIsEqualTo
@@ -91,7 +91,9 @@
modifier
.offset(offset)
.element(key)
- .intermediateLayout { measurable, constraints ->
+ .approachLayout(
+ isMeasurementApproachInProgress = { layoutState.isTransitioning() }
+ ) { measurable, constraints ->
onLayout()
val placement = measurable.measure(constraints)
layout(placement.width, placement.height) {
@@ -525,7 +527,7 @@
// page should be composed.
HorizontalPager(
pagerState,
- outOfBoundsPageCount = 0,
+ beyondViewportPageCount = 0,
) { page ->
when (page) {
0 -> Box(Modifier.element(TestElements.Foo).fillMaxSize())
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
index ac7717b..ce4c5275 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
@@ -202,11 +202,11 @@
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun data(): List<TestCase> =
- listOf(
- TestCase(NestedScrollSource.Drag),
- TestCase(NestedScrollSource.Fling),
- TestCase(NestedScrollSource.Wheel),
+ fun data(): List<TestCase> {
+ return listOf(
+ TestCase(NestedScrollSource.UserInput),
+ TestCase(NestedScrollSource.SideEffect),
)
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index c96a8ce..9e9a002 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -24,17 +24,21 @@
import android.provider.Settings
import android.widget.RemoteViews
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalTutorialInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS
@@ -45,8 +49,14 @@
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
@@ -63,6 +73,7 @@
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -94,6 +105,8 @@
private lateinit var mediaRepository: FakeCommunalMediaRepository
private lateinit var userRepository: FakeUserRepository
private lateinit var shadeTestUtil: ShadeTestUtil
+ private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
+ private lateinit var communalRepository: FakeCommunalRepository
private lateinit var underTest: CommunalViewModel
@@ -106,12 +119,14 @@
MockitoAnnotations.initMocks(this)
keyguardRepository = kosmos.fakeKeyguardRepository
+ keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
tutorialRepository = kosmos.fakeCommunalTutorialRepository
widgetRepository = kosmos.fakeCommunalWidgetRepository
smartspaceRepository = kosmos.fakeSmartspaceRepository
mediaRepository = kosmos.fakeCommunalMediaRepository
userRepository = kosmos.fakeUserRepository
shadeTestUtil = kosmos.shadeTestUtil
+ communalRepository = kosmos.fakeCommunalRepository
kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
@@ -125,6 +140,7 @@
underTest =
CommunalViewModel(
testScope,
+ kosmos.keyguardTransitionInteractor,
kosmos.communalInteractor,
kosmos.communalTutorialInteractor,
kosmos.shadeInteractor,
@@ -326,6 +342,105 @@
assertThat(underTest.canChangeScene()).isFalse()
}
+ @Test
+ fun isFocusable_isFalse_whenTransitioningAwayFromGlanceableHub() =
+ testScope.runTest {
+ val isFocusable by collectLastValue(underTest.isFocusable)
+
+ // Shade not expanded.
+ shadeTestUtil.setLockscreenShadeExpansion(0f)
+ // On communal scene.
+ communalRepository.setTransitionState(
+ flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+ )
+ // Open bouncer.
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+
+ keyguardTransitionRepository.sendTransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ transitionState = TransitionState.RUNNING,
+ value = 0.5f,
+ )
+ assertThat(isFocusable).isEqualTo(false)
+
+ // Transitioned to bouncer.
+ keyguardTransitionRepository.sendTransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ transitionState = TransitionState.FINISHED,
+ value = 1f,
+ )
+ assertThat(isFocusable).isEqualTo(false)
+ }
+
+ @Test
+ fun isFocusable_isFalse_whenNotOnCommunalScene() =
+ testScope.runTest {
+ val isFocusable by collectLastValue(underTest.isFocusable)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope = testScope,
+ )
+ shadeTestUtil.setLockscreenShadeExpansion(0f)
+ // Transitioned away from communal scene.
+ communalRepository.setTransitionState(
+ flowOf(ObservableTransitionState.Idle(CommunalScenes.Blank))
+ )
+
+ assertThat(isFocusable).isEqualTo(false)
+ }
+
+ @Test
+ fun isFocusable_isTrue_whenIdleOnCommunal_andShadeNotExpanded() =
+ testScope.runTest {
+ val isFocusable by collectLastValue(underTest.isFocusable)
+
+ // On communal scene.
+ communalRepository.setTransitionState(
+ flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+ )
+ // Transitioned to Glanceable hub.
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope = testScope,
+ )
+ // Shade not expanded.
+ shadeTestUtil.setLockscreenShadeExpansion(0f)
+
+ assertThat(isFocusable).isEqualTo(true)
+ }
+
+ @Test
+ fun isFocusable_isFalse_whenQsIsExpanded() =
+ testScope.runTest {
+ val isFocusable by collectLastValue(underTest.isFocusable)
+
+ // On communal scene.
+ communalRepository.setTransitionState(
+ flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+ )
+ // Transitioned to Glanceable hub.
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope = testScope,
+ )
+ // Qs is expanded.
+ shadeTestUtil.setQsExpansion(1f)
+
+ assertThat(isFocusable).isEqualTo(false)
+ }
+
private suspend fun setIsMainUser(isMainUser: Boolean) {
whenever(user.isMain).thenReturn(isMainUser)
userRepository.setUserInfos(listOf(user))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 2fb8212..f58e01f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.scene.data.repository.sceneContainerRepository
import com.android.systemui.scene.sceneContainerConfig
import com.android.systemui.scene.sceneKeys
+import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.testKosmos
@@ -39,7 +40,6 @@
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -54,19 +54,46 @@
private lateinit var underTest: SceneInteractor
- @Before
- fun setUp() {
- underTest = kosmos.sceneInteractor
- }
-
@Test
fun allSceneKeys() {
+ underTest = kosmos.sceneInteractor
assertThat(underTest.allSceneKeys()).isEqualTo(kosmos.sceneKeys)
}
@Test
+ fun changeScene_toUnknownScene_doesNothing() =
+ testScope.runTest {
+ val sceneKeys =
+ listOf(
+ Scenes.QuickSettings,
+ Scenes.Shade,
+ Scenes.Lockscreen,
+ Scenes.Gone,
+ Scenes.Communal,
+ )
+ val navigationDistances =
+ mapOf(
+ Scenes.Gone to 0,
+ Scenes.Lockscreen to 0,
+ Scenes.Communal to 1,
+ Scenes.Shade to 2,
+ Scenes.QuickSettings to 3,
+ )
+ kosmos.sceneContainerConfig =
+ SceneContainerConfig(sceneKeys, Scenes.Lockscreen, navigationDistances)
+ underTest = kosmos.sceneInteractor
+ val currentScene by collectLastValue(underTest.currentScene)
+ val previousScene = currentScene
+ assertThat(previousScene).isNotEqualTo(Scenes.Bouncer)
+ underTest.changeScene(Scenes.Bouncer, "reason")
+ assertThat(currentScene).isEqualTo(previousScene)
+ }
+
+ @Test
fun changeScene() =
testScope.runTest {
+ underTest = kosmos.sceneInteractor
+
val currentScene by collectLastValue(underTest.currentScene)
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
@@ -77,6 +104,8 @@
@Test
fun changeScene_toGoneWhenUnl_doesNotThrow() =
testScope.runTest {
+ underTest = kosmos.sceneInteractor
+
val currentScene by collectLastValue(underTest.currentScene)
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
@@ -91,11 +120,15 @@
@Test(expected = IllegalStateException::class)
fun changeScene_toGoneWhenStillLocked_throws() =
- testScope.runTest { underTest.changeScene(Scenes.Gone, "reason") }
+ testScope.runTest {
+ underTest = kosmos.sceneInteractor
+ underTest.changeScene(Scenes.Gone, "reason")
+ }
@Test
fun sceneChanged_inDataSource() =
testScope.runTest {
+ underTest = kosmos.sceneInteractor
val currentScene by collectLastValue(underTest.currentScene)
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
@@ -107,6 +140,7 @@
@Test
fun transitionState() =
testScope.runTest {
+ underTest = kosmos.sceneInteractor
val underTest = kosmos.sceneContainerRepository
val transitionState =
MutableStateFlow<ObservableTransitionState>(
@@ -143,6 +177,7 @@
@Test
fun transitioningTo() =
testScope.runTest {
+ underTest = kosmos.sceneInteractor
val transitionState =
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Idle(underTest.currentScene.value)
@@ -179,6 +214,7 @@
@Test
fun isTransitionUserInputOngoing_idle_false() =
testScope.runTest {
+ underTest = kosmos.sceneInteractor
val transitionState =
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Idle(Scenes.Shade)
@@ -193,6 +229,7 @@
@Test
fun isTransitionUserInputOngoing_transition_true() =
testScope.runTest {
+ underTest = kosmos.sceneInteractor
val transitionState =
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Transition(
@@ -213,6 +250,7 @@
@Test
fun isTransitionUserInputOngoing_updateMidTransition_false() =
testScope.runTest {
+ underTest = kosmos.sceneInteractor
val transitionState =
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Transition(
@@ -244,6 +282,7 @@
@Test
fun isTransitionUserInputOngoing_updateOnIdle_false() =
testScope.runTest {
+ underTest = kosmos.sceneInteractor
val transitionState =
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Transition(
@@ -268,6 +307,7 @@
@Test
fun isVisible() =
testScope.runTest {
+ underTest = kosmos.sceneInteractor
val isVisible by collectLastValue(underTest.isVisible)
assertThat(isVisible).isTrue()
@@ -281,6 +321,7 @@
@Test
fun isVisible_duringRemoteUserInteraction_forcedVisible() =
testScope.runTest {
+ underTest = kosmos.sceneInteractor
underTest.setVisible(false, "reason")
val isVisible by collectLastValue(underTest.isVisible)
assertThat(isVisible).isFalse()
diff --git a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
index d13efd2..f644584f 100644
--- a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
@@ -174,7 +174,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
- android:accessibilityLiveRegion="polite"
+ android:accessibilityLiveRegion="assertive"
android:fadingEdge="horizontal"
android:gravity="center_horizontal"
android:scrollHorizontally="true"
diff --git a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
index a6e660f..46b8e46 100644
--- a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
@@ -152,7 +152,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
- android:accessibilityLiveRegion="polite"
+ android:accessibilityLiveRegion="assertive"
android:fadingEdge="horizontal"
android:gravity="center_horizontal"
android:scrollHorizontally="true"
diff --git a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
index c724d24..d51fe58 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
@@ -150,7 +150,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
- android:accessibilityLiveRegion="polite"
+ android:accessibilityLiveRegion="assertive"
android:fadingEdge="horizontal"
android:gravity="center_horizontal"
android:scrollHorizontally="true"
diff --git a/packages/SystemUI/res/layout/screenshot_shelf.xml b/packages/SystemUI/res/layout/screenshot_shelf.xml
index 79b82bf..7adfa6c 100644
--- a/packages/SystemUI/res/layout/screenshot_shelf.xml
+++ b/packages/SystemUI/res/layout/screenshot_shelf.xml
@@ -20,133 +20,138 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
- <FrameLayout
- android:id="@+id/actions_container_background"
- android:visibility="gone"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:elevation="4dp"
- android:background="@drawable/shelf_action_chip_container_background"
- android:layout_marginHorizontal="@dimen/overlay_action_container_margin_horizontal"
- android:layout_marginBottom="@dimen/screenshot_shelf_vertical_margin"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintBottom_toTopOf="@id/guideline"
- >
- <HorizontalScrollView
- android:id="@+id/actions_container"
- android:layout_width="wrap_content"
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/screenshot_static"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <FrameLayout
+ android:id="@+id/actions_container_background"
+ android:visibility="gone"
android:layout_height="wrap_content"
- android:layout_marginVertical="@dimen/overlay_action_container_padding_vertical"
- android:layout_marginHorizontal="@dimen/overlay_action_chip_margin_start"
- android:background="@drawable/shelf_action_container_clipping_shape"
- android:clipToOutline="true"
- android:scrollbars="none">
- <LinearLayout
- android:id="@+id/screenshot_actions"
+ android:layout_width="wrap_content"
+ android:elevation="4dp"
+ android:background="@drawable/shelf_action_chip_container_background"
+ android:layout_marginHorizontal="@dimen/overlay_action_container_margin_horizontal"
+ android:layout_marginBottom="@dimen/screenshot_shelf_vertical_margin"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/guideline"
+ >
+ <HorizontalScrollView
+ android:id="@+id/actions_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:showDividers="middle"
- android:divider="@drawable/shelf_action_chip_divider"
- android:animateLayoutChanges="true"
- />
- </HorizontalScrollView>
- </FrameLayout>
- <View
- android:id="@+id/screenshot_preview_border"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
- android:layout_marginTop="@dimen/overlay_border_width_neg"
- android:layout_marginEnd="@dimen/overlay_border_width_neg"
- android:layout_marginBottom="@dimen/screenshot_shelf_vertical_margin"
- android:elevation="4dp"
- android:background="@drawable/overlay_border"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="@id/screenshot_preview"
- app:layout_constraintEnd_toEndOf="@id/screenshot_preview"
- app:layout_constraintBottom_toTopOf="@id/actions_container_background"/>
- <ImageView
- android:id="@+id/screenshot_preview"
- android:layout_width="@dimen/overlay_x_scale"
- android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/overlay_border_width"
- android:layout_marginBottom="@dimen/overlay_border_width"
- android:layout_gravity="center"
- android:elevation="4dp"
- android:contentDescription="@string/screenshot_edit_description"
- android:scaleType="fitEnd"
- android:background="@drawable/overlay_preview_background"
- android:adjustViewBounds="true"
- android:clickable="true"
- app:layout_constraintStart_toStartOf="@id/screenshot_preview_border"
- app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"/>
- <ImageView
- android:id="@+id/screenshot_badge"
- android:layout_width="56dp"
- android:layout_height="56dp"
- android:visibility="gone"
- android:elevation="5dp"
- app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"
- app:layout_constraintEnd_toEndOf="@id/screenshot_preview_border"/>
- <FrameLayout
- android:id="@+id/screenshot_dismiss_button"
- android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
- android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
- android:elevation="7dp"
- android:visibility="gone"
- app:layout_constraintStart_toEndOf="@id/screenshot_preview"
- app:layout_constraintEnd_toEndOf="@id/screenshot_preview"
- app:layout_constraintTop_toTopOf="@id/screenshot_preview"
- app:layout_constraintBottom_toTopOf="@id/screenshot_preview"
- android:contentDescription="@string/screenshot_dismiss_description">
+ android:layout_marginVertical="@dimen/overlay_action_container_padding_vertical"
+ android:layout_marginHorizontal="@dimen/overlay_action_chip_margin_start"
+ android:background="@drawable/shelf_action_container_clipping_shape"
+ android:clipToOutline="true"
+ android:scrollbars="none">
+ <LinearLayout
+ android:id="@+id/screenshot_actions"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:showDividers="middle"
+ android:divider="@drawable/shelf_action_chip_divider"
+ android:animateLayoutChanges="true"
+ />
+ </HorizontalScrollView>
+ </FrameLayout>
+ <View
+ android:id="@+id/screenshot_preview_border"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
+ android:layout_marginTop="@dimen/overlay_border_width_neg"
+ android:layout_marginEnd="@dimen/overlay_border_width_neg"
+ android:layout_marginBottom="@dimen/screenshot_shelf_vertical_margin"
+ android:elevation="4dp"
+ android:background="@drawable/overlay_border"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="@id/screenshot_preview"
+ app:layout_constraintEnd_toEndOf="@id/screenshot_preview"
+ app:layout_constraintBottom_toTopOf="@id/actions_container_background"/>
<ImageView
- android:id="@+id/screenshot_dismiss_image"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_margin="@dimen/overlay_dismiss_button_margin"
- android:background="@drawable/circular_background"
- android:backgroundTint="?androidprv:attr/materialColorPrimary"
- android:tint="?androidprv:attr/materialColorOnPrimary"
- android:padding="4dp"
- android:src="@drawable/ic_close"/>
- </FrameLayout>
- <ImageView
- android:id="@+id/screenshot_scrollable_preview"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:scaleType="matrix"
- android:visibility="gone"
- app:layout_constraintStart_toStartOf="@id/screenshot_preview"
- app:layout_constraintTop_toTopOf="@id/screenshot_preview"
- android:elevation="7dp"/>
+ android:id="@+id/screenshot_preview"
+ android:layout_width="@dimen/overlay_x_scale"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/overlay_border_width"
+ android:layout_marginBottom="@dimen/overlay_border_width"
+ android:layout_gravity="center"
+ android:elevation="4dp"
+ android:contentDescription="@string/screenshot_edit_description"
+ android:scaleType="fitEnd"
+ android:background="@drawable/overlay_preview_background"
+ android:adjustViewBounds="true"
+ android:clickable="true"
+ app:layout_constraintStart_toStartOf="@id/screenshot_preview_border"
+ app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"/>
+ <ImageView
+ android:id="@+id/screenshot_badge"
+ android:layout_width="56dp"
+ android:layout_height="56dp"
+ android:visibility="gone"
+ android:elevation="5dp"
+ app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"
+ app:layout_constraintEnd_toEndOf="@id/screenshot_preview_border"/>
+ <FrameLayout
+ android:id="@+id/screenshot_dismiss_button"
+ android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
+ android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
+ android:elevation="7dp"
+ android:visibility="gone"
+ app:layout_constraintStart_toEndOf="@id/screenshot_preview"
+ app:layout_constraintEnd_toEndOf="@id/screenshot_preview"
+ app:layout_constraintTop_toTopOf="@id/screenshot_preview"
+ app:layout_constraintBottom_toTopOf="@id/screenshot_preview"
+ android:contentDescription="@string/screenshot_dismiss_description">
+ <ImageView
+ android:id="@+id/screenshot_dismiss_image"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="@dimen/overlay_dismiss_button_margin"
+ android:background="@drawable/circular_background"
+ android:backgroundTint="?androidprv:attr/materialColorPrimary"
+ android:tint="?androidprv:attr/materialColorOnPrimary"
+ android:padding="4dp"
+ android:src="@drawable/ic_close"/>
+ </FrameLayout>
+ <ImageView
+ android:id="@+id/screenshot_scrollable_preview"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:scaleType="matrix"
+ android:visibility="gone"
+ app:layout_constraintStart_toStartOf="@id/screenshot_preview"
+ app:layout_constraintTop_toTopOf="@id/screenshot_preview"
+ android:elevation="7dp"/>
- <androidx.constraintlayout.widget.Guideline
- android:id="@+id/guideline"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- app:layout_constraintGuide_end="0dp" />
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/guideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ app:layout_constraintGuide_end="0dp" />
- <FrameLayout
- android:id="@+id/screenshot_message_container"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginHorizontal="@dimen/overlay_action_container_margin_horizontal"
- android:layout_marginTop="4dp"
- android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
- android:paddingHorizontal="@dimen/overlay_action_container_padding_end"
- android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
- android:elevation="4dp"
- android:background="@drawable/action_chip_container_background"
- android:visibility="gone"
- app:layout_constraintTop_toBottomOf="@id/guideline"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintWidth_max="450dp"
- app:layout_constraintHorizontal_bias="0">
- <include layout="@layout/screenshot_work_profile_first_run" />
- <include layout="@layout/screenshot_detection_notice" />
- </FrameLayout>
+ <FrameLayout
+ android:id="@+id/screenshot_message_container"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="@dimen/overlay_action_container_margin_horizontal"
+ android:layout_marginTop="4dp"
+ android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
+ android:paddingHorizontal="@dimen/overlay_action_container_padding_end"
+ android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
+ android:elevation="4dp"
+ android:background="@drawable/action_chip_container_background"
+ android:visibility="gone"
+ app:layout_constraintTop_toBottomOf="@id/guideline"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintWidth_max="450dp"
+ app:layout_constraintHorizontal_bias="0">
+ <include layout="@layout/screenshot_work_profile_first_run" />
+ <include layout="@layout/screenshot_detection_notice" />
+ </FrameLayout>
+ </androidx.constraintlayout.widget.ConstraintLayout>
<ImageView
android:id="@+id/screenshot_flash"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml
index f35de05..dc9c4f1 100644
--- a/packages/SystemUI/res/layout/volume_dialog_row.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_row.xml
@@ -52,7 +52,6 @@
android:paddingRight="0dp"
android:paddingStart="0dp"
android:paddingEnd="0dp"
- android:clickable="true"
android:layout_width="@dimen/volume_row_slider_height"
android:layout_height="match_parent"
android:layout_gravity="center"
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index a211147..da56951 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -112,10 +112,6 @@
}
// set selected to enable marquee unless a screen reader is enabled
- logoView.isSelected =
- !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
- logoDescriptionView.isSelected =
- !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
titleView.isSelected =
!accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
subtitleView.isSelected =
@@ -419,19 +415,6 @@
indicatorMessageView.isSelected =
!accessibilityManager.isEnabled ||
!accessibilityManager.isTouchExplorationEnabled
-
- /**
- * Note: Talkback 14.0 has new rate-limitation design to reduce frequency of
- * TYPE_WINDOW_CONTENT_CHANGED events to once every 30 seconds. (context:
- * b/281765653#comment18) Using {@link View#announceForAccessibility}
- * instead as workaround since sending events exceeding this frequency is
- * required.
- */
- indicatorMessageView?.text?.let {
- if (it.isNotBlank()) {
- view.announceForAccessibility(it)
- }
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 511bdc4..a081ecc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -37,8 +37,8 @@
) {
val currentScene: Flow<SceneKey> = communalInteractor.desiredScene
- /** Whether communal hub can be focused to enable accessibility actions. */
- val isFocusable: Flow<Boolean> = communalInteractor.isIdleOnCommunal
+ /** Whether communal hub can be focused by accessibility tools. */
+ open val isFocusable: Flow<Boolean> = MutableStateFlow(false)
/** Whether widgets are currently being re-ordered. */
open val reorderingWidgets: StateFlow<Boolean> = MutableStateFlow(false)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 9dacf8c..24ea7b6 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -22,6 +22,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
@@ -54,6 +56,7 @@
@Inject
constructor(
@Application private val scope: CoroutineScope,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val communalInteractor: CommunalInteractor,
tutorialInteractor: CommunalTutorialInteractor,
private val shadeInteractor: ShadeInteractor,
@@ -93,6 +96,18 @@
private val _currentPopup: MutableStateFlow<PopupType?> = MutableStateFlow(null)
override val currentPopup: Flow<PopupType?> = _currentPopup.asStateFlow()
+ // The widget is focusable for accessibility when the hub is fully visible and shade is not
+ // opened.
+ override val isFocusable: Flow<Boolean> =
+ combine(
+ keyguardTransitionInteractor.isFinishedInState(KeyguardState.GLANCEABLE_HUB),
+ communalInteractor.isIdleOnCommunal,
+ shadeInteractor.isAnyFullyExpanded,
+ ) { transitionedToGlanceableHub, isIdleOnCommunal, isAnyFullyExpanded ->
+ transitionedToGlanceableHub && isIdleOnCommunal && !isAnyFullyExpanded
+ }
+ .distinctUntilChanged()
+
private val _isEnableWidgetDialogShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
val isEnableWidgetDialogShowing: Flow<Boolean> = _isEnableWidgetDialogShowing.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
index 840c3a8..0fe9bf4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
@@ -22,8 +22,10 @@
import android.graphics.Rect
import android.view.View
import android.view.ViewOutlineProvider
+import android.view.accessibility.AccessibilityNodeInfo
import com.android.systemui.animation.LaunchableView
import com.android.systemui.animation.LaunchableViewDelegate
+import com.android.systemui.res.R
/** AppWidgetHostView that displays in communal hub with support for rounded corners. */
class CommunalAppWidgetHostView(context: Context) : AppWidgetHostView(context), LaunchableView {
@@ -42,6 +44,25 @@
init {
enforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(context)
enforcedRectangle = Rect()
+
+ accessibilityDelegate =
+ object : AccessibilityDelegate() {
+ override fun onInitializeAccessibilityNodeInfo(
+ host: View,
+ info: AccessibilityNodeInfo
+ ) {
+ super.onInitializeAccessibilityNodeInfo(host, info)
+ // Hint user to long press in order to enter edit mode
+ info.addAction(
+ AccessibilityNodeInfo.AccessibilityAction(
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id,
+ context.getString(
+ R.string.accessibility_action_label_edit_widgets
+ ).lowercase()
+ )
+ )
+ }
+ }
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 0bc29a8..95bc514 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -123,6 +123,7 @@
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.globalactions.domain.interactor.GlobalActionsInteractor;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
import com.android.systemui.plugins.GlobalActionsPanelPlugin;
import com.android.systemui.scrim.ScrimDrawable;
@@ -186,6 +187,7 @@
private static final String GLOBAL_ACTION_KEY_LOGOUT = "logout";
static final String GLOBAL_ACTION_KEY_EMERGENCY = "emergency";
static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot";
+ static final String GLOBAL_ACTION_KEY_SYSTEM_UPDATE = "system_update";
// See NotificationManagerService#scheduleDurationReachedLocked
private static final long TOAST_FADE_TIME = 333;
@@ -213,6 +215,7 @@
private final TelecomManager mTelecomManager;
private final MetricsLogger mMetricsLogger;
private final UiEventLogger mUiEventLogger;
+ private final ActivityStarter mActivityStarter;
// Used for RingerModeTracker
private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
@@ -317,7 +320,10 @@
GA_CLOSE_TAP_OUTSIDE(810),
@UiEvent(doc = "Power menu was closed via power + volume up.")
- GA_CLOSE_POWER_VOLUP(811);
+ GA_CLOSE_POWER_VOLUP(811),
+
+ @UiEvent(doc = "System Update button was pressed.")
+ GA_SYSTEM_UPDATE_PRESS(1716);
private final int mId;
@@ -349,6 +355,7 @@
@NonNull VibratorHelper vibrator,
@Main Resources resources,
ConfigurationController configurationController,
+ ActivityStarter activityStarter,
UserTracker userTracker,
KeyguardStateController keyguardStateController,
UserManager userManager,
@@ -385,6 +392,7 @@
mSecureSettings = secureSettings;
mResources = resources;
mConfigurationController = configurationController;
+ mActivityStarter = activityStarter;
mUserTracker = userTracker;
mUserManager = userManager;
mTrustManager = trustManager;
@@ -659,6 +667,8 @@
if (shouldDisplayEmergency()) {
addIfShouldShowAction(tempActions, new EmergencyDialerAction());
}
+ } else if (GLOBAL_ACTION_KEY_SYSTEM_UPDATE.equals(actionKey)) {
+ addIfShouldShowAction(tempActions, new SystemUpdateAction());
} else {
Log.e(TAG, "Invalid global action key " + actionKey);
}
@@ -1145,6 +1155,40 @@
}
}
+ @VisibleForTesting
+ final class SystemUpdateAction extends SinglePressAction {
+
+ SystemUpdateAction() {
+ super(com.android.settingslib.R.drawable.ic_system_update,
+ com.android.settingslib.R.string.system_update_settings_list_item_title);
+ }
+
+ @Override
+ public void onPress() {
+ mUiEventLogger.log(GlobalActionsEvent.GA_SYSTEM_UPDATE_PRESS);
+ launchSystemUpdate();
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+
+ private void launchSystemUpdate() {
+ Intent intent = new Intent(Settings.ACTION_SYSTEM_UPDATE_SETTINGS);
+ intent.addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ // postStartActivityDismissingKeyguard is used for showing keyguard
+ // input/pin/password screen if lockscreen is secured, before sending the intent.
+ mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
+ }
+ }
+
private Action getSettingsAction() {
return new SinglePressAction(R.drawable.ic_settings,
R.string.global_action_settings) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index 30f33a3..f8086f5 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -21,19 +21,20 @@
import android.graphics.Paint
import android.graphics.Point
import android.os.Handler
-import android.os.SystemClock
import android.util.Log
import android.util.MathUtils
import android.view.Gravity
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import android.view.VelocityTracker
+import android.view.View
import android.view.ViewConfiguration
import android.view.WindowManager
import androidx.annotation.VisibleForTesting
import androidx.core.os.postDelayed
import androidx.core.view.isVisible
import androidx.dynamicanimation.animation.DynamicAnimation
+import com.android.internal.jank.Cuj
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.util.LatencyTracker
import com.android.systemui.dagger.qualifiers.Main
@@ -41,6 +42,7 @@
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.ViewController
+import com.android.systemui.util.time.SystemClock
import java.io.PrintWriter
import javax.inject.Inject
import kotlin.math.abs
@@ -84,6 +86,7 @@
private val windowManager: WindowManager,
private val viewConfiguration: ViewConfiguration,
@Main private val mainHandler: Handler,
+ private val systemClock: SystemClock,
private val vibratorHelper: VibratorHelper,
private val configurationController: ConfigurationController,
private val latencyTracker: LatencyTracker,
@@ -102,6 +105,7 @@
private val windowManager: WindowManager,
private val viewConfiguration: ViewConfiguration,
@Main private val mainHandler: Handler,
+ private val systemClock: SystemClock,
private val vibratorHelper: VibratorHelper,
private val configurationController: ConfigurationController,
private val latencyTracker: LatencyTracker,
@@ -115,6 +119,7 @@
windowManager,
viewConfiguration,
mainHandler,
+ systemClock,
vibratorHelper,
configurationController,
latencyTracker,
@@ -158,9 +163,9 @@
private var gestureInactiveTime = 0L
private val elapsedTimeSinceInactive
- get() = SystemClock.uptimeMillis() - gestureInactiveTime
+ get() = systemClock.uptimeMillis() - gestureInactiveTime
private val elapsedTimeSinceEntry
- get() = SystemClock.uptimeMillis() - gestureEntryTime
+ get() = systemClock.uptimeMillis() - gestureEntryTime
private var pastThresholdWhileEntryOrInactiveTime = 0L
private var entryToActiveDelay = 0F
@@ -178,7 +183,7 @@
// Distance in pixels a drag can be considered for a fling event
private var minFlingDistance = 0
- private val failsafeRunnable = Runnable { onFailsafe() }
+ internal val failsafeRunnable = Runnable { onFailsafe() }
internal enum class GestureState {
/* Arrow is off the screen and invisible */
@@ -370,6 +375,7 @@
// Receiving a CANCEL implies that something else intercepted
// the gesture, i.e., the user did not cancel their gesture.
// Therefore, disappear immediately, with minimum fanfare.
+ interactionJankMonitor.cancel(Cuj.CUJ_BACK_PANEL_ARROW)
updateArrowState(GestureState.GONE)
velocityTracker = null
}
@@ -692,10 +698,10 @@
}
if (isPastThresholdForFirstTime) {
- pastThresholdWhileEntryOrInactiveTime = SystemClock.uptimeMillis()
+ pastThresholdWhileEntryOrInactiveTime = systemClock.uptimeMillis()
entryToActiveDelay = dynamicDelay()
}
- val timePastThreshold = SystemClock.uptimeMillis() - pastThresholdWhileEntryOrInactiveTime
+ val timePastThreshold = systemClock.uptimeMillis() - pastThresholdWhileEntryOrInactiveTime
return timePastThreshold > entryToActiveDelay
}
@@ -881,6 +887,16 @@
previousState = currentState
currentState = newState
+ // First, update the jank tracker
+ when (currentState) {
+ GestureState.ENTRY -> {
+ interactionJankMonitor.cancel(Cuj.CUJ_BACK_PANEL_ARROW)
+ interactionJankMonitor.begin(mView, Cuj.CUJ_BACK_PANEL_ARROW)
+ }
+ GestureState.GONE -> interactionJankMonitor.end(Cuj.CUJ_BACK_PANEL_ARROW)
+ else -> {}
+ }
+
when (currentState) {
GestureState.CANCELLED -> {
backCallback.cancelBack()
@@ -912,7 +928,7 @@
mView.isVisible = true
updateRestingArrowDimens()
- gestureEntryTime = SystemClock.uptimeMillis()
+ gestureEntryTime = systemClock.uptimeMillis()
}
GestureState.ACTIVE -> {
previousXTranslationOnActiveOffset = previousXTranslation
@@ -927,7 +943,7 @@
mView.popOffEdge(popVelocity)
}
GestureState.INACTIVE -> {
- gestureInactiveTime = SystemClock.uptimeMillis()
+ gestureInactiveTime = systemClock.uptimeMillis()
// Typically entering INACTIVE means
// totalTouchDelta <= deactivationSwipeTriggerThreshold
@@ -1041,6 +1057,11 @@
pw.println(" isLeftPanel=${mView.isLeftPanel}")
}
+ @VisibleForTesting
+ internal fun getBackPanelView(): BackPanel {
+ return mView
+ }
+
init {
if (DEBUG)
mView.drawDebugInfo = { canvas ->
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index d5b05ef..60469c0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -116,8 +116,6 @@
public class InternetDialogController implements AccessPointController.AccessPointCallback {
private static final String TAG = "InternetDialogController";
- private static final String ACTION_NETWORK_PROVIDER_SETTINGS =
- "android.settings.NETWORK_PROVIDER_SETTINGS";
private static final String ACTION_WIFI_SCANNING_SETTINGS =
"android.settings.WIFI_SCANNING_SETTINGS";
/**
@@ -361,7 +359,8 @@
@VisibleForTesting
protected Intent getSettingsIntent() {
- return new Intent(ACTION_NETWORK_PROVIDER_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return new Intent(Settings.ACTION_NETWORK_PROVIDER_SETTINGS).addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK);
}
@Nullable
diff --git a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
index 2a73b53..063a52c 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
@@ -16,10 +16,16 @@
package com.android.systemui.scene
+import com.android.systemui.CoreStartable
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.scene.domain.startable.SceneContainerStartable
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
+import dagger.Binds
import dagger.Module
import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
/** Scene framework Dagger module suitable for variants that want to exclude "keyguard" scenes. */
@Module(
@@ -31,28 +37,41 @@
ShadeSceneModule::class,
],
)
-object KeyguardlessSceneContainerFrameworkModule {
+interface KeyguardlessSceneContainerFrameworkModule {
- // TODO(b/298234162): provide a SceneContainerStartable without lockscreen and bouncer.
+ @Binds
+ @IntoMap
+ @ClassKey(SceneContainerStartable::class)
+ fun containerStartable(impl: SceneContainerStartable): CoreStartable
- @Provides
- fun containerConfig(): SceneContainerConfig {
- return SceneContainerConfig(
- // Note that this list is in z-order. The first one is the bottom-most and the
- // last one is top-most.
- sceneKeys =
- listOf(
- Scenes.Gone,
- Scenes.QuickSettings,
- Scenes.Shade,
- ),
- initialSceneKey = Scenes.Gone,
- navigationDistances =
- mapOf(
- Scenes.Gone to 0,
- Scenes.Shade to 1,
- Scenes.QuickSettings to 2,
- ),
- )
+ @Binds
+ @IntoMap
+ @ClassKey(WindowRootViewVisibilityInteractor::class)
+ fun bindWindowRootViewVisibilityInteractor(
+ impl: WindowRootViewVisibilityInteractor
+ ): CoreStartable
+
+ companion object {
+
+ @Provides
+ fun containerConfig(): SceneContainerConfig {
+ return SceneContainerConfig(
+ // Note that this list is in z-order. The first one is the bottom-most and the
+ // last one is top-most.
+ sceneKeys =
+ listOf(
+ Scenes.Gone,
+ Scenes.QuickSettings,
+ Scenes.Shade,
+ ),
+ initialSceneKey = Scenes.Gone,
+ navigationDistances =
+ mapOf(
+ Scenes.Gone to 0,
+ Scenes.Shade to 1,
+ Scenes.QuickSettings to 2,
+ ),
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
index 994b012..5748ad4 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
@@ -81,14 +81,6 @@
toScene: SceneKey,
transitionKey: TransitionKey? = null,
) {
- check(allSceneKeys().contains(toScene)) {
- """
- Cannot set the desired scene key to "$toScene". The configuration does not
- contain a scene with that key.
- """
- .trimIndent()
- }
-
dataSource.changeScene(
toScene = toScene,
transitionKey = transitionKey,
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 2ccd3b9..93cef61 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -162,6 +162,10 @@
loggingReason: String,
transitionKey: TransitionKey? = null,
) {
+ if (!repository.allSceneKeys().contains(toScene)) {
+ return
+ }
+
check(
toScene != Scenes.Gone || deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked
) {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 39ec12f..d5de28a3 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -57,12 +57,14 @@
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
import com.android.systemui.util.asIndenting
+import com.android.systemui.util.kotlin.getOrNull
import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.printSection
import com.android.systemui.util.println
import dagger.Lazy
import java.io.PrintWriter
+import java.util.Optional
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -107,7 +109,7 @@
private val authenticationInteractor: Lazy<AuthenticationInteractor>,
private val windowController: NotificationShadeWindowController,
private val deviceProvisioningInteractor: DeviceProvisioningInteractor,
- private val centralSurfaces: CentralSurfaces,
+ private val centralSurfacesOptLazy: Lazy<Optional<CentralSurfaces>>,
private val headsUpInteractor: HeadsUpNotificationInteractor,
private val occlusionInteractor: SceneContainerOcclusionInteractor,
private val faceUnlockInteractor: DeviceEntryFaceAuthInteractor,
@@ -115,6 +117,8 @@
private val uiEventLogger: UiEventLogger,
private val sceneBackInteractor: SceneBackInteractor,
) : CoreStartable {
+ private val centralSurfaces: CentralSurfaces?
+ get() = centralSurfacesOptLazy.get().getOrNull()
override fun start() {
if (SceneContainerFlag.isEnabled) {
@@ -542,7 +546,7 @@
}
.collect { isInteractingOrNull ->
isInteractingOrNull?.let { isInteracting ->
- centralSurfaces.setInteracting(
+ centralSurfaces?.setInteracting(
StatusBarManager.WINDOW_STATUS_BAR,
isInteracting,
)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
index 864f29a..d4e711e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
@@ -229,6 +229,8 @@
return CallbackToFutureAdapter.getFuture(
(completer) -> {
executor.execute(() -> {
+ // save images as quickly as possible on the background thread
+ Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
try {
completer.set(task.execute());
} catch (ImageExportException | InterruptedException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ReferenceScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/ReferenceScreenshotModule.java
index afb0280..d1b08f1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ReferenceScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ReferenceScreenshotModule.java
@@ -35,4 +35,10 @@
@Binds
ScreenshotActionsProvider.Factory bindScreenshotActionsProviderFactory(
DefaultScreenshotActionsProvider.Factory defaultScreenshotActionsProviderFactory);
+
+ /** */
+ @Provides
+ static ThumbnailObserver providesThumbnailObserver() {
+ return new ThumbnailObserver();
+ };
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
index 12a3daa..9b754f3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
@@ -32,6 +32,7 @@
import android.window.OnBackInvokedCallback
import android.window.OnBackInvokedDispatcher
import androidx.core.animation.doOnEnd
+import androidx.core.animation.doOnStart
import com.android.internal.logging.UiEventLogger
import com.android.systemui.log.DebugLogger.debugLog
import com.android.systemui.res.R
@@ -56,6 +57,7 @@
private val logger: UiEventLogger,
private val viewModel: ScreenshotViewModel,
private val windowManager: WindowManager,
+ private val thumbnailObserver: ThumbnailObserver,
@Assisted private val context: Context,
@Assisted private val displayId: Int
) : ScreenshotViewProxy {
@@ -85,6 +87,7 @@
onDismissalRequested = { event, velocity -> requestDismissal(event, velocity) },
onDismissalCancelled = { animationController.getSwipeReturnAnimation().start() }
)
+ view.updateInsets(windowManager.currentWindowMetrics.windowInsets)
addPredictiveBackListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
setOnKeyListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
debugLog(DEBUG_WINDOW) { "adding OnComputeInternalInsetsListener" }
@@ -99,6 +102,10 @@
info.touchableRegion.set(touchableRegion)
}
screenshotPreview = view.screenshotPreview
+ thumbnailObserver.setViews(
+ view.screenshotPreview,
+ view.requireViewById(R.id.screenshot_preview_border)
+ )
}
override fun reset() {
@@ -106,13 +113,19 @@
isPendingSharedTransition = false
viewModel.reset()
}
- override fun updateInsets(insets: WindowInsets) {}
+ override fun updateInsets(insets: WindowInsets) {
+ view.updateInsets(insets)
+ }
override fun updateOrientation(insets: WindowInsets) {}
override fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator {
val entrance = animationController.getEntranceAnimation(screenRect, showFlash)
- // reset the timeout when animation finishes
- entrance.doOnEnd { callbacks?.onUserInteraction() }
+ entrance.doOnStart { thumbnailObserver.onEntranceStarted() }
+ entrance.doOnEnd {
+ // reset the timeout when animation finishes
+ callbacks?.onUserInteraction()
+ thumbnailObserver.onEntranceComplete()
+ }
return entrance
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ThumbnailObserver.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ThumbnailObserver.kt
new file mode 100644
index 0000000..cf62a14
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ThumbnailObserver.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.view.View
+import android.widget.ImageView
+
+/** An observer of thumbnail UI and entrance state that can be overridden if needed. */
+open class ThumbnailObserver {
+ /** Thumbnail image and border views. */
+ open fun setViews(image: ImageView, border: View) {}
+
+ /** Entrance animation has begun. */
+ open fun onEntranceStarted() {}
+
+ /** Entrance animation has completed/stopped. */
+ open fun onEntranceComplete() {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
index 2e4473e..4eceb17 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
@@ -25,6 +25,7 @@
import android.util.MathUtils
import android.view.View
import android.view.animation.AnimationUtils
+import android.widget.ImageView
import androidx.core.animation.doOnEnd
import androidx.core.animation.doOnStart
import com.android.systemui.res.R
@@ -34,7 +35,7 @@
class ScreenshotAnimationController(private val view: ScreenshotShelfView) {
private var animator: Animator? = null
- private val screenshotPreview = view.requireViewById<View>(R.id.screenshot_preview)
+ private val screenshotPreview = view.requireViewById<ImageView>(R.id.screenshot_preview)
private val flashView = view.requireViewById<View>(R.id.screenshot_flash)
private val actionContainer = view.requireViewById<View>(R.id.actions_container_background)
private val fastOutSlowIn =
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
index f9af4b9..4437bf5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
@@ -17,20 +17,25 @@
package com.android.systemui.screenshot.ui
import android.content.Context
+import android.content.res.Configuration
import android.graphics.Insets
import android.graphics.Rect
import android.graphics.Region
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
+import android.view.ViewGroup
+import android.view.WindowInsets
+import android.widget.FrameLayout
import android.widget.ImageView
-import androidx.constraintlayout.widget.ConstraintLayout
import com.android.systemui.res.R
import com.android.systemui.screenshot.FloatingWindowUtil
+import kotlin.math.max
class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) :
- ConstraintLayout(context, attrs) {
+ FrameLayout(context, attrs) {
lateinit var screenshotPreview: ImageView
+ private lateinit var screenshotStatic: ViewGroup
var onTouchInterceptListener: ((MotionEvent) -> Boolean)? = null
private val displayMetrics = context.resources.displayMetrics
@@ -43,6 +48,7 @@
// Get focus so that the key events go to the layout.
isFocusableInTouchMode = true
screenshotPreview = requireViewById(R.id.screenshot_preview)
+ screenshotStatic = requireViewById(R.id.screenshot_static)
actionsContainerBackground = requireViewById(R.id.actions_container_background)
dismissButton = requireViewById(R.id.screenshot_dismiss_button)
}
@@ -66,6 +72,40 @@
return region
}
+ fun updateInsets(insets: WindowInsets) {
+ val orientation = mContext.resources.configuration.orientation
+ val inPortrait = orientation == Configuration.ORIENTATION_PORTRAIT
+ val p = screenshotStatic.layoutParams as LayoutParams
+ val cutout = insets.displayCutout
+ val navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars())
+ if (cutout == null) {
+ p.setMargins(0, 0, 0, navBarInsets.bottom)
+ } else {
+ val waterfall = cutout.waterfallInsets
+ if (inPortrait) {
+ p.setMargins(
+ waterfall.left,
+ max(cutout.safeInsetTop.toDouble(), waterfall.top.toDouble()).toInt(),
+ waterfall.right,
+ max(
+ cutout.safeInsetBottom.toDouble(),
+ max(navBarInsets.bottom.toDouble(), waterfall.bottom.toDouble())
+ )
+ .toInt()
+ )
+ } else {
+ p.setMargins(
+ max(cutout.safeInsetLeft.toDouble(), waterfall.left.toDouble()).toInt(),
+ waterfall.top,
+ max(cutout.safeInsetRight.toDouble(), waterfall.right.toDouble()).toInt(),
+ max(navBarInsets.bottom.toDouble(), waterfall.bottom.toDouble()).toInt()
+ )
+ }
+ }
+ screenshotStatic.layoutParams = p
+ screenshotStatic.requestLayout()
+ }
+
private fun getSwipeRegion(): Region {
val swipeRegion = Region()
val padding = FloatingWindowUtil.dpToPx(displayMetrics, -1 * TOUCH_PADDING_DP).toInt()
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
index 3376b8c..734a530 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
@@ -16,8 +16,11 @@
package com.android.systemui.screenshot.ui.binder
+import android.graphics.Bitmap
import android.view.LayoutInflater
import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.lifecycle.Lifecycle
@@ -68,7 +71,7 @@
launch {
viewModel.preview.collect { bitmap ->
if (bitmap != null) {
- previewView.setImageBitmap(bitmap)
+ setScreenshotBitmap(previewView, bitmap)
previewView.visibility = View.VISIBLE
previewBorder.visibility = View.VISIBLE
} else {
@@ -128,4 +131,23 @@
}
}
}
+
+ private fun setScreenshotBitmap(screenshotPreview: ImageView, bitmap: Bitmap) {
+ screenshotPreview.setImageBitmap(bitmap)
+ val hasPortraitAspectRatio = bitmap.width < bitmap.height
+ val fixedSize = screenshotPreview.resources.getDimensionPixelSize(R.dimen.overlay_x_scale)
+ val params: ViewGroup.LayoutParams = screenshotPreview.layoutParams
+ if (hasPortraitAspectRatio) {
+ params.width = fixedSize
+ params.height = FrameLayout.LayoutParams.WRAP_CONTENT
+ screenshotPreview.scaleType = ImageView.ScaleType.FIT_START
+ } else {
+ params.width = FrameLayout.LayoutParams.WRAP_CONTENT
+ params.height = fixedSize
+ screenshotPreview.scaleType = ImageView.ScaleType.FIT_END
+ }
+
+ screenshotPreview.layoutParams = params
+ screenshotPreview.requestLayout()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/shared/flag/DualShade.kt b/packages/SystemUI/src/com/android/systemui/shade/shared/flag/DualShade.kt
new file mode 100644
index 0000000..4db4058
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/shared/flag/DualShade.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.shared.flag
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading and using the Dual Shade feature flag. */
+object DualShade {
+
+ /** The aconfig flag name. */
+ const val FLAG_NAME = Flags.FLAG_DUAL_SHADE
+
+ /** The flag description -- not an aconfig flag name. */
+ const val DESCRIPTION = "DualShadeFlag"
+
+ /** A token used for dependency declaration. */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Whether the feature is enabled. */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.dualShade()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, DESCRIPTION)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, DESCRIPTION)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
index 0dcbe9b2..229bdce 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
@@ -26,8 +26,9 @@
import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.annotation.WorkerThread;
-import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.DumpUtilsKt;
import com.android.systemui.util.annotations.WeaklyReferencedCallback;
@@ -118,7 +119,7 @@
private final Intent mServiceIntent;
private final UserTracker mUserTracker;
private final int mFlags;
- private final Executor mExecutor;
+ private final Executor mBgExecutor;
private final ServiceTransformer<T> mTransformer;
private final ArrayList<WeakReference<Callback<T>>> mCallbacks;
private Optional<Integer> mLastDisconnectReason;
@@ -130,30 +131,34 @@
* Default constructor for {@link ObservableServiceConnection}.
* @param context The context from which the service will be bound with.
* @param serviceIntent The intent to bind service with.
- * @param executor The executor for connection callbacks to be delivered on
+ * @param bgExecutor The executor for connection callbacks to be delivered on
* @param transformer A {@link ServiceTransformer} for transforming the resulting service
* into a desired type.
*/
@Inject
public ObservableServiceConnection(Context context, Intent serviceIntent,
UserTracker userTracker,
- @Main Executor executor,
+ @Background Executor bgExecutor,
ServiceTransformer<T> transformer) {
mContext = context;
mServiceIntent = serviceIntent;
mUserTracker = userTracker;
mFlags = Context.BIND_AUTO_CREATE;
- mExecutor = executor;
+ mBgExecutor = bgExecutor;
mTransformer = transformer;
mCallbacks = new ArrayList<>();
mLastDisconnectReason = Optional.empty();
}
/**
- * Initiate binding to the service.
- * @return {@code true} if initiating binding succeed, {@code false} otherwise.
+ * Initiate binding to the service in the background.
*/
- public boolean bind() {
+ public void bind() {
+ mBgExecutor.execute(this::bindInternal);
+ }
+
+ @WorkerThread
+ private void bindInternal() {
boolean bindResult = false;
try {
bindResult = mContext.bindServiceAsUser(mServiceIntent, this, mFlags,
@@ -166,18 +171,17 @@
if (DEBUG) {
Log.d(TAG, "bind. bound:" + bindResult);
}
- return bindResult;
}
/**
* Disconnect from the service if bound.
*/
public void unbind() {
- onDisconnected(DISCONNECT_REASON_UNBIND);
+ mBgExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_UNBIND));
}
/**
- * Adds a callback for receiving connection updates.
+ * Adds a callback for receiving connection updates. The callback is executed in the background.
* @param callback The {@link Callback} to receive future updates.
*/
public void addCallback(Callback<T> callback) {
@@ -185,7 +189,7 @@
Log.d(TAG, "addCallback:" + callback);
}
- mExecutor.execute(() -> {
+ mBgExecutor.execute(() -> {
final Iterator<WeakReference<Callback<T>>> iterator = mCallbacks.iterator();
while (iterator.hasNext()) {
@@ -210,14 +214,15 @@
* Removes previously added callback from receiving future connection updates.
* @param callback The {@link Callback} to be removed.
*/
- public void removeCallback(Callback callback) {
+ public void removeCallback(Callback<T> callback) {
if (DEBUG) {
Log.d(TAG, "removeCallback:" + callback);
}
- mExecutor.execute(() -> mCallbacks.removeIf(el-> el.get() == callback));
+ mBgExecutor.execute(() -> mCallbacks.removeIf(el-> el.get() == callback));
}
+ @WorkerThread
private void onDisconnected(@DisconnectReason int reason) {
if (DEBUG) {
Log.d(TAG, "onDisconnected:" + reason);
@@ -240,7 +245,7 @@
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
- mExecutor.execute(() -> {
+ mBgExecutor.execute(() -> {
if (DEBUG) {
Log.d(TAG, "onServiceConnected");
}
@@ -268,7 +273,7 @@
final Iterator<WeakReference<Callback<T>>> iterator = mCallbacks.iterator();
while (iterator.hasNext()) {
- final Callback cb = iterator.next().get();
+ final Callback<T> cb = iterator.next().get();
if (cb != null) {
applicator.accept(cb);
} else {
@@ -279,16 +284,16 @@
@Override
public void onServiceDisconnected(ComponentName name) {
- mExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_DISCONNECTED));
+ mBgExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_DISCONNECTED));
}
@Override
public void onBindingDied(ComponentName name) {
- mExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_BINDING_DIED));
+ mBgExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_BINDING_DIED));
}
@Override
public void onNullBinding(ComponentName name) {
- mExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_NULL_BINDING));
+ mBgExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_NULL_BINDING));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
index 5979f3e..64f8246 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
@@ -48,7 +48,7 @@
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final SystemClock mSystemClock;
- private final DelayableExecutor mMainExecutor;
+ private final DelayableExecutor mBgExecutor;
private final int mBaseReconnectDelayMs;
private final int mMaxReconnectAttempts;
private final int mMinConnectionDuration;
@@ -71,8 +71,8 @@
private final Observer.Callback mObserverCallback = () -> initiateConnectionAttempt();
- private final ObservableServiceConnection.Callback mConnectionCallback =
- new ObservableServiceConnection.Callback() {
+ private final ObservableServiceConnection.Callback<T> mConnectionCallback =
+ new ObservableServiceConnection.Callback<>() {
private long mStartTime;
@Override
@@ -95,12 +95,10 @@
}
};
- // TODO: b/326449074 - Ensure the DelayableExecutor is on the correct thread, and update the
- // qualifier (to @Main) or name (to bgExecutor) to be consistent with that.
@Inject
public PersistentConnectionManager(
SystemClock clock,
- @Background DelayableExecutor mainExecutor,
+ @Background DelayableExecutor bgExecutor,
DumpManager dumpManager,
@Named(DUMPSYS_NAME) String dumpsysName,
@Named(SERVICE_CONNECTION) ObservableServiceConnection<T> serviceConnection,
@@ -109,7 +107,7 @@
@Named(MIN_CONNECTION_DURATION_MS) int minConnectionDurationMs,
@Named(OBSERVER) Observer observer) {
mSystemClock = clock;
- mMainExecutor = mainExecutor;
+ mBgExecutor = bgExecutor;
mConnection = serviceConnection;
mObserver = observer;
mDumpManager = dumpManager;
@@ -195,7 +193,7 @@
"scheduling connection attempt in " + reconnectDelayMs + "milliseconds");
}
- mCurrentReconnectCancelable = mMainExecutor.executeDelayed(mConnectRunnable,
+ mCurrentReconnectCancelable = mBgExecutor.executeDelayed(mConnectRunnable,
reconnectDelayMs);
mReconnectAttempts++;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index ee642a6..0386338 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.media.AudioManager
+import android.util.Log
import com.android.internal.logging.UiEventLogger
import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
import com.android.settingslib.volume.shared.model.AudioStream
@@ -144,6 +145,7 @@
if (isMutedOrNoVolume) {
when (audioStream.value) {
AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_off
+ AudioManager.STREAM_BLUETOOTH_SCO -> R.drawable.ic_volume_off
AudioManager.STREAM_VOICE_CALL -> R.drawable.ic_volume_off
AudioManager.STREAM_RING ->
if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
@@ -158,12 +160,18 @@
R.drawable.ic_volume_off
}
AudioManager.STREAM_ALARM -> R.drawable.ic_volume_off
- else -> null
+ else -> {
+ Log.wtf(TAG, "No icon for the stream: $audioStream")
+ R.drawable.ic_volume_off
+ }
}
} else {
iconsByStream[audioStream]
+ ?: run {
+ Log.wtf(TAG, "No icon for the stream: $audioStream")
+ R.drawable.ic_music_note
+ }
}
- ?: error("No icon for the stream: $audioStream")
return Icon.Resource(iconRes, null)
}
@@ -196,4 +204,8 @@
* when using [AudioStream] directly because it expects another type.
*/
class FactoryAudioStreamWrapper(val audioStream: AudioStream)
+
+ private companion object {
+ const val TAG = "AudioStreamSliderViewModel"
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index 77b3040..cfe37ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -67,6 +67,7 @@
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.globalactions.domain.interactor.GlobalActionsInteractor;
import com.android.systemui.kosmos.KosmosJavaAdapter;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.GlobalActions;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.settings.UserTracker;
@@ -114,6 +115,7 @@
private SecureSettings mSecureSettings;
@Mock private Resources mResources;
@Mock private ConfigurationController mConfigurationController;
+ @Mock private ActivityStarter mActivityStarter;
@Mock private UserTracker mUserTracker;
@Mock private KeyguardStateController mKeyguardStateController;
@Mock private UserManager mUserManager;
@@ -173,6 +175,7 @@
mVibratorHelper,
mResources,
mConfigurationController,
+ mActivityStarter,
mUserTracker,
mKeyguardStateController,
mUserManager,
@@ -458,6 +461,18 @@
}
}
+ private static <T> void assertNoItemsOfType(List<T> stuff, Class<? extends T> klass) {
+ for (int i = 0; i < stuff.size(); i++) {
+ assertThat(stuff.get(i)).isNotInstanceOf(klass);
+ }
+ }
+
+ private static <T> void assertOneItemOfType(List<T> stuff, Class<? extends T> klass) {
+ List<?> classes = stuff.stream().map((item) -> item.getClass()).toList();
+ assertThat(classes).containsNoDuplicates();
+ assertThat(classes).contains(klass);
+ }
+
@Test
public void testCreateActionItems_lockdownEnabled_doesShowLockdown() {
mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
@@ -641,6 +656,113 @@
assertThat(mInteractor.isVisible().getValue()).isFalse();
}
+ @Test
+ public void testShouldLogSystemUpdatePress() {
+ GlobalActionsDialogLite.SystemUpdateAction systemUpdateAction =
+ mGlobalActionsDialogLite.new SystemUpdateAction();
+ systemUpdateAction.onPress();
+ verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_SYSTEM_UPDATE_PRESS);
+ }
+
+ @Test
+ public void testCreateActionItems_systemUpdateEnabled_doesShowSystemUpdate() {
+ mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
+ doReturn(5).when(mGlobalActionsDialogLite).getMaxShownPowerItems();
+ doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayEmergency();
+ doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any());
+ doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any());
+ String[] actions = {
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_SYSTEM_UPDATE
+ };
+ doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions();
+ mGlobalActionsDialogLite.createActionItems();
+
+ assertItemsOfType(mGlobalActionsDialogLite.mItems,
+ GlobalActionsDialogLite.EmergencyAction.class,
+ GlobalActionsDialogLite.LockDownAction.class,
+ GlobalActionsDialogLite.ShutDownAction.class,
+ GlobalActionsDialogLite.RestartAction.class,
+ GlobalActionsDialogLite.SystemUpdateAction.class);
+ assertThat(mGlobalActionsDialogLite.mOverflowItems).isEmpty();
+ assertThat(mGlobalActionsDialogLite.mPowerItems).isEmpty();
+ }
+
+ @Test
+ public void testCreateActionItems_systemUpdateDisabled_doesntShowSystemUpdateAction() {
+ mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
+ doReturn(5).when(mGlobalActionsDialogLite).getMaxShownPowerItems();
+ doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayEmergency();
+ doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any());
+ doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any());
+ String[] actions = {
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART
+ };
+ doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions();
+ mGlobalActionsDialogLite.createActionItems();
+
+ assertNoItemsOfType(mGlobalActionsDialogLite.mItems,
+ GlobalActionsDialogLite.SystemUpdateAction.class);
+ assertThat(mGlobalActionsDialogLite.mOverflowItems).isEmpty();
+ assertThat(mGlobalActionsDialogLite.mPowerItems).isEmpty();
+ }
+
+ @Test
+ public void testCreateActionItems_systemUpdateEnabled_locked_showsSystemUpdate() {
+ mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
+ doReturn(5).when(mGlobalActionsDialogLite).getMaxShownPowerItems();
+ doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayEmergency();
+ doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any());
+ String[] actions = {
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_SYSTEM_UPDATE
+ };
+ doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions();
+
+ // Show dialog with keyguard showing
+ mGlobalActionsDialogLite.showOrHideDialog(true, true, null);
+
+ assertOneItemOfType(mGlobalActionsDialogLite.mItems,
+ GlobalActionsDialogLite.SystemUpdateAction.class);
+
+ // Hide dialog
+ mGlobalActionsDialogLite.showOrHideDialog(true, true, null);
+ }
+
+ @Test
+ public void testCreateActionItems_systemUpdateEnabled_notProvisioned_noSystemUpdate() {
+ mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
+ doReturn(5).when(mGlobalActionsDialogLite).getMaxShownPowerItems();
+ doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayEmergency();
+ doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any());
+ String[] actions = {
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_SYSTEM_UPDATE
+ };
+ doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions();
+
+ // Show dialog with keyguard showing
+ mGlobalActionsDialogLite.showOrHideDialog(false, false, null);
+
+ assertNoItemsOfType(mGlobalActionsDialogLite.mItems,
+ GlobalActionsDialogLite.SystemUpdateAction.class);
+
+ // Hide dialog
+ mGlobalActionsDialogLite.showOrHideDialog(false, false, null);
+ }
+
private UserInfo mockCurrentUser(int flags) {
return new UserInfo(10, "A User", flags);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
index e6c259a..f1c97dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
@@ -17,7 +17,6 @@
package com.android.systemui.navigationbar.gestural
import android.os.Handler
-import android.os.Looper
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.HapticFeedbackConstants
@@ -28,6 +27,7 @@
import android.view.ViewConfiguration
import android.view.WindowManager
import androidx.test.filters.SmallTest
+import com.android.internal.jank.Cuj
import com.android.internal.util.LatencyTracker
import com.android.systemui.SysuiTestCase
import com.android.systemui.jank.interactionJankMonitor
@@ -35,6 +35,7 @@
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.testKosmos
+import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -43,6 +44,7 @@
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -55,6 +57,7 @@
}
private val kosmos = testKosmos()
private lateinit var mBackPanelController: BackPanelController
+ private lateinit var systemClock: FakeSystemClock
private lateinit var testableLooper: TestableLooper
private var triggerThreshold: Float = 0.0f
private val touchSlop = ViewConfiguration.get(context).scaledEdgeSlop
@@ -69,12 +72,15 @@
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
+ systemClock = FakeSystemClock()
mBackPanelController =
BackPanelController(
context,
windowManager,
ViewConfiguration.get(context),
- Handler.createAsync(checkNotNull(Looper.myLooper())),
+ Handler.createAsync(testableLooper.looper),
+ systemClock,
vibratorHelper,
configurationController,
latencyTracker,
@@ -83,7 +89,6 @@
mBackPanelController.setLayoutParams(layoutParams)
mBackPanelController.setBackCallback(backCallback)
mBackPanelController.setIsLeftPanel(true)
- testableLooper = TestableLooper.get(this)
triggerThreshold = mBackPanelController.params.staticTriggerThreshold
}
@@ -103,6 +108,7 @@
assertThat(mBackPanelController.currentState)
.isEqualTo(BackPanelController.GestureState.GONE)
+ verify(interactionJankMonitor, never()).begin(any())
}
@Test
@@ -110,23 +116,37 @@
startTouch()
// Move once to cross the touch slop
continueTouch(START_X + touchSlop.toFloat() + 1)
+ assertThat(mBackPanelController.currentState)
+ .isEqualTo(BackPanelController.GestureState.ENTRY)
+ verify(interactionJankMonitor).cancel(Cuj.CUJ_BACK_PANEL_ARROW)
+ verify(interactionJankMonitor)
+ .begin(mBackPanelController.getBackPanelView(), Cuj.CUJ_BACK_PANEL_ARROW)
// Move again to cross the back trigger threshold
continueTouch(START_X + touchSlop + triggerThreshold + 1)
// Wait threshold duration and hold touch past trigger threshold
- Thread.sleep((MAX_DURATION_ENTRY_BEFORE_ACTIVE_ANIMATION + 1).toLong())
+ moveTimeForward((MAX_DURATION_ENTRY_BEFORE_ACTIVE_ANIMATION + 1).toLong())
continueTouch(START_X + touchSlop + triggerThreshold + 1)
assertThat(mBackPanelController.currentState)
.isEqualTo(BackPanelController.GestureState.ACTIVE)
verify(backCallback).setTriggerBack(true)
- testableLooper.moveTimeForward(100)
- testableLooper.processAllMessages()
+ moveTimeForward(100)
verify(vibratorHelper)
.performHapticFeedback(any(), eq(HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE))
finishTouchActionUp(START_X + touchSlop + triggerThreshold + 1)
assertThat(mBackPanelController.currentState)
.isEqualTo(BackPanelController.GestureState.COMMITTED)
verify(backCallback).triggerBack()
+
+ // Because the Handler that is typically used for transitioning the arrow state from
+ // COMMITTED to GONE is used as an animation-end-listener on a SpringAnimation,
+ // there is no way to meaningfully test that the state becomes GONE and that the tracked
+ // jank interaction is ended. So instead, manually trigger the failsafe, which does
+ // the same thing:
+ mBackPanelController.failsafeRunnable.run()
+ assertThat(mBackPanelController.currentState)
+ .isEqualTo(BackPanelController.GestureState.GONE)
+ verify(interactionJankMonitor).end(Cuj.CUJ_BACK_PANEL_ARROW)
}
@Test
@@ -134,19 +154,22 @@
startTouch()
// Move once to cross the touch slop
continueTouch(START_X + touchSlop.toFloat() + 1)
+ assertThat(mBackPanelController.currentState)
+ .isEqualTo(BackPanelController.GestureState.ENTRY)
// Move again to cross the back trigger threshold
continueTouch(
START_X + touchSlop + triggerThreshold -
mBackPanelController.params.deactivationTriggerThreshold
)
// Wait threshold duration and hold touch before trigger threshold
- Thread.sleep((MAX_DURATION_ENTRY_BEFORE_ACTIVE_ANIMATION + 1).toLong())
+ moveTimeForward((MAX_DURATION_ENTRY_BEFORE_ACTIVE_ANIMATION + 1).toLong())
continueTouch(
START_X + touchSlop + triggerThreshold -
mBackPanelController.params.deactivationTriggerThreshold
)
clearInvocations(backCallback)
- Thread.sleep(MIN_DURATION_ACTIVE_BEFORE_INACTIVE_ANIMATION)
+ moveTimeForward(MIN_DURATION_ACTIVE_BEFORE_INACTIVE_ANIMATION)
+
// Move in the opposite direction to cross the deactivation threshold and cancel back
continueTouch(START_X)
@@ -175,4 +198,10 @@
private fun createMotionEvent(action: Int, x: Float, y: Float): MotionEvent {
return MotionEvent.obtain(0L, 0L, action, x, y, 0)
}
+
+ private fun moveTimeForward(millis: Long) {
+ systemClock.advanceTime(millis)
+ testableLooper.moveTimeForward(millis)
+ testableLooper.processAllMessages()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
index 5d34120..8d26c87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
@@ -16,8 +16,6 @@
package com.android.systemui.util.service;
-import static com.google.common.truth.Truth.assertThat;
-
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
@@ -118,6 +116,7 @@
connection.addCallback(mCallback);
mExecutor.runAllReady();
connection.bind();
+ mExecutor.runAllReady();
when(mTransformer.convert(eq(mBinder))).thenReturn(mResult);
@@ -143,8 +142,8 @@
when(mContext.bindServiceAsUser(eq(mIntent), eq(connection), anyInt(),
eq(UserHandle.of(MAIN_USER_ID)))).thenReturn(true);
connection.bind();
+ mExecutor.runAllReady();
connection.onServiceDisconnected(mComponentName);
-
mExecutor.runAllReady();
// Ensure proper disconnect reason reported back
@@ -157,6 +156,7 @@
clearInvocations(mContext);
// Ensure unbind after disconnect has no effect on the connection
connection.unbind();
+ mExecutor.runAllReady();
verify(mContext, never()).unbindService(eq(connection));
}
@@ -197,7 +197,8 @@
// Verify that the exception was caught and that bind returns false, and we properly
// unbind.
- assertThat(connection.bind()).isFalse();
+ connection.bind();
+ mExecutor.runAllReady();
verify(mContext).unbindService(connection);
}
@@ -212,13 +213,15 @@
.thenThrow(new SecurityException());
// Verify that bind returns false and we properly unbind.
- assertThat(connection.bind()).isFalse();
+ connection.bind();
+ mExecutor.runAllReady();
verify(mContext).unbindService(connection);
clearInvocations(mContext);
// Ensure unbind after the failed bind has no effect.
connection.unbind();
+ mExecutor.runAllReady();
verify(mContext, never()).unbindService(eq(connection));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 2017954..56e5e29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -77,10 +77,10 @@
import android.os.UserManager;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
import android.service.dreams.IDreamManager;
import android.service.notification.NotificationListenerService;
import android.service.notification.ZenModeConfig;
-import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.Pair;
import android.util.SparseArray;
@@ -99,46 +99,28 @@
import com.android.launcher3.icons.BubbleIconFactory;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository;
import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.FakeFeatureFlagsClassic;
+import com.android.systemui.flags.SceneContainerFlagParameterizationKt;
import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.keyguard.data.repository.FakeCommandQueue;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
-import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.scene.FakeWindowRootViewComponent;
-import com.android.systemui.scene.data.repository.SceneContainerRepository;
-import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.logger.SceneLogger;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shade.LargeScreenHeaderHelper;
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeWindowLogger;
-import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
-import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl;
-import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.NotificationEntryHelper;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -154,7 +136,6 @@
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProviderTestUtil;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -163,13 +144,10 @@
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
-import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
-import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
import com.android.systemui.util.FakeEventLog;
import com.android.systemui.util.settings.FakeGlobalSettings;
import com.android.systemui.util.settings.SystemSettings;
@@ -207,8 +185,6 @@
import com.android.wm.shell.taskview.TaskViewTransitions;
import com.android.wm.shell.transition.Transitions;
-import kotlinx.coroutines.test.TestScope;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -227,8 +203,11 @@
import java.util.Optional;
import java.util.concurrent.Executor;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(ParameterizedAndroidJunit4.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class BubblesTest extends SysuiTestCase {
@Mock
@@ -355,11 +334,8 @@
private Icon mAppBubbleIcon;
@Mock
private Display mDefaultDisplay;
- @Mock
- private LargeScreenHeaderHelper mLargeScreenHeaderHelper;
private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
- private final TestScope mTestScope = mKosmos.getTestScope();
private ShadeInteractor mShadeInteractor;
private ShellTaskOrganizer mShellTaskOrganizer;
private TaskViewTransitions mTaskViewTransitions;
@@ -376,8 +352,16 @@
private UserHandle mUser0;
private FakeBubbleProperties mBubbleProperties;
- private FromLockscreenTransitionInteractor mFromLockscreenTransitionInteractor;
- private FromPrimaryBouncerTransitionInteractor mFromPrimaryBouncerTransitionInteractor;
+
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag();
+ }
+
+ public BubblesTest(FlagsParameterization flags) {
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
@Before
public void setUp() throws Exception {
@@ -402,77 +386,14 @@
FakeDeviceProvisioningRepository deviceProvisioningRepository =
- new FakeDeviceProvisioningRepository();
+ mKosmos.getFakeDeviceProvisioningRepository();
deviceProvisioningRepository.setDeviceProvisioned(true);
- FakeKeyguardRepository keyguardRepository = new FakeKeyguardRepository();
- FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
- FakeShadeRepository shadeRepository = new FakeShadeRepository();
- FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
-
- PowerInteractor powerInteractor = new PowerInteractor(
- mKosmos.getPowerRepository(),
- mKosmos.getFalsingCollector(),
- mock(ScreenOffAnimationController.class),
- mStatusBarStateController);
-
- SceneInteractor sceneInteractor = new SceneInteractor(
- mTestScope.getBackgroundScope(),
- new SceneContainerRepository(
- mTestScope.getBackgroundScope(),
- mKosmos.getFakeSceneContainerConfig(),
- mKosmos.getSceneDataSource()),
- mock(SceneLogger.class),
- mKosmos.getDeviceUnlockedInteractor());
-
- KeyguardTransitionInteractor keyguardTransitionInteractor =
- mKosmos.getKeyguardTransitionInteractor();
- KeyguardInteractor keyguardInteractor = new KeyguardInteractor(
- keyguardRepository,
- new FakeCommandQueue(),
- powerInteractor,
- new FakeKeyguardBouncerRepository(),
- new ConfigurationInteractor(configurationRepository),
- shadeRepository,
- keyguardTransitionInteractor,
- () -> sceneInteractor,
- () -> mKosmos.getFromGoneTransitionInteractor(),
- () -> mKosmos.getSharedNotificationContainerInteractor(),
- mTestScope);
-
- mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
- mFromPrimaryBouncerTransitionInteractor =
- mKosmos.getFromPrimaryBouncerTransitionInteractor();
-
- ResourcesSplitShadeStateController splitShadeStateController =
- new ResourcesSplitShadeStateController();
DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor =
mock(DeviceEntryUdfpsInteractor.class);
when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(MutableStateFlow(false));
- mShadeInteractor =
- new ShadeInteractorImpl(
- mTestScope.getBackgroundScope(),
- mKosmos.getDeviceProvisioningInteractor(),
- new FakeDisableFlagsRepository(),
- mDozeParameters,
- keyguardRepository,
- keyguardTransitionInteractor,
- powerInteractor,
- new FakeUserSetupRepository(),
- mock(UserSwitcherInteractor.class),
- new ShadeInteractorLegacyImpl(
- mTestScope.getBackgroundScope(), keyguardRepository,
- new SharedNotificationContainerInteractor(
- configurationRepository,
- mContext,
- splitShadeStateController,
- keyguardInteractor,
- deviceEntryUdfpsInteractor,
- () -> mLargeScreenHeaderHelper),
- shadeRepository
- )
- );
+ mShadeInteractor = mKosmos.getShadeInteractor();
mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(
mContext,
@@ -2467,6 +2388,10 @@
mStateChangeCalls++;
mLastUpdate = update;
}
+
+ @Override
+ public void animateBubbleBarLocation(BubbleBarLocation location) {
+ }
}
private static class FakeBubbleProperties implements BubbleProperties {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
index 16d08dd..fff3b14 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
@@ -32,7 +32,7 @@
val Kosmos.scenes by Fixture { fakeScenes }
val Kosmos.initialSceneKey by Fixture { Scenes.Lockscreen }
-val Kosmos.sceneContainerConfig by Fixture {
+var Kosmos.sceneContainerConfig by Fixture {
val navigationDistances =
mapOf(
Scenes.Gone to 0,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerStartableKosmos.kt
index c0f5039..ebe591b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerStartableKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerStartableKosmos.kt
@@ -37,7 +37,7 @@
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.notificationShadeWindowController
-import com.android.systemui.statusbar.phone.centralSurfaces
+import com.android.systemui.statusbar.phone.centralSurfacesOptional
import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
val Kosmos.sceneContainerStartable by Fixture {
@@ -58,7 +58,7 @@
authenticationInteractor = { authenticationInteractor },
windowController = notificationShadeWindowController,
deviceProvisioningInteractor = deviceProvisioningInteractor,
- centralSurfaces = centralSurfaces,
+ centralSurfacesOptLazy = { centralSurfacesOptional },
headsUpInteractor = headsUpNotificationInteractor,
occlusionInteractor = sceneContainerOcclusionInteractor,
faceUnlockInteractor = deviceEntryFaceAuthInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/CentralSurfacesKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/CentralSurfacesKosmos.kt
index 1611f62..f71bf03 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/CentralSurfacesKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/CentralSurfacesKosmos.kt
@@ -19,5 +19,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.util.mockito.mock
+import java.util.Optional
+var Kosmos.centralSurfacesOptional by Fixture { Optional.of(centralSurfaces) }
val Kosmos.centralSurfaces by Fixture { mock<CentralSurfaces>() }
diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING
index e77f846f..f6885e1 100644
--- a/ravenwood/TEST_MAPPING
+++ b/ravenwood/TEST_MAPPING
@@ -1,11 +1,18 @@
+// Keep the following two TEST_MAPPINGs in sync:
+// frameworks/base/ravenwood/TEST_MAPPING
+// frameworks/base/tools/hoststubgen/TEST_MAPPING
{
"presubmit": [
+ { "name": "tiny-framework-dump-test" },
+ { "name": "hoststubgentest" },
+ { "name": "hoststubgen-invoke-test" },
{
"name": "RavenwoodMockitoTest_device"
},
{
"name": "RavenwoodBivalentTest_device"
},
+ // The sysui tests should match vendor/unbundled_google/packages/SystemUIGoogle/TEST_MAPPING
{
"name": "SystemUIGoogleTests",
"options": [
@@ -18,6 +25,19 @@
]
}
],
+ "presubmit-large": [
+ {
+ "name": "SystemUITests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
+ ],
"ravenwood-presubmit": [
{
"name": "RavenwoodMinimumTest",
diff --git a/ravenwood/scripts/ravenwood-stats-collector.sh b/ravenwood/scripts/ravenwood-stats-collector.sh
index b5843d0..beacde2 100755
--- a/ravenwood/scripts/ravenwood-stats-collector.sh
+++ b/ravenwood/scripts/ravenwood-stats-collector.sh
@@ -39,14 +39,18 @@
local jar=$1
local file=$2
- sed -e '1d' -e "s/^/$jar,/" $file
+ # Use sed to remove the header + prepend the jar filename.
+ sed -e '1d' -e "s/^/$jar,/" $file
}
collect_stats() {
local out="$1"
{
- echo 'Jar,PackageName,ClassName,SupportedMethods,TotalMethods'
- dump "framework-minus-apex" hoststubgen_framework-minus-apex_stats.csv
+ # Copy the header, with the first column appended.
+ echo -n "Jar,"
+ head -n 1 hoststubgen_framework-minus-apex_stats.csv
+
+ dump "framework-minus-apex" hoststubgen_framework-minus-apex_stats.csv
dump "service.core" hoststubgen_services.core_stats.csv
} > "$out"
@@ -56,7 +60,10 @@
collect_apis() {
local out="$1"
{
- echo 'Jar,PackageName,ClassName,MethodName,Descriptor'
+ # Copy the header, with the first column appended.
+ echo -n "Jar,"
+ head -n 1 hoststubgen_framework-minus-apex_apis.csv
+
dump "framework-minus-apex" hoststubgen_framework-minus-apex_apis.csv
dump "service.core" hoststubgen_services.core_apis.csv
} > "$out"
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 2d1aba4..bc83a0e 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -2691,7 +2691,7 @@
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_RADIO_POWER_STATE_CHANGED)
- && idMatch(r, subId, phoneId)) {
+ && idMatchRelaxed(r, subId, phoneId)) {
try {
r.callback.onRadioPowerStateChanged(state);
} catch (RemoteException ex) {
@@ -4089,6 +4089,45 @@
}
}
+ /**
+ * Match the sub id or phone id of the event to the record with relaxed rules
+ *
+ * We follow the rules below:
+ * 1) If sub id of the event is invalid, phone id should be used.
+ * 2) If record's phoneId is also invalid then allow phone 0 notifications
+ * 3) The event on default sub should be notified to the records
+ * which register the default sub id.
+ * 4) Sub id should be exactly matched for all other cases.
+ * TODO: b/337878785 for longterm fix
+ */
+ boolean idMatchRelaxed(Record r, int subId, int phoneId) {
+ if (!Flags.useRelaxedIdMatch()) {
+ return idMatch(r, subId, phoneId);
+ }
+
+ if (subId < 0) {
+ // Invalid case, we need compare phoneId.
+ // If the record does not have a valid phone Id send phone 0 notifications.
+ // A record's phoneId can get invalid if there is no SIM or modem was restarting
+ // when caller registered.
+ if (r.phoneId == INVALID_SIM_SLOT_INDEX) {
+ return (phoneId == 0);
+ } else {
+ return (r.phoneId == phoneId);
+ }
+ }
+
+ if (r.subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
+ // if the registered record does not have a valid phoneId then use the phone 0
+ if (r.phoneId == INVALID_SIM_SLOT_INDEX) {
+ return (phoneId == 0);
+ }
+ return (subId == mDefaultSubId);
+ } else {
+ return (r.subId == subId);
+ }
+ }
+
private boolean checkFineLocationAccess(Record r) {
return checkFineLocationAccess(r, Build.VERSION_CODES.BASE);
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 23891d2..ec0d897 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -3864,10 +3864,12 @@
final long lastTopTime = sr.app.mState.getLastTopTime();
final long constantTimeLimit = getTimeLimitForFgsType(fgsType);
final long nowUptime = SystemClock.uptimeMillis();
- if (constantTimeLimit > (nowUptime - lastTopTime)) {
+ if (lastTopTime != Long.MIN_VALUE && constantTimeLimit > (nowUptime - lastTopTime)) {
+ // Discard any other messages for this service
+ mFGSAnrTimer.discard(sr);
+ mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
// The app was in the TOP state after the FGS was started so its time allowance
// should be counted from that time since this is considered a user interaction
- mFGSAnrTimer.discard(sr);
final Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
mAm.mHandler.sendMessageAtTime(msg, lastTopTime + constantTimeLimit);
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index b703076..c6c1f98 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -155,9 +155,6 @@
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
@@ -225,9 +222,18 @@
private static final int USER_SWITCH_CALLBACKS_TIMEOUT_MS = 5 * 1000;
/**
+ * Amount of time waited for
+ * {@link ActivityTaskManagerInternal.ScreenObserver#onKeyguardStateChanged} callbacks to be
+ * called after calling {@link WindowManagerService#lockDeviceNow}.
+ * Otherwise, we should throw a {@link RuntimeException} and never dismiss the
+ * {@link UserSwitchingDialog}.
+ */
+ static final int SHOW_KEYGUARD_TIMEOUT_MS = 20 * 1000;
+
+ /**
* Amount of time waited for {@link WindowManagerService#dismissKeyguard} callbacks to be
* called after dismissing the keyguard.
- * Otherwise, we should move on to dismiss the dialog {@link #dismissUserSwitchDialog()}
+ * Otherwise, we should move on to dismiss the dialog {@link #dismissUserSwitchDialog}}
* and report user switch is complete {@link #REPORT_USER_SWITCH_COMPLETE_MSG}.
*/
private static final int DISMISS_KEYGUARD_TIMEOUT_MS = 2 * 1000;
@@ -1925,15 +1931,8 @@
updateProfileRelatedCaches();
mInjector.getWindowManager().setCurrentUser(userId);
mInjector.reportCurWakefulnessUsageEvent();
- // Once the internal notion of the active user has switched, we lock the device
- // with the option to show the user switcher on the keyguard.
if (userSwitchUiEnabled) {
mInjector.getWindowManager().setSwitchingUser(true);
- // Only lock if the user has a secure keyguard PIN/Pattern/Pwd
- if (mInjector.getKeyguardManager().isDeviceSecure(userId)) {
- // Make sure the device is locked before moving on with the user switch
- mInjector.lockDeviceNowAndWaitForKeyguardShown();
- }
}
} else {
@@ -2516,32 +2515,54 @@
@VisibleForTesting
void completeUserSwitch(int oldUserId, int newUserId) {
- final boolean isUserSwitchUiEnabled = isUserSwitchUiEnabled();
- // serialize each conditional step
- await(
- // STEP 1 - If there is no challenge set, dismiss the keyguard right away
- isUserSwitchUiEnabled && !mInjector.getKeyguardManager().isDeviceSecure(newUserId),
- mInjector::dismissKeyguard,
- () -> await(
- // STEP 2 - If user switch ui was enabled, dismiss user switch dialog
- isUserSwitchUiEnabled,
- this::dismissUserSwitchDialog,
- () -> {
- // STEP 3 - Send REPORT_USER_SWITCH_COMPLETE_MSG to broadcast
- // ACTION_USER_SWITCHED & call UserSwitchObservers.onUserSwitchComplete
- mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
- mHandler.sendMessage(mHandler.obtainMessage(
- REPORT_USER_SWITCH_COMPLETE_MSG, oldUserId, newUserId));
- }
- ));
+ final Runnable sendUserSwitchCompleteMessage = () -> {
+ mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
+ mHandler.sendMessage(mHandler.obtainMessage(
+ REPORT_USER_SWITCH_COMPLETE_MSG, oldUserId, newUserId));
+ };
+ if (isUserSwitchUiEnabled()) {
+ if (mInjector.getKeyguardManager().isDeviceSecure(newUserId)) {
+ this.showKeyguard(() -> dismissUserSwitchDialog(sendUserSwitchCompleteMessage));
+ } else {
+ this.dismissKeyguard(() -> dismissUserSwitchDialog(sendUserSwitchCompleteMessage));
+ }
+ } else {
+ sendUserSwitchCompleteMessage.run();
+ }
}
- private void await(boolean condition, Consumer<Runnable> conditionalStep, Runnable nextStep) {
- if (condition) {
- conditionalStep.accept(nextStep);
- } else {
- nextStep.run();
- }
+ protected void showKeyguard(Runnable runnable) {
+ runWithTimeout(mInjector::showKeyguard, SHOW_KEYGUARD_TIMEOUT_MS, runnable, () -> {
+ throw new RuntimeException(
+ "Keyguard is not shown in " + SHOW_KEYGUARD_TIMEOUT_MS + " ms.");
+ }, "showKeyguard");
+ }
+
+ protected void dismissKeyguard(Runnable runnable) {
+ runWithTimeout(mInjector::dismissKeyguard, DISMISS_KEYGUARD_TIMEOUT_MS, runnable, runnable,
+ "dismissKeyguard");
+ }
+
+ private void runWithTimeout(Consumer<Runnable> task, int timeoutMs, Runnable onSuccess,
+ Runnable onTimeout, String traceMsg) {
+ final AtomicInteger state = new AtomicInteger(0); // state = 0 (RUNNING)
+
+ asyncTraceBegin(traceMsg, 0);
+
+ mHandler.postDelayed(() -> {
+ if (state.compareAndSet(0, 1)) { // state = 1 (TIMEOUT)
+ asyncTraceEnd(traceMsg, 0);
+ Slogf.w(TAG, "Timeout: %s did not finish in %d ms", traceMsg, timeoutMs);
+ onTimeout.run();
+ }
+ }, timeoutMs);
+
+ task.accept(() -> {
+ if (state.compareAndSet(0, 2)) { // state = 2 (SUCCESS)
+ asyncTraceEnd(traceMsg, 0);
+ onSuccess.run();
+ }
+ });
}
private void moveUserToForeground(UserState uss, int newUserId) {
@@ -3977,29 +3998,45 @@
return IStorageManager.Stub.asInterface(ServiceManager.getService("mount"));
}
- protected void dismissKeyguard(Runnable runnable) {
- final AtomicBoolean isFirst = new AtomicBoolean(true);
- final Runnable runOnce = () -> {
- if (isFirst.getAndSet(false)) {
- runnable.run();
- }
- };
+ protected void showKeyguard(Runnable runnable) {
+ if (getWindowManager().isKeyguardLocked()) {
+ runnable.run();
+ return;
+ }
+ getActivityTaskManagerInternal().registerScreenObserver(
+ new ActivityTaskManagerInternal.ScreenObserver() {
+ @Override
+ public void onAwakeStateChanged(boolean isAwake) {
- mHandler.postDelayed(runOnce, DISMISS_KEYGUARD_TIMEOUT_MS);
+ }
+
+ @Override
+ public void onKeyguardStateChanged(boolean isShowing) {
+ if (isShowing) {
+ getActivityTaskManagerInternal().unregisterScreenObserver(this);
+ runnable.run();
+ }
+ }
+ }
+ );
+ getWindowManager().lockDeviceNow();
+ }
+
+ protected void dismissKeyguard(Runnable runnable) {
getWindowManager().dismissKeyguard(new IKeyguardDismissCallback.Stub() {
@Override
public void onDismissError() throws RemoteException {
- mHandler.post(runOnce);
+ runnable.run();
}
@Override
public void onDismissSucceeded() throws RemoteException {
- mHandler.post(runOnce);
+ runnable.run();
}
@Override
public void onDismissCancelled() throws RemoteException {
- mHandler.post(runOnce);
+ runnable.run();
}
}, /* message= */ null);
}
@@ -4025,43 +4062,5 @@
void onSystemUserVisibilityChanged(boolean visible) {
getUserManagerInternal().onSystemUserVisibilityChanged(visible);
}
-
- void lockDeviceNowAndWaitForKeyguardShown() {
- if (getWindowManager().isKeyguardLocked()) {
- return;
- }
-
- final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
- t.traceBegin("lockDeviceNowAndWaitForKeyguardShown");
-
- final CountDownLatch latch = new CountDownLatch(1);
- ActivityTaskManagerInternal.ScreenObserver screenObserver =
- new ActivityTaskManagerInternal.ScreenObserver() {
- @Override
- public void onAwakeStateChanged(boolean isAwake) {
-
- }
-
- @Override
- public void onKeyguardStateChanged(boolean isShowing) {
- if (isShowing) {
- latch.countDown();
- }
- }
- };
-
- getActivityTaskManagerInternal().registerScreenObserver(screenObserver);
- getWindowManager().lockDeviceNow();
- try {
- if (!latch.await(20, TimeUnit.SECONDS)) {
- throw new RuntimeException("Keyguard is not shown in 20 seconds");
- }
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- } finally {
- getActivityTaskManagerInternal().unregisterScreenObserver(screenObserver);
- t.traceEnd();
- }
- }
}
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index e59de6a..798aaee 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -1563,19 +1563,29 @@
private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops,
String persistentDeviceId) {
ArrayList<AppOpsManager.OpEntry> resOps = null;
+ boolean shouldReturnRestrictedAppOps = mContext.checkPermission(
+ Manifest.permission.GET_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid())
+ == PackageManager.PERMISSION_GRANTED;
if (ops == null) {
resOps = new ArrayList<>();
- for (int j=0; j<pkgOps.size(); j++) {
+ for (int j = 0; j < pkgOps.size(); j++) {
Op curOp = pkgOps.valueAt(j);
+ if (opRestrictsRead(curOp.op) && !shouldReturnRestrictedAppOps) {
+ continue;
+ }
resOps.add(getOpEntryForResult(curOp, persistentDeviceId));
}
} else {
- for (int j=0; j<ops.length; j++) {
+ for (int j = 0; j < ops.length; j++) {
Op curOp = pkgOps.get(ops[j]);
if (curOp != null) {
if (resOps == null) {
resOps = new ArrayList<>();
}
+ if (opRestrictsRead(curOp.op) && !shouldReturnRestrictedAppOps) {
+ continue;
+ }
resOps.add(getOpEntryForResult(curOp, persistentDeviceId));
}
}
@@ -4244,10 +4254,21 @@
private void verifyIncomingOp(int op) {
if (op >= 0 && op < AppOpsManager._NUM_OP) {
- // Enforce manage appops permission if it's a restricted read op.
+ // Enforce privileged appops permission if it's a restricted read op.
if (opRestrictsRead(op)) {
- mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
- Binder.getCallingPid(), Binder.getCallingUid(), "verifyIncomingOp");
+ if (!(mContext.checkPermission(Manifest.permission.MANAGE_APPOPS,
+ Binder.getCallingPid(), Binder.getCallingUid())
+ == PackageManager.PERMISSION_GRANTED || mContext.checkPermission(
+ Manifest.permission.GET_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid())
+ == PackageManager.PERMISSION_GRANTED || mContext.checkPermission(
+ Manifest.permission.MANAGE_APP_OPS_MODES,
+ Binder.getCallingPid(), Binder.getCallingUid())
+ == PackageManager.PERMISSION_GRANTED)) {
+ throw new SecurityException("verifyIncomingOp: uid " + Binder.getCallingUid()
+ + " does not have any of {MANAGE_APPOPS, GET_APP_OPS_STATS, "
+ + "MANAGE_APP_OPS_MODES}");
+ }
}
return;
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 77654d4..da528a2 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1772,6 +1772,7 @@
@Override
public void handleMessage(Message msg) {
+ int muteCheckDelayMs = BTA2DP_MUTE_CHECK_DELAY_MS;
switch (msg.what) {
case MSG_RESTORE_DEVICES:
synchronized (mSetModeLock) {
@@ -1870,7 +1871,7 @@
btInfo.mDevice, btInfo.mProfile, btInfo.mIsLeOutput,
"MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE");
synchronized (mDeviceStateLock) {
- mDeviceInventory.onBluetoothDeviceConfigChange(btInfo,
+ muteCheckDelayMs += mDeviceInventory.onBluetoothDeviceConfigChange(btInfo,
codecAndChanged.first, codecAndChanged.second,
BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
}
@@ -2060,7 +2061,7 @@
// Give some time to Bluetooth service to post a connection message
// in case of active device switch
if (MESSAGES_MUTE_MUSIC.contains(msg.what)) {
- sendMsg(MSG_CHECK_MUTE_MUSIC, SENDMSG_REPLACE, BTA2DP_MUTE_CHECK_DELAY_MS);
+ sendMsg(MSG_CHECK_MUTE_MUSIC, SENDMSG_REPLACE, muteCheckDelayMs);
}
if (isMessageHandledUnderWakelock(msg.what)) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 9bdc51e..c9612ca 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -864,9 +864,25 @@
}
}
+ // Additional delay added to the music mute duration when a codec config change is executed.
+ static final int BT_CONFIG_CHANGE_MUTE_DELAY_MS = 500;
+ /**
+ * Handles a Bluetooth link codec configuration change communicated by the Bluetooth stack.
+ * Called when either A2DP or LE Audio codec encoding or sampling rate changes:
+ * the change is communicated to native audio policy to eventually reconfigure the audio
+ * path.
+ * Also used to notify a change in preferred mode (duplex or output) for Bluetooth profiles.
+ *
+ * @param btInfo contains all information on the Bluetooth device and profile
+ * @param codec the requested audio encoding (e.g SBC)
+ * @param codecChanged true if a codec parameter changed, false for preferred mode change
+ * @param event currently only EVENT_DEVICE_CONFIG_CHANGE
+ * @return an optional additional delay in milliseconds to add to the music mute period in
+ * case of an actual codec reconfiguration.
+ */
@GuardedBy("mDeviceBroker.mDeviceStateLock")
- /*package*/ void onBluetoothDeviceConfigChange(
+ /*package*/ int onBluetoothDeviceConfigChange(
@NonNull AudioDeviceBroker.BtDeviceInfo btInfo,
@AudioSystem.AudioFormatNativeEnumForBtCodec int codec,
boolean codecChanged, int event) {
@@ -874,10 +890,11 @@
+ "onBluetoothDeviceConfigChange")
.set(MediaMetrics.Property.EVENT, BtHelper.deviceEventToString(event));
+ int delayMs = 0;
final BluetoothDevice btDevice = btInfo.mDevice;
if (btDevice == null) {
mmi.set(MediaMetrics.Property.EARLY_RETURN, "btDevice null").record();
- return;
+ return delayMs;
}
if (AudioService.DEBUG_DEVICES) {
Log.d(TAG, "onBluetoothDeviceConfigChange btDevice=" + btDevice);
@@ -899,7 +916,7 @@
.printSlog(EventLogger.Event.ALOGI, TAG));
mmi.set(MediaMetrics.Property.EARLY_RETURN, "A2dp config change ignored")
.record();
- return;
+ return delayMs;
}
final String key = DeviceInfo.makeDeviceListKey(
AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
@@ -907,7 +924,7 @@
if (di == null) {
Log.e(TAG, "invalid null DeviceInfo in onBluetoothDeviceConfigChange");
mmi.set(MediaMetrics.Property.EARLY_RETURN, "null DeviceInfo").record();
- return;
+ return delayMs;
}
mmi.set(MediaMetrics.Property.ADDRESS, address)
@@ -915,7 +932,6 @@
.set(MediaMetrics.Property.INDEX, volume)
.set(MediaMetrics.Property.NAME, di.mDeviceName);
-
if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) {
if (btInfo.mProfile == BluetoothProfile.A2DP
|| btInfo.mProfile == BluetoothProfile.LE_AUDIO
@@ -943,6 +959,7 @@
+ address
+ " codec=" + AudioSystem.audioFormatToString(codec))
.printSlog(EventLogger.Event.ALOGI, TAG));
+ delayMs = BT_CONFIG_CHANGE_MUTE_DELAY_MS;
}
}
}
@@ -952,6 +969,7 @@
}
}
mmi.record();
+ return delayMs;
}
/*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
diff --git a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
index e330ed5..030ce12 100644
--- a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
+++ b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
@@ -22,7 +22,10 @@
import static android.media.AudioManager.ADJUST_UNMUTE;
import android.content.Context;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceVolumeManager;
import android.media.AudioManager;
+import android.media.VolumeInfo;
import android.os.ShellCommand;
import java.io.PrintWriter;
@@ -58,8 +61,12 @@
return getSoundDoseValue();
case "reset-sound-dose-timeout":
return resetSoundDoseTimeout();
+ case "set-ringer-mode":
+ return setRingerMode();
case "set-volume":
return setVolume();
+ case "set-device-volume":
+ return setDeviceVolume();
case "adj-mute":
return adjMute();
case "adj-unmute":
@@ -95,8 +102,12 @@
pw.println(" Returns the current sound dose value");
pw.println(" reset-sound-dose-timeout");
pw.println(" Resets the sound dose timeout used for momentary exposure");
+ pw.println(" set-ringer-mode NORMAL|SILENT|VIBRATE");
+ pw.println(" Sets the Ringer mode to one of NORMAL|SILENT|VIBRATE");
pw.println(" set-volume STREAM_TYPE VOLUME_INDEX");
pw.println(" Sets the volume for STREAM_TYPE to VOLUME_INDEX");
+ pw.println(" set-device-volume STREAM_TYPE VOLUME_INDEX NATIVE_DEVICE_TYPE");
+ pw.println(" Sets for NATIVE_DEVICE_TYPE the STREAM_TYPE volume to VOLUME_INDEX");
pw.println(" adj-mute STREAM_TYPE");
pw.println(" mutes the STREAM_TYPE");
pw.println(" adj-unmute STREAM_TYPE");
@@ -143,6 +154,34 @@
return 0;
}
+ private int setRingerMode() {
+ String ringerModeText = getNextArg();
+ if (ringerModeText == null) {
+ getErrPrintWriter().println("Error: no ringer mode specified");
+ return 1;
+ }
+
+ final int ringerMode = getRingerMode(ringerModeText);
+ if (!AudioManager.isValidRingerMode(ringerMode)) {
+ getErrPrintWriter().println(
+ "Error: invalid value of ringerMode, should be one of NORMAL|SILENT|VIBRATE");
+ return 1;
+ }
+
+ final AudioManager am = mService.mContext.getSystemService(AudioManager.class);
+ am.setRingerModeInternal(ringerMode);
+ return 0;
+ }
+
+ private int getRingerMode(String ringerModeText) {
+ return switch (ringerModeText) {
+ case "NORMAL" -> AudioManager.RINGER_MODE_NORMAL;
+ case "VIBRATE" -> AudioManager.RINGER_MODE_VIBRATE;
+ case "SILENT" -> AudioManager.RINGER_MODE_SILENT;
+ default -> -1;
+ };
+ }
+
private int getIsSurroundFormatEnabled() {
String surroundFormatText = getNextArg();
@@ -257,6 +296,23 @@
return 0;
}
+ private int setDeviceVolume() {
+ final Context context = mService.mContext;
+ final AudioDeviceVolumeManager advm = (AudioDeviceVolumeManager) context.getSystemService(
+ Context.AUDIO_DEVICE_VOLUME_SERVICE);
+ final int stream = readIntArg();
+ final int index = readIntArg();
+ final int device = readIntArg();
+
+ final VolumeInfo volume = new VolumeInfo.Builder(stream).setVolumeIndex(index).build();
+ final AudioDeviceAttributes ada = new AudioDeviceAttributes(
+ /*native type*/ device, /*address*/ "foo");
+ getOutPrintWriter().println(
+ "calling AudioDeviceVolumeManager.setDeviceVolume(" + volume + ", " + ada + ")");
+ advm.setDeviceVolume(volume, ada);
+ return 0;
+ }
+
private int adjMute() {
final Context context = mService.mContext;
final AudioManager am = context.getSystemService(AudioManager.class);
diff --git a/services/core/java/com/android/server/input/InputShellCommand.java b/services/core/java/com/android/server/input/InputShellCommand.java
index 138186b..4c5a3c2 100644
--- a/services/core/java/com/android/server/input/InputShellCommand.java
+++ b/services/core/java/com/android/server/input/InputShellCommand.java
@@ -333,8 +333,8 @@
out.println();
out.println("The commands and default sources are:");
out.println(" text <string> (Default: keyboard)");
- out.println(" keyevent [--longpress|--doubletap|--async"
- + "|--delay <duration between keycodes in ms>]"
+ out.println(" keyevent [--longpress|--duration <duration to hold key down in ms>]"
+ + " [--doubletap] [--async] [--delay <duration between keycodes in ms>]"
+ " <key code number or name> ..."
+ " (Default: keyboard)");
out.println(" tap <x> <y> (Default: touchscreen)");
@@ -402,6 +402,7 @@
boolean async = false;
boolean doubleTap = false;
long delayMs = 0;
+ long durationMs = 0;
String arg = getNextArgRequired();
do {
@@ -411,9 +412,21 @@
doubleTap = (doubleTap || arg.equals("--doubletap"));
if (arg.equals("--delay")) {
delayMs = Long.parseLong(getNextArgRequired());
+ } else if (arg.equals("--duration")) {
+ durationMs = Long.parseLong(getNextArgRequired());
}
} while ((arg = getNextArg()) != null);
+ if (durationMs > 0 && longPress) {
+ getErrPrintWriter().println(
+ "--duration and --longpress cannot be used at the same time.");
+ throw new IllegalArgumentException(
+ "keyevent args should only contain either durationMs or longPress");
+ }
+ if (longPress) {
+ durationMs = ViewConfiguration.getLongPressTimeout();
+ }
+
boolean firstInput = true;
do {
if (!firstInput && delayMs > 0) {
@@ -422,16 +435,17 @@
firstInput = false;
final int keyCode = KeyEvent.keyCodeFromString(arg);
- sendKeyEvent(inputSource, keyCode, longPress, displayId, async);
+ sendKeyEvent(inputSource, keyCode, durationMs, displayId, async);
if (doubleTap) {
sleep(ViewConfiguration.getDoubleTapMinTime());
- sendKeyEvent(inputSource, keyCode, longPress, displayId, async);
+ sendKeyEvent(inputSource, keyCode, durationMs, displayId, async);
}
} while ((arg = getNextArg()) != null);
}
private void sendKeyEvent(
- int inputSource, int keyCode, boolean longPress, int displayId, boolean async) {
+ int inputSource, int keyCode, long durationMs, int displayId,
+ boolean async) {
final long now = SystemClock.uptimeMillis();
KeyEvent event = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0 /* repeatCount */,
@@ -440,13 +454,23 @@
event.setDisplayId(displayId);
injectKeyEvent(event, async);
- if (longPress) {
- sleep(ViewConfiguration.getLongPressTimeout());
- // Some long press behavior would check the event time, we set a new event time here.
- final long nextEventTime = now + ViewConfiguration.getLongPressTimeout();
- KeyEvent longPressEvent = KeyEvent.changeTimeRepeat(
- event, nextEventTime, 1 /* repeatCount */, KeyEvent.FLAG_LONG_PRESS);
- injectKeyEvent(longPressEvent, async);
+ long firstSleepDurationMs = Math.min(durationMs, ViewConfiguration.getLongPressTimeout());
+ if (firstSleepDurationMs > 0) {
+ sleep(firstSleepDurationMs);
+ // Send FLAG_LONG_PRESS right after `longPressTimeout`, and resume sleep if needed.
+ if (durationMs >= ViewConfiguration.getLongPressTimeout()) {
+ // Some long press behavior would check the event time, we set a new event time
+ // here.
+ final long nextEventTime = now + ViewConfiguration.getLongPressTimeout();
+ KeyEvent longPressEvent = KeyEvent.changeTimeRepeat(event, nextEventTime,
+ 1 /* repeatCount */, KeyEvent.FLAG_LONG_PRESS);
+ injectKeyEvent(longPressEvent, async);
+
+ long secondSleepDurationMs = durationMs - firstSleepDurationMs;
+ if (secondSleepDurationMs > 0) {
+ sleep(secondSleepDurationMs);
+ }
+ }
}
injectKeyEvent(KeyEvent.changeAction(event, KeyEvent.ACTION_UP), async);
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index 8002300..880787e 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -203,7 +203,7 @@
SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class);
if (subManager != null) {
- if (Flags.subscriptionsListenerThread()) {
+ if (Flags.subscriptionsChangedListenerThread()) {
subManager.addOnSubscriptionsChangedListener(FgThread.getExecutor(),
mOnSubscriptionsChangeListener);
} else {
diff --git a/services/core/java/com/android/server/net/TEST_MAPPING b/services/core/java/com/android/server/net/TEST_MAPPING
index 4fc1a17..ad6b0ca 100644
--- a/services/core/java/com/android/server/net/TEST_MAPPING
+++ b/services/core/java/com/android/server/net/TEST_MAPPING
@@ -1,7 +1,7 @@
{
"presubmit-large": [
{
- "name": "CtsHostsideNetworkTests",
+ "name": "CtsHostsideNetworkPolicyTests",
"options": [
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 4747689..143bc5c 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1725,7 +1725,28 @@
synchronized (mConfigLock) {
if (policy == null || mConfig == null) return;
final ZenModeConfig newConfig = mConfig.copy();
- newConfig.applyNotificationPolicy(policy);
+ if (Flags.modesApi() && !Flags.modesUi()) {
+ // Fix for b/337193321 -- propagate changes to notificationPolicy to rules where
+ // the user cannot edit zen policy to emulate the previous "inheritance".
+ ZenPolicy previousPolicy = ZenAdapters.notificationPolicyToZenPolicy(
+ newConfig.toNotificationPolicy());
+ ZenPolicy newPolicy = ZenAdapters.notificationPolicyToZenPolicy(policy);
+
+ newConfig.applyNotificationPolicy(policy);
+
+ if (!previousPolicy.equals(newPolicy)) {
+ for (ZenRule rule : newConfig.automaticRules.values()) {
+ if (!SystemZenRules.isSystemOwnedRule(rule)
+ && rule.zenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
+ && (rule.zenPolicy == null || rule.zenPolicy.equals(previousPolicy)
+ || rule.zenPolicy.equals(getDefaultZenPolicy()))) {
+ rule.zenPolicy = newPolicy;
+ }
+ }
+ }
+ } else {
+ newConfig.applyNotificationPolicy(policy);
+ }
setConfigLocked(newConfig, null, origin, "setNotificationPolicy", callingUid);
}
}
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index c8fd7e4..8a85328 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -19,6 +19,7 @@
import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
import android.Manifest;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.AppOpsManager;
@@ -68,6 +69,7 @@
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.OptionalInt;
@@ -335,14 +337,22 @@
}
static class Injector {
+ class RoleManagerWrapper {
+ List<String> getRoleHolders(@NonNull String roleName) {
+ return mContext.getSystemService(RoleManager.class).getRoleHolders(roleName);
+ }
+ }
+
Context mContext;
ArraySet<String> mAllowlistedPackages;
AtomicFile mMappingFile;
+ RoleManagerWrapper mRoleManagerWrapper;
Injector(Context context, ArraySet<String> allowlistedPackages, AtomicFile mappingFile) {
mContext = context;
mAllowlistedPackages = allowlistedPackages;
mMappingFile = mappingFile;
+ mRoleManagerWrapper = new RoleManagerWrapper();
}
Context getContext() {
@@ -368,6 +378,10 @@
void setSystemProperty(String key, String value) {
SystemProperties.set(key, value);
}
+
+ RoleManagerWrapper getRoleManagerWrapper() {
+ return mRoleManagerWrapper;
+ }
}
BugreportManagerServiceImpl(Context context) {
@@ -546,7 +560,7 @@
if (!allowlisted) {
final long token = Binder.clearCallingIdentity();
try {
- allowlisted = mContext.getSystemService(RoleManager.class).getRoleHolders(
+ allowlisted = mInjector.getRoleManagerWrapper().getRoleHolders(
ROLE_SYSTEM_AUTOMOTIVE_PROJECTION).contains(callingPackage);
} finally {
Binder.restoreCallingIdentity(token);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 0f4e482..ae485ed 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3984,6 +3984,8 @@
// packageName -> list of components to send broadcasts now
final ArrayMap<String, ArrayList<String>> sendNowBroadcasts = new ArrayMap<>(targetSize);
+ final List<PackageMetrics.ComponentStateMetrics> componentStateMetricsList =
+ new ArrayList<PackageMetrics.ComponentStateMetrics>();
synchronized (mLock) {
Computer computer = snapshotComputer();
boolean scheduleBroadcastMessage = false;
@@ -3997,11 +3999,17 @@
// update enabled settings
final ComponentEnabledSetting setting = settings.get(i);
final String packageName = setting.getPackageName();
- if (!setEnabledSettingInternalLocked(computer, pkgSettings.get(packageName),
- setting, userId, callingPackage)) {
+ final PackageSetting packageSetting = pkgSettings.get(packageName);
+ final PackageMetrics.ComponentStateMetrics componentStateMetrics =
+ new PackageMetrics.ComponentStateMetrics(setting,
+ UserHandle.getUid(userId, packageSetting.getAppId()),
+ packageSetting.getEnabled(userId));
+ if (!setEnabledSettingInternalLocked(computer, packageSetting, setting, userId,
+ callingPackage)) {
continue;
}
anyChanged = true;
+ componentStateMetricsList.add(componentStateMetrics);
if ((setting.getEnabledFlags() & PackageManager.SYNCHRONOUS) != 0) {
isSynchronous = true;
@@ -4029,6 +4037,9 @@
return;
}
+ // Log the metrics when the component state is changed.
+ PackageMetrics.reportComponentStateChanged(computer, componentStateMetricsList, userId);
+
if (isSynchronous) {
flushPackageRestrictionsAsUserInternalLocked(userId);
} else {
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index a0b6897..20598f9 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -19,12 +19,21 @@
import static android.os.Process.INVALID_UID;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.admin.SecurityLog;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.content.pm.Flags;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.content.pm.parsing.ApkLiteParseUtils;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.Pair;
+import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.util.FrameworkStatsLog;
@@ -41,12 +50,14 @@
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
/**
* Metrics class for reporting stats to logging infrastructures like statsd
*/
final class PackageMetrics {
+ private static final String TAG = "PackageMetrics";
public static final int STEP_PREPARE = 1;
public static final int STEP_SCAN = 2;
public static final int STEP_RECONCILE = 3;
@@ -344,4 +355,76 @@
SecurityLog.writeEvent(SecurityLog.TAG_PACKAGE_UNINSTALLED, packageName, versionCode,
userId);
}
+
+ public static class ComponentStateMetrics {
+ public int mUid;
+ public int mComponentOldState;
+ public int mComponentNewState;
+ public boolean mIsForWholeApp;
+ @NonNull private String mPackageName;
+ @Nullable private String mClassName;
+
+ ComponentStateMetrics(@NonNull PackageManager.ComponentEnabledSetting setting, int uid,
+ int componentOldState) {
+ mUid = uid;
+ mComponentOldState = componentOldState;
+ mComponentNewState = setting.getEnabledState();
+ mIsForWholeApp = !setting.isComponent();
+ mPackageName = setting.getPackageName();
+ mClassName = setting.getClassName();
+ }
+
+ public boolean isSameComponent(ActivityInfo activityInfo) {
+ if (activityInfo == null) {
+ return false;
+ }
+ return mIsForWholeApp ? TextUtils.equals(activityInfo.packageName, mPackageName)
+ : activityInfo.getComponentName().equals(
+ new ComponentName(mPackageName, mClassName));
+ }
+ }
+
+ public static void reportComponentStateChanged(@NonNull Computer computer,
+ List<ComponentStateMetrics> componentStateMetricsList, @UserIdInt int userId) {
+ if (!Flags.componentStateChangedMetrics()) {
+ return;
+ }
+ if (componentStateMetricsList == null || componentStateMetricsList.isEmpty()) {
+ Slog.d(TAG, "Fail to report component state due to metrics is empty");
+ return;
+ }
+ boolean isLauncher = false;
+ final List<ResolveInfo> resolveInfosForLauncher = getHomeActivitiesResolveInfoAsUser(
+ computer, userId);
+ final int resolveInfosForLauncherSize =
+ resolveInfosForLauncher != null ? resolveInfosForLauncher.size() : 0;
+ final int metricsSize = componentStateMetricsList.size();
+ for (int i = 0; i < metricsSize; i++) {
+ final ComponentStateMetrics componentStateMetrics = componentStateMetricsList.get(i);
+ for (int j = 0; j < resolveInfosForLauncherSize; j++) {
+ ResolveInfo resolveInfo = resolveInfosForLauncher.get(j);
+ if (componentStateMetrics.isSameComponent(resolveInfo.activityInfo)) {
+ isLauncher = true;
+ break;
+ }
+ }
+ reportComponentStateChanged(componentStateMetrics.mUid,
+ componentStateMetrics.mComponentOldState,
+ componentStateMetrics.mComponentNewState,
+ isLauncher,
+ componentStateMetrics.mIsForWholeApp);
+ }
+ }
+
+ private static void reportComponentStateChanged(int uid, int componentOldState,
+ int componentNewState, boolean isLauncher, boolean isForWholeApp) {
+ FrameworkStatsLog.write(FrameworkStatsLog.COMPONENT_STATE_CHANGED_REPORTED,
+ uid, componentOldState, componentNewState, isLauncher, isForWholeApp);
+ }
+
+ private static List<ResolveInfo> getHomeActivitiesResolveInfoAsUser(@NonNull Computer computer,
+ @UserIdInt int userId) {
+ return computer.queryIntentActivitiesInternal(computer.getHomeIntent(), /* resolvedType */
+ null, /* flags */ 0, userId);
+ }
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 5bd845f..3303367 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2167,7 +2167,7 @@
// We don't need to start a new activity, and the client said not to do anything
// if that is the case, so this is it! And for paranoia, make sure we have
// correctly resumed the top activity.
- if (!mMovedToFront && mDoResume && !avoidMoveToFront()) {
+ if (!mMovedToFront && mDoResume) {
ProtoLog.d(WM_DEBUG_TASKS, "Bring to front target: %s from %s", mTargetRootTask,
targetTaskTop);
mTargetRootTask.moveToFront("intentActivityFound");
@@ -2196,7 +2196,7 @@
if (mMovedToFront) {
// We moved the task to front, use starting window to hide initial drawn delay.
targetTaskTop.showStartingWindow(true /* taskSwitch */);
- } else if (mDoResume && !avoidMoveToFront()) {
+ } else if (mDoResume) {
// Make sure the root task and its belonging display are moved to topmost.
mTargetRootTask.moveToFront("intentActivityFound");
}
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 90a53ee..c9703d8 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -303,8 +303,7 @@
removedWindowContainer = currentTask;
backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
final ActivityRecord ar = prevTask.getTopNonFinishingActivity();
- mShowWallpaper =
- ar != null && ar.forAllWindows(WindowState::hasWallpaper, true);
+ mShowWallpaper = ar != null && ar.hasWallpaper();
} else {
// If it reaches the top activity, we will check the below task from parent.
// If it's null or multi-window and has different parent task, fallback the type
diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
index d56b503..1c59977 100644
--- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
@@ -111,19 +111,7 @@
}
if (phase == PHASE_WINDOWING_MODE) {
- return RESULT_DONE;
- }
-
- // TODO(b/336998072) - Find a better solution to this that makes use of the logic from
- // TaskLaunchParamsModifier. Put logic in common utils, return RESULT_CONTINUE, inherit
- // from parent class, etc.
- if (outParams.mPreferredTaskDisplayArea == null && task.getRootTask() != null) {
- appendLog("display-from-task=" + task.getRootTask().getDisplayId());
- outParams.mPreferredTaskDisplayArea = task.getRootTask().getDisplayArea();
- }
-
- if (phase == PHASE_DISPLAY_AREA) {
- return RESULT_DONE;
+ return RESULT_CONTINUE;
}
if (!currentParams.mBounds.isEmpty()) {
@@ -135,7 +123,7 @@
appendLog("setting desktop mode task bounds to %s", outParams.mBounds);
- return RESULT_DONE;
+ return RESULT_CONTINUE;
}
/**
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 6458eac..d555f1a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -666,15 +666,6 @@
DELEGATION_CERT_SELECTION,
});
- /**
- * System property whose value indicates whether the device is fully owned by an organization:
- * it can be either a device owner device, or a device with an organization-owned managed
- * profile.
- *
- * <p>The state is stored as a Boolean string.
- */
- private static final String PROPERTY_ORGANIZATION_OWNED = "ro.organization_owned";
-
private static final int STATUS_BAR_DISABLE_MASK =
StatusBarManager.DISABLE_EXPAND |
StatusBarManager.DISABLE_NOTIFICATION_ICONS |
@@ -2356,7 +2347,6 @@
void loadOwners() {
synchronized (getLockObject()) {
mOwners.load();
- setDeviceOwnershipSystemPropertyLocked();
if (mOwners.hasDeviceOwner()) {
setGlobalSettingDeviceOwnerType(
mOwners.getDeviceOwnerType(mOwners.getDeviceOwnerPackageName()));
@@ -2720,27 +2710,6 @@
+ defaultRestrictions);
}
- private void setDeviceOwnershipSystemPropertyLocked() {
- final boolean deviceProvisioned =
- mInjector.settingsGlobalGetInt(Settings.Global.DEVICE_PROVISIONED, 0) != 0;
- final boolean hasDeviceOwner = mOwners.hasDeviceOwner();
- final boolean hasOrgOwnedProfile = isOrganizationOwnedDeviceWithManagedProfile();
- // If the device is not provisioned and there is currently no management, do not set the
- // read-only system property yet, since device owner / org-owned profile may still be
- // provisioned.
- if (!hasDeviceOwner && !hasOrgOwnedProfile && !deviceProvisioned) {
- return;
- }
- final String value = Boolean.toString(hasDeviceOwner || hasOrgOwnedProfile);
- final String currentVal = mInjector.systemPropertiesGet(PROPERTY_ORGANIZATION_OWNED, null);
- if (TextUtils.isEmpty(currentVal)) {
- Slogf.i(LOG_TAG, "Set ro.organization_owned property to " + value);
- mInjector.systemPropertiesSet(PROPERTY_ORGANIZATION_OWNED, value);
- } else if (!value.equals(currentVal)) {
- Slogf.w(LOG_TAG, "Cannot change existing ro.organization_owned to " + value);
- }
- }
-
private void maybeStartSecurityLogMonitorOnActivityManagerReady() {
if (!mInjector.securityLogIsLoggingEnabled()) {
return;
@@ -9447,7 +9416,6 @@
mOwners.setDeviceOwner(admin, userId);
mOwners.writeDeviceOwner();
- setDeviceOwnershipSystemPropertyLocked();
//TODO(b/180371154): when provisionFullyManagedDevice is used in tests, remove this
// hard-coded default value setting.
@@ -15303,8 +15271,6 @@
private class SetupContentObserver extends ContentObserver {
private final Uri mUserSetupComplete = Settings.Secure.getUriFor(
Settings.Secure.USER_SETUP_COMPLETE);
- private final Uri mDeviceProvisioned = Settings.Global.getUriFor(
- Settings.Global.DEVICE_PROVISIONED);
private final Uri mPaired = Settings.Secure.getUriFor(Settings.Secure.DEVICE_PAIRED);
private final Uri mDefaultImeChanged = Settings.Secure.getUriFor(
Settings.Secure.DEFAULT_INPUT_METHOD);
@@ -15318,7 +15284,6 @@
void register() {
mInjector.registerContentObserver(mUserSetupComplete, false, this, UserHandle.USER_ALL);
- mInjector.registerContentObserver(mDeviceProvisioned, false, this, UserHandle.USER_ALL);
if (mIsWatch) {
mInjector.registerContentObserver(mPaired, false, this, UserHandle.USER_ALL);
}
@@ -15334,12 +15299,6 @@
public void onChange(boolean selfChange, Uri uri, int userId) {
if (mUserSetupComplete.equals(uri) || (mIsWatch && mPaired.equals(uri))) {
updateUserSetupCompleteAndPaired();
- } else if (mDeviceProvisioned.equals(uri)) {
- synchronized (getLockObject()) {
- // Set PROPERTY_DEVICE_OWNER_PRESENT, for the SUW case where setting the property
- // is delayed until device is marked as provisioned.
- setDeviceOwnershipSystemPropertyLocked();
- }
} else if (mDefaultImeChanged.equals(uri)) {
synchronized (getLockObject()) {
if (mUserIdsWithPendingChangesByOwner.contains(userId)) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index a0d9be54..eeb4976 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -16,6 +16,9 @@
package com.android.server.devicepolicy;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -185,6 +188,9 @@
}
}
+ // TODO: when a local policy exists for a user, this callback will be invoked for this user
+ // individually as well as for USER_ALL. This can be optimized by separating local and global
+ // enforcement in the policy engine.
static boolean setUserControlDisabledPackages(
@Nullable Set<String> packages, Context context, int userId, PolicyKey policyKey) {
Binder.withCleanCallingIdentity(() -> {
@@ -201,20 +207,35 @@
return;
}
final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
- for (var pkg : packages) {
- final var appInfo = pmi.getApplicationInfo(pkg,
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
- Process.myUid(), userId);
- if (appInfo != null) {
- DevicePolicyManagerService.setBgUsageAppOp(appOpsManager, appInfo);
- }
- }
+ resolveUsers(userId).forEach(
+ user -> setBgUsageAppOp(packages, pmi, user, appOpsManager));
}
});
return true;
}
+ /** Handles USER_ALL expanding it into the list of all intact users. */
+ private static List<Integer> resolveUsers(int userId) {
+ if (userId == UserHandle.USER_ALL) {
+ UserManagerInternal userManager = LocalServices.getService(UserManagerInternal.class);
+ return userManager.getUsers(/* excludeDying= */ true)
+ .stream().map(ui -> ui.id).toList();
+ } else {
+ return List.of(userId);
+ }
+ }
+
+ private static void setBgUsageAppOp(Set<String> packages, PackageManagerInternal pmi,
+ int userId, AppOpsManager appOpsManager) {
+ for (var pkg : packages) {
+ int packageFlags = MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;
+ final var appInfo = pmi.getApplicationInfo(pkg, packageFlags, Process.myUid(), userId);
+ if (appInfo != null) {
+ DevicePolicyManagerService.setBgUsageAppOp(appOpsManager, appInfo);
+ }
+ }
+ }
+
static boolean addPersistentPreferredActivity(
@Nullable ComponentName preferredActivity, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
index 23314cd..1322545 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
@@ -20,12 +20,17 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.content.res.TypedArray;
import android.os.Looper;
import android.platform.test.annotations.EnableFlags;
import android.service.dreams.DreamService;
@@ -41,6 +46,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -83,6 +89,18 @@
assertThat(metadata.dreamCategory).isEqualTo(DreamService.DREAM_CATEGORY_DEFAULT);
}
+ @Test
+ public void testMetadataParsing_exceptionReading() {
+ final PackageManager packageManager = Mockito.mock(PackageManager.class);
+ final ServiceInfo serviceInfo = Mockito.mock(ServiceInfo.class);
+ final TypedArray rawMetadata = Mockito.mock(TypedArray.class);
+ when(packageManager.extractPackageItemInfoAttributes(eq(serviceInfo), any(), any(), any()))
+ .thenReturn(rawMetadata);
+ when(rawMetadata.getString(anyInt())).thenThrow(new RuntimeException("failure"));
+
+ assertThat(DreamService.getDreamMetadata(packageManager, serviceInfo)).isNull();
+ }
+
private DreamService.DreamMetadata getDreamMetadata(String dreamClassName)
throws PackageManager.NameNotFoundException {
final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index c6f3eb3..30e3b18 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -58,6 +58,7 @@
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doNothing;
@@ -90,6 +91,7 @@
import android.os.Message;
import android.os.PowerManagerInternal;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.IStorageManager;
@@ -203,7 +205,10 @@
doNothing().when(mInjector).activityManagerOnUserStopped(anyInt());
doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt());
doNothing().when(mInjector).taskSupervisorRemoveUser(anyInt());
- doNothing().when(mInjector).lockDeviceNowAndWaitForKeyguardShown();
+ doAnswer(invocation -> {
+ ((Runnable) invocation.getArgument(0)).run();
+ return null;
+ }).when(mInjector).showKeyguard(any());
mockIsUsersOnSecondaryDisplaysEnabled(false);
// All UserController params are set to default.
@@ -540,7 +545,6 @@
expectedCodes.add(REPORT_USER_SWITCH_COMPLETE_MSG);
if (backgroundUserStopping) {
expectedCodes.add(CLEAR_USER_JOURNEY_SESSION_MSG);
- expectedCodes.add(0); // this is for directly posting in stopping.
}
if (expectScheduleBackgroundUserStopping) {
expectedCodes.add(SCHEDULED_STOP_BACKGROUND_USER_MSG);
@@ -1419,21 +1423,13 @@
// mock the device to be secure in order to expect the keyguard to be shown
when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true);
- // call real lockDeviceNowAndWaitForKeyguardShown method for this test
- doCallRealMethod().when(mInjector).lockDeviceNowAndWaitForKeyguardShown();
+ // call real showKeyguard method for this test
+ doCallRealMethod().when(mInjector).showKeyguard(any());
- // call startUser on a thread because we're expecting it to be blocked
- Thread threadStartUser = new Thread(()-> {
- mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
- });
- threadStartUser.start();
+ mUserController.completeUserSwitch(TEST_USER_ID1, TEST_USER_ID2);
- // make sure the switch is stalled...
- Thread.sleep(2000);
- // by checking REPORT_USER_SWITCH_MSG is not sent yet
- assertNull(mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG));
- // and the thread is still alive
- assertTrue(threadStartUser.isAlive());
+ // make sure the switch is stalled by checking the UserSwitchingDialog is not dismissed yet
+ verify(mInjector, never()).dismissUserSwitchingDialog(any());
// mock send the keyguard shown event
ArgumentCaptor<ActivityTaskManagerInternal.ScreenObserver> captor = ArgumentCaptor.forClass(
@@ -1441,12 +1437,42 @@
verify(mInjector.mActivityTaskManagerInternal).registerScreenObserver(captor.capture());
captor.getValue().onKeyguardStateChanged(true);
- // verify the switch now moves on...
- Thread.sleep(1000);
- // by checking REPORT_USER_SWITCH_MSG is sent
- assertNotNull(mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG));
- // and the thread is finished
- assertFalse(threadStartUser.isAlive());
+ // verify the switch now moves on by checking the UserSwitchingDialog is dismissed
+ verify(mInjector, atLeastOnce()).dismissUserSwitchingDialog(any());
+
+ // verify that SHOW_KEYGUARD_TIMEOUT is ignored and does not crash the system
+ try {
+ mInjector.mHandler.processPostDelayedCallbacksWithin(
+ UserController.SHOW_KEYGUARD_TIMEOUT_MS);
+ } catch (RuntimeException e) {
+ throw new AssertionError(
+ "SHOW_KEYGUARD_TIMEOUT is not ignored and crashed the system", e);
+ }
+ }
+
+ @Test
+ public void testRuntimeExceptionIsThrownIfTheKeyguardIsNotShown() throws Exception {
+ // enable user switch ui, because keyguard is only shown then
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
+
+ // mock the device to be secure in order to expect the keyguard to be shown
+ when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true);
+
+ // suppress showKeyguard method for this test
+ doNothing().when(mInjector).showKeyguard(any());
+
+ mUserController.completeUserSwitch(TEST_USER_ID1, TEST_USER_ID2);
+
+ // verify that the system has crashed
+ assertThrows("Should have thrown RuntimeException", RuntimeException.class, () -> {
+ mInjector.mHandler.processPostDelayedCallbacksWithin(
+ UserController.SHOW_KEYGUARD_TIMEOUT_MS);
+ });
+
+ // make sure the UserSwitchingDialog is not dismissed
+ verify(mInjector, never()).dismissUserSwitchingDialog(any());
}
private void setUpAndStartUserInBackground(int userId) throws Exception {
@@ -1793,7 +1819,9 @@
Set<Integer> getMessageCodes() {
Set<Integer> result = new LinkedHashSet<>();
for (Message msg : mMessages) {
- result.add(msg.what);
+ if (msg.what != 0) { // ignore mHandle.post and mHandler.postDelayed messages
+ result.add(msg.what);
+ }
}
return result;
}
@@ -1817,14 +1845,28 @@
@Override
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
+ final Runnable cb = msg.getCallback();
+ if (cb != null && uptimeMillis <= SystemClock.uptimeMillis()) {
+ // run mHandler.post calls immediately
+ cb.run();
+ return true;
+ }
Message copy = new Message();
copy.copyFrom(msg);
+ copy.setCallback(cb);
mMessages.add(copy);
- if (msg.getCallback() != null) {
- msg.getCallback().run();
- msg.setCallback(null);
- }
return super.sendMessageAtTime(msg, uptimeMillis);
}
+
+ public void processPostDelayedCallbacksWithin(long millis) {
+ final long whenMax = SystemClock.uptimeMillis() + millis;
+ for (Message msg : mMessages) {
+ final Runnable cb = msg.getCallback();
+ if (cb != null && msg.getWhen() <= whenMax) {
+ msg.setCallback(null);
+ cb.run();
+ }
+ }
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index a6f2196..9862663 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -16,8 +16,6 @@
package com.android.server.os;
-import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
-
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
@@ -25,9 +23,9 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
+import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager;
import android.app.admin.flags.Flags;
-import android.app.role.RoleManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
@@ -61,6 +59,8 @@
import org.mockito.MockitoAnnotations;
import java.io.FileDescriptor;
+import java.util.Collections;
+import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -104,7 +104,7 @@
ArraySet<String> mAllowlistedPackages = new ArraySet<>();
mAllowlistedPackages.add(mContext.getPackageName());
mInjector = new TestInjector(mContext, mAllowlistedPackages, mMappingFile,
- mMockUserManager, mMockDevicePolicyManager);
+ mMockUserManager, mMockDevicePolicyManager, null);
mService = new BugreportManagerServiceImpl(mInjector);
mBugreportFileManager = new BugreportManagerServiceImpl.BugreportFileManager(mMappingFile);
when(mPackageManager.getPackageUidAsUser(anyString(), anyInt())).thenReturn(mCallingUid);
@@ -114,24 +114,8 @@
@After
public void tearDown() throws Exception {
- // Changes to RoleManager persist between tests, so we need to clear out any funny
- // business we did in previous tests.
+ // Clean up the mapping file between tests since it would otherwise persist.
mMappingFile.delete();
- RoleManager roleManager = mContext.getSystemService(RoleManager.class);
- CallbackFuture future = new CallbackFuture();
- runWithShellPermissionIdentity(
- () -> {
- roleManager.setBypassingRoleQualification(false);
- roleManager.removeRoleHolderAsUser(
- "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION",
- mContext.getPackageName(),
- /* flags= */ 0,
- Process.myUserHandle(),
- mContext.getMainExecutor(),
- future);
- });
-
- assertThat(future.get()).isEqualTo(true);
}
@Test
@@ -267,7 +251,10 @@
@Test
public void testCancelBugreportWithoutRole() {
- clearAllowlist();
+ // Create a new service to clear the allowlist
+ mService = new BugreportManagerServiceImpl(
+ new TestInjector(mContext, new ArraySet<>(), mMappingFile,
+ mMockUserManager, mMockDevicePolicyManager, null));
assertThrows(SecurityException.class, () -> mService.cancelBugreport(
Binder.getCallingUid(), mContext.getPackageName()));
@@ -275,29 +262,13 @@
@Test
public void testCancelBugreportWithRole() throws Exception {
- clearAllowlist();
- RoleManager roleManager = mContext.getSystemService(RoleManager.class);
- CallbackFuture future = new CallbackFuture();
- runWithShellPermissionIdentity(
- () -> {
- roleManager.setBypassingRoleQualification(true);
- roleManager.addRoleHolderAsUser(
- "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION",
- mContext.getPackageName(),
- /* flags= */ 0,
- Process.myUserHandle(),
- mContext.getMainExecutor(),
- future);
- });
-
- assertThat(future.get()).isEqualTo(true);
- mService.cancelBugreport(Binder.getCallingUid(), mContext.getPackageName());
- }
-
- private void clearAllowlist() {
+ // Create a new service to clear the allowlist, but override the role manager
mService = new BugreportManagerServiceImpl(
new TestInjector(mContext, new ArraySet<>(), mMappingFile,
- mMockUserManager, mMockDevicePolicyManager));
+ mMockUserManager, mMockDevicePolicyManager,
+ "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION"));
+
+ mService.cancelBugreport(Binder.getCallingUid(), mContext.getPackageName());
}
private static class Listener implements IDumpstateListener {
@@ -359,10 +330,22 @@
private boolean mBugreportStarted = false;
TestInjector(Context context, ArraySet<String> allowlistedPackages, AtomicFile mappingFile,
- UserManager um, DevicePolicyManager dpm) {
+ UserManager um, DevicePolicyManager dpm, String grantedRole) {
super(context, allowlistedPackages, mappingFile);
mUserManager = um;
mDevicePolicyManager = dpm;
+
+ if (grantedRole != null) {
+ mRoleManagerWrapper =
+ new BugreportManagerServiceImpl.Injector.RoleManagerWrapper() {
+ @Override
+ List<String> getRoleHolders(@NonNull String roleName) {
+ return roleName.equals(grantedRole)
+ ? Collections.singletonList(mContext.getPackageName())
+ : Collections.emptyList();
+ }
+ };
+ }
}
@Override
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 9559a25..5fdb396 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -6095,6 +6095,67 @@
assertThat(readPolicy.allowConversations()).isFalse();
}
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ public void setNotificationPolicy_updatesRulePolicies_ifRulePolicyIsDefaultOrGlobalPolicy() {
+ ZenPolicy defaultZenPolicy = mZenModeHelper.getDefaultZenPolicy();
+ Policy previousManualPolicy = mZenModeHelper.mConfig.toNotificationPolicy();
+ ZenPolicy previousManualZenPolicy = ZenAdapters.notificationPolicyToZenPolicy(
+ previousManualPolicy);
+ ZenPolicy customZenPolicy = new ZenPolicy.Builder(defaultZenPolicy).allowConversations(
+ CONVERSATION_SENDERS_ANYONE).build();
+
+ mZenModeHelper.mConfig.automaticRules.clear();
+ addZenRule(mZenModeHelper.mConfig, "appWithDefault", "app.pkg",
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS, defaultZenPolicy);
+ addZenRule(mZenModeHelper.mConfig, "appWithSameAsManual", "app.pkg",
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS, previousManualZenPolicy);
+ addZenRule(mZenModeHelper.mConfig, "appWithCustom", "app.pkg",
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS, customZenPolicy);
+ addZenRule(mZenModeHelper.mConfig, "appWithOtherFilter", "app.pkg",
+ ZEN_MODE_ALARMS, null);
+ addZenRule(mZenModeHelper.mConfig, "systemWithDefault", "android",
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS, defaultZenPolicy);
+ addZenRule(mZenModeHelper.mConfig, "systemWithSameAsManual", "android",
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS, previousManualZenPolicy);
+
+ Policy newManualPolicy = new Policy(PRIORITY_CATEGORY_EVENTS, 0, 0);
+ mZenModeHelper.setNotificationPolicy(newManualPolicy, UPDATE_ORIGIN_USER, 0);
+ ZenPolicy newManualZenPolicy = ZenAdapters.notificationPolicyToZenPolicy(newManualPolicy);
+
+ // Only app rules with default or same-as-manual policies were updated.
+ assertThat(mZenModeHelper.mConfig.automaticRules.get("appWithDefault").zenPolicy)
+ .isEqualTo(newManualZenPolicy);
+ assertThat(mZenModeHelper.mConfig.automaticRules.get("appWithSameAsManual").zenPolicy)
+ .isEqualTo(newManualZenPolicy);
+
+ assertThat(mZenModeHelper.mConfig.automaticRules.get("appWithCustom").zenPolicy)
+ .isEqualTo(customZenPolicy);
+ assertThat(mZenModeHelper.mConfig.automaticRules.get("appWithOtherFilter").zenPolicy)
+ .isNull();
+ assertThat(mZenModeHelper.mConfig.automaticRules.get("systemWithDefault").zenPolicy)
+ .isEqualTo(defaultZenPolicy);
+ assertThat(mZenModeHelper.mConfig.automaticRules.get("systemWithSameAsManual").zenPolicy)
+ .isEqualTo(previousManualZenPolicy);
+ }
+
+ private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode,
+ @Nullable ZenPolicy zenPolicy) {
+ ZenRule rule = new ZenRule();
+ rule.id = id;
+ rule.pkg = ownerPkg;
+ rule.enabled = true;
+ rule.zenMode = zenMode;
+ rule.zenPolicy = zenPolicy;
+ // Plus stuff so that isValidAutomaticRule() passes
+ rule.name = String.format("Rule %s from %s with mode=%s and policy=%s", id, ownerPkg,
+ zenMode, zenPolicy);
+ rule.conditionId = Uri.parse(rule.name);
+
+ config.automaticRules.put(id, rule);
+ }
+
private static final Correspondence<ZenRule, ZenRule> IGNORE_METADATA =
Correspondence.transforming(zr -> {
Parcel p = Parcel.obtain();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index feef6b1..f92387c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -26,7 +26,6 @@
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE;
-import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_DONE;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;
import static org.junit.Assert.assertEquals;
@@ -97,12 +96,12 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- public void testReturnsDoneIfDesktopWindowingIsEnabledAndUnsupportedDeviceOverridden() {
+ public void testReturnsContinueIfDesktopWindowingIsEnabledAndUnsupportedDeviceOverridden() {
setupDesktopModeLaunchParamsModifier(/*isDesktopModeSupported=*/ true,
/*enforceDeviceRestrictions=*/ false);
final Task task = new TaskBuilder(mSupervisor).build();
- assertEquals(RESULT_DONE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
}
@Test
@@ -135,22 +134,22 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- public void testReturnsDoneIfTaskUsingActivityTypeStandard() {
+ public void testReturnsContinueIfTaskUsingActivityTypeStandard() {
setupDesktopModeLaunchParamsModifier();
final Task task = new TaskBuilder(mSupervisor).setActivityType(
ACTIVITY_TYPE_STANDARD).build();
- assertEquals(RESULT_DONE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- public void testReturnsDoneIfTaskUsingActivityTypeUndefined() {
+ public void testReturnsContinueIfTaskUsingActivityTypeUndefined() {
setupDesktopModeLaunchParamsModifier();
final Task task = new TaskBuilder(mSupervisor).setActivityType(
ACTIVITY_TYPE_UNDEFINED).build();
- assertEquals(RESULT_DONE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
}
@Test
@@ -176,7 +175,7 @@
task.getDisplayArea().setBounds(new Rect(0, 0, displayWidth, displayHeight));
final int desiredWidth = (int) (displayWidth * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
final int desiredHeight = (int) (displayHeight * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
- assertEquals(RESULT_DONE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
assertEquals(desiredWidth, mResult.mBounds.width());
assertEquals(desiredHeight, mResult.mBounds.height());
}
@@ -192,7 +191,7 @@
mCurrent.mPreferredTaskDisplayArea = mockTaskDisplayArea;
mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
- assertEquals(RESULT_DONE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
assertEquals(mockTaskDisplayArea, mResult.mPreferredTaskDisplayArea);
assertEquals(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode);
}
diff --git a/tests/Input/src/com/android/server/input/InputShellCommandTest.java b/tests/Input/src/com/android/server/input/InputShellCommandTest.java
index f4845a5..11f4633 100644
--- a/tests/Input/src/com/android/server/input/InputShellCommandTest.java
+++ b/tests/Input/src/com/android/server/input/InputShellCommandTest.java
@@ -125,6 +125,14 @@
assertThat(mInputEventInjector.mInjectedEvents).isEmpty();
}
+ @Test
+ public void testInvalidKeyEventCommandArgsCombination() {
+ // --duration and --longpress must not be sent together
+ runCommand("keyevent --duration 1000 --longpress KEYCODE_A");
+
+ assertThat(mInputEventInjector.mInjectedEvents).isEmpty();
+ }
+
private InputEvent getSingleInjectedInputEvent() {
assertThat(mInputEventInjector.mInjectedEvents).hasSize(1);
return mInputEventInjector.mInjectedEvents.get(0);
diff --git a/tools/hoststubgen/TEST_MAPPING b/tools/hoststubgen/TEST_MAPPING
index eca258c..f6885e1 100644
--- a/tools/hoststubgen/TEST_MAPPING
+++ b/tools/hoststubgen/TEST_MAPPING
@@ -1,13 +1,63 @@
+// Keep the following two TEST_MAPPINGs in sync:
+// frameworks/base/ravenwood/TEST_MAPPING
+// frameworks/base/tools/hoststubgen/TEST_MAPPING
{
"presubmit": [
{ "name": "tiny-framework-dump-test" },
{ "name": "hoststubgentest" },
- { "name": "hoststubgen-invoke-test" }
+ { "name": "hoststubgen-invoke-test" },
+ {
+ "name": "RavenwoodMockitoTest_device"
+ },
+ {
+ "name": "RavenwoodBivalentTest_device"
+ },
+ // The sysui tests should match vendor/unbundled_google/packages/SystemUIGoogle/TEST_MAPPING
+ {
+ "name": "SystemUIGoogleTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ],
+ "presubmit-large": [
+ {
+ "name": "SystemUITests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
],
"ravenwood-presubmit": [
{
+ "name": "RavenwoodMinimumTest",
+ "host": true
+ },
+ {
+ "name": "RavenwoodMockitoTest",
+ "host": true
+ },
+ {
"name": "CtsUtilTestCasesRavenwood",
"host": true
+ },
+ {
+ "name": "RavenwoodCoreTest",
+ "host": true
+ },
+ {
+ "name": "RavenwoodBivalentTest",
+ "host": true
}
]
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 2f432cc..7212beb 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -16,6 +16,7 @@
package com.android.hoststubgen
import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.dumper.ApiDumper
import com.android.hoststubgen.filters.AnnotationBasedFilter
import com.android.hoststubgen.filters.ClassWidePolicyPropagatingFilter
import com.android.hoststubgen.filters.ConstantFilter
@@ -89,7 +90,11 @@
log.i("Dump file created at $it")
}
options.apiListFile.ifSet {
- PrintWriter(it).use { pw -> stats.dumpApis(pw) }
+ PrintWriter(it).use { pw ->
+ // TODO, when dumping a jar that's not framework-minus-apex.jar, we need to feed
+ // framework-minus-apex.jar so that we can dump inherited methods from it.
+ ApiDumper(pw, allClasses, null, filter).dump()
+ }
log.i("API list file created at $it")
}
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt
index da61469..9045db2 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt
@@ -15,7 +15,8 @@
*/
package com.android.hoststubgen
-import com.android.hoststubgen.asm.toHumanReadableClassName
+import com.android.hoststubgen.asm.getOuterClassNameFromFullClassName
+import com.android.hoststubgen.asm.getPackageNameFromFullClassName
import com.android.hoststubgen.filters.FilterPolicyWithReason
import org.objectweb.asm.Opcodes
import java.io.PrintWriter
@@ -55,8 +56,8 @@
// Ignore methods where policy isn't relevant
if (policy.isIgnoredForStats) return
- val packageName = resolvePackageName(fullClassName)
- val className = resolveOuterClassName(fullClassName)
+ val packageName = getPackageNameFromFullClassName(fullClassName)
+ val className = getOuterClassNameFromFullClassName(fullClassName)
// Ignore methods for certain generated code
if (className.endsWith("Proto")
@@ -88,42 +89,4 @@
}
}
}
-
- fun dumpApis(pw: PrintWriter) {
- pw.printf("PackageName,ClassName,MethodName,MethodDesc\n")
- apis.sortedWith(compareBy({ it.fullClassName }, { it.methodName }, { it.methodDesc }))
- .forEach { api ->
- pw.printf(
- "%s,%s,%s,%s\n",
- csvEscape(resolvePackageName(api.fullClassName)),
- csvEscape(resolveClassName(api.fullClassName)),
- csvEscape(api.methodName),
- csvEscape(api.methodDesc),
- )
- }
- }
-
- private fun resolvePackageName(fullClassName: String): String {
- val start = fullClassName.lastIndexOf('/')
- return fullClassName.substring(0, start).toHumanReadableClassName()
- }
-
- private fun resolveOuterClassName(fullClassName: String): String {
- val start = fullClassName.lastIndexOf('/')
- val end = fullClassName.indexOf('$')
- if (end == -1) {
- return fullClassName.substring(start + 1)
- } else {
- return fullClassName.substring(start + 1, end)
- }
- }
-
- private fun resolveClassName(fullClassName: String): String {
- val pos = fullClassName.lastIndexOf('/')
- if (pos == -1) {
- return fullClassName
- } else {
- return fullClassName.substring(pos + 1)
- }
- }
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
index 83e122f..b8d1800 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
@@ -76,17 +76,45 @@
return null
}
-private val removeLastElement = """[./][^./]*$""".toRegex()
+val periodOrSlash = charArrayOf('.', '/')
-fun getPackageNameFromClassName(className: String): String {
- return className.replace(removeLastElement, "")
+fun getPackageNameFromFullClassName(fullClassName: String): String {
+ val pos = fullClassName.lastIndexOfAny(periodOrSlash)
+ if (pos == -1) {
+ return ""
+ } else {
+ return fullClassName.substring(0, pos)
+ }
}
-fun resolveClassName(className: String, packageName: String): String {
+fun getClassNameFromFullClassName(fullClassName: String): String {
+ val pos = fullClassName.lastIndexOfAny(periodOrSlash)
+ if (pos == -1) {
+ return fullClassName
+ } else {
+ return fullClassName.substring(pos + 1)
+ }
+}
+
+fun getOuterClassNameFromFullClassName(fullClassName: String): String {
+ val start = fullClassName.lastIndexOfAny(periodOrSlash)
+ val end = fullClassName.indexOf('$')
+ if (end == -1) {
+ return fullClassName.substring(start + 1)
+ } else {
+ return fullClassName.substring(start + 1, end)
+ }
+}
+
+/**
+ * If [className] is a fully qualified name, just return it.
+ * Otherwise, prepend [defaultPackageName].
+ */
+fun resolveClassNameWithDefaultPackage(className: String, defaultPackageName: String): String {
if (className.contains('.') || className.contains('/')) {
return className
}
- return "$packageName.$className"
+ return "$defaultPackageName.$className"
}
fun String.toJvmClassName(): String {
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/dumper/ApiDumper.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/dumper/ApiDumper.kt
new file mode 100644
index 0000000..aaefee4
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/dumper/ApiDumper.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.hoststubgen.dumper
+
+import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
+import com.android.hoststubgen.asm.CTOR_NAME
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.getClassNameFromFullClassName
+import com.android.hoststubgen.asm.getPackageNameFromFullClassName
+import com.android.hoststubgen.asm.toHumanReadableClassName
+import com.android.hoststubgen.csvEscape
+import com.android.hoststubgen.filters.FilterPolicy
+import com.android.hoststubgen.filters.FilterPolicyWithReason
+import com.android.hoststubgen.filters.OutputFilter
+import com.android.hoststubgen.log
+import org.objectweb.asm.Type
+import org.objectweb.asm.tree.ClassNode
+import java.io.PrintWriter
+
+/**
+ * Dump all the API methods in [classes], with inherited methods, with their policies.
+ */
+class ApiDumper(
+ val pw: PrintWriter,
+ val classes: ClassNodes,
+ val frameworkClasses: ClassNodes?,
+ val filter: OutputFilter,
+) {
+ private data class MethodKey(
+ val name: String,
+ val descriptor: String,
+ )
+
+ val javaStandardApiPolicy = FilterPolicy.Stub.withReason("Java standard API")
+
+ private val shownMethods = mutableSetOf<MethodKey>()
+
+ /**
+ * Do the dump.
+ */
+ fun dump() {
+ pw.printf("PackageName,ClassName,FromSubclass,DeclareClass,MethodName,MethodDesc" +
+ ",Supported,Policy,Reason\n")
+
+ classes.forEach { classNode ->
+ shownMethods.clear()
+ dump(classNode, classNode)
+ }
+ }
+
+ private fun dumpMethod(
+ classPackage: String,
+ className: String,
+ isSuperClass: Boolean,
+ methodClassName: String,
+ methodName: String,
+ methodDesc: String,
+ policy: FilterPolicyWithReason,
+ ) {
+ pw.printf(
+ "%s,%s,%d,%s,%s,%s,%d,%s,%s\n",
+ csvEscape(classPackage),
+ csvEscape(className),
+ if (isSuperClass) { 1 } else { 0 },
+ csvEscape(methodClassName),
+ csvEscape(methodName),
+ csvEscape(methodDesc),
+ if (policy.policy.isSupported) { 1 } else { 0 },
+ policy.policy,
+ csvEscape(policy.reason),
+ )
+ }
+
+ private fun isDuplicate(methodName: String, methodDesc: String): Boolean {
+ val methodKey = MethodKey(methodName, methodDesc)
+
+ if (shownMethods.contains(methodKey)) {
+ return true
+ }
+ shownMethods.add(methodKey)
+ return false
+ }
+
+ private fun dump(
+ dumpClass: ClassNode,
+ methodClass: ClassNode,
+ ) {
+ val pkg = getPackageNameFromFullClassName(dumpClass.name).toHumanReadableClassName()
+ val cls = getClassNameFromFullClassName(dumpClass.name).toHumanReadableClassName()
+
+ val isSuperClass = dumpClass != methodClass
+
+ methodClass.methods?.sortedWith(compareBy({ it.name }, { it.desc }))?.forEach { method ->
+
+ // Don't print ctor's from super classes.
+ if (isSuperClass) {
+ if (CTOR_NAME == method.name || CLASS_INITIALIZER_NAME == method.name) {
+ return@forEach
+ }
+ }
+ // If we already printed the method from a subclass, don't print it.
+ if (isDuplicate(method.name, method.desc)) {
+ return@forEach
+ }
+
+ val policy = filter.getPolicyForMethod(methodClass.name, method.name, method.desc)
+
+ // Let's skip "Remove" APIs. Ideally we want to print it, just to make the CSV
+ // complete, we still need to hide methods substituted (== @RavenwoodReplace) methods
+ // and for now we don't have an easy way to detect it.
+ if (policy.policy == FilterPolicy.Remove) {
+ return@forEach
+ }
+
+ val renameTo = filter.getRenameTo(methodClass.name, method.name, method.desc)
+
+ dumpMethod(pkg, cls, isSuperClass, methodClass.name.toHumanReadableClassName(),
+ renameTo ?: method.name, method.desc, policy)
+ }
+
+ // Dump super class methods.
+ dumpSuper(dumpClass, methodClass.superName)
+
+ // Dump interface methods (which may have default methods).
+ methodClass.interfaces?.sorted()?.forEach { interfaceName ->
+ dumpSuper(dumpClass, interfaceName)
+ }
+ }
+
+ /**
+ * Dump a given super class / interface.
+ */
+ private fun dumpSuper(
+ dumpClass: ClassNode,
+ methodClassName: String,
+ ) {
+ classes.findClass(methodClassName)?.let { methodClass ->
+ dump(dumpClass, methodClass)
+ return
+ }
+ frameworkClasses?.findClass(methodClassName)?.let { methodClass ->
+ dump(dumpClass, methodClass)
+ return
+ }
+ if (methodClassName.startsWith("java/") ||
+ methodClassName.startsWith("javax/")
+ ) {
+ dumpStandardClass(dumpClass, methodClassName)
+ return
+ }
+ log.w("Super class or interface $methodClassName (used by ${dumpClass.name}) not found.")
+ }
+
+ /**
+ * Dump methods from Java standard classes.
+ */
+ private fun dumpStandardClass(
+ dumpClass: ClassNode,
+ methodClassName: String,
+ ) {
+ val pkg = getPackageNameFromFullClassName(dumpClass.name).toHumanReadableClassName()
+ val cls = getClassNameFromFullClassName(dumpClass.name).toHumanReadableClassName()
+
+ val methodClassName = methodClassName.toHumanReadableClassName()
+
+ try {
+ val clazz = Class.forName(methodClassName)
+
+ // Method.getMethods() returns only public methods, but with inherited ones.
+ // Method.getDeclaredMethods() returns private methods too, but no inherited methods.
+ //
+ // Since we're only interested in public ones, just use getMethods().
+ clazz.methods.forEach { method ->
+ val methodName = method.name
+ val methodDesc = Type.getMethodDescriptor(method)
+
+ // If we already printed the method from a subclass, don't print it.
+ if (isDuplicate(methodName, methodDesc)) {
+ return@forEach
+ }
+
+ dumpMethod(pkg, cls, true, methodClassName,
+ methodName, methodDesc, javaStandardApiPolicy)
+ }
+ } catch (e: ClassNotFoundException) {
+ log.w("JVM type $methodClassName (used by ${dumpClass.name}) not found.")
+ }
+ }
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
index 45e140c..6643492 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
@@ -20,8 +20,8 @@
import com.android.hoststubgen.LogLevel
import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.asm.UnifiedVisitor
-import com.android.hoststubgen.asm.getPackageNameFromClassName
-import com.android.hoststubgen.asm.resolveClassName
+import com.android.hoststubgen.asm.getPackageNameFromFullClassName
+import com.android.hoststubgen.asm.resolveClassNameWithDefaultPackage
import com.android.hoststubgen.asm.toJvmClassName
import com.android.hoststubgen.filters.FilterPolicy
import com.android.hoststubgen.filters.FilterPolicyWithReason
@@ -89,7 +89,7 @@
) {
super.visit(version, access, name, signature, superName, interfaces)
currentClassName = name
- currentPackageName = getPackageNameFromClassName(name)
+ currentPackageName = getPackageNameFromFullClassName(name)
classPolicy = filter.getPolicyForClass(currentClassName)
log.d("[%s] visit: %s (package: %s)", this.javaClass.simpleName, name, currentPackageName)
@@ -98,7 +98,8 @@
log.indent()
filter.getNativeSubstitutionClass(currentClassName)?.let { className ->
- val fullClassName = resolveClassName(className, currentPackageName).toJvmClassName()
+ val fullClassName = resolveClassNameWithDefaultPackage(className, currentPackageName)
+ .toJvmClassName()
log.d(" NativeSubstitutionClass: $fullClassName")
if (classes.findClass(fullClassName) == null) {
log.w("Native substitution class $fullClassName not found. Class must be " +