diff options
160 files changed, 3657 insertions, 1436 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index fa1127843840..a42adadcbeed 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -173,6 +173,11 @@ java_aconfig_library { 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 e649485ed5e5..e82df1203137 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 2f2a7656e680..53cf7d59f974 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -451,12 +451,12 @@ package android { 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 @@ package android { 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 @@ package android { 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 @@ package android { 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 @@ package android { 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 @@ package android { 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 @@ package android { 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 @@ package android { 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 @@ package android { 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 @@ package android { 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 @@ package android { 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 @@ package android { 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 @@ package android { 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 @@ package android { 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 @@ package android { 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 @@ package android { 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 @@ package android { 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 @@ package android { 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 e12da637c2eb..f10c0fc21455 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -437,10 +437,10 @@ package android { 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 @@ package android { 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 5268ec42e21c..a0f0ccaec58c 100644 --- a/core/java/android/app/AppOpInfo.java +++ b/core/java/android/app/AppOpInfo.java @@ -88,7 +88,7 @@ class AppOpInfo { /** * 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 8e766c95b2e8..20b2357e967d 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -3265,7 +3265,7 @@ public class AppOpsManager { } /** - * 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 f1e44cc267d6..1df8f63aa402 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -1104,6 +1104,10 @@ public final class LoadedApk { 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 4d7e29b19771..05a2aece789f 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -9770,6 +9770,12 @@ public class Notification implements Parcelable * 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 f092945a5d28..9b3fb5c73415 100644 --- a/core/java/android/app/Service.java +++ b/core/java/android/app/Service.java @@ -1197,6 +1197,24 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac /** * 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/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 31c9a258da56..18914e120d52 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -275,13 +275,23 @@ flag { } flag { - name: "headless_single_user_bad_device_admin_state_fix" - namespace: "enterprise" - description: "Fix the bad state in DPMS caused by an earlier bug related to the headless single user change" - bug: "332477138" - metadata { - purpose: PURPOSE_BUGFIX - } + name: "headless_single_user_bad_device_admin_state_fix" + namespace: "enterprise" + description: "Fix the bad state in DPMS caused by an earlier bug related to the headless single user change" + bug: "332477138" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "onboarding_bugreport_storage_bug_fix" + namespace: "enterprise" + description: "Add a separate storage limit for deferred bugreports" + bug: "330177040" + metadata { + purpose: PURPOSE_BUGFIX + } } flag { diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index 6158917cc7df..205f1e9c1f5c 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -240,3 +240,11 @@ flag { bug: "297603927" is_fixed_read_only: true } + +flag { + name: "component_state_changed_metrics" + namespace: "package_manager_service" + description: "Feature flag to log the metrics when the component state is changed." + bug: "316916801" + is_fixed_read_only: true +} diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index c6a8762e4d79..342479bc159e 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -5062,21 +5062,29 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri /** * <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 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <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> - * </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> + * <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</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;">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;">JPEG/JPEG_R</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;">PRIV</td> * <td style="text-align: center;">S1080P</td> - * <td style="text-align: center;">JPEG</td> + * <td style="text-align: center;">JPEG/JPEG_R</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;">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> + * <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;">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> + * <td style="text-align: center;">S720P</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;">S1440P</td> - * <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;">S720P</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;">YUV</td> - * <td style="text-align: center;">S1080P</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;">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;">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 eb644e8cfa01..dfbf06b20e2c 100644 --- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java @@ -917,8 +917,11 @@ public final class CameraExtensionCharacteristics { * 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 @@ public final class CameraExtensionCharacteristics { * * @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 @@ public final class CameraExtensionCharacteristics { } 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 @@ public final class CameraExtensionCharacteristics { } 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 875550aea5f5..a10e2505758e 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.os.RemoteException; 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 @@ public class CameraExtensionJpegProcessor implements ICaptureProcessorImpl { 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 class CameraExtensionJpegProcessor implements ICaptureProcessorImpl { } 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 class CameraExtensionJpegProcessor implements ICaptureProcessorImpl { 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 class CameraExtensionJpegProcessor implements ICaptureProcessorImpl { } 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 @@ public class CameraExtensionJpegProcessor implements ICaptureProcessorImpl { 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 c00e6101b363..3ae319999e35 100644 --- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java @@ -390,7 +390,16 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { 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 f0c6e2e4e123..40f047732c06 100644 --- a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java +++ b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java @@ -112,19 +112,30 @@ public final class CameraExtensionUtils { 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 72ab970b4b80..e6ddf3556490 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1736,6 +1736,24 @@ public final class Settings { "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 997c958187fe..5f6bdbf193b9 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -1383,16 +1383,22 @@ public class DreamService extends Service implements Window.Callback { 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/service/dreams/flags.aconfig b/core/java/android/service/dreams/flags.aconfig index f87cb85f94b7..a42eaff68917 100644 --- a/core/java/android/service/dreams/flags.aconfig +++ b/core/java/android/service/dreams/flags.aconfig @@ -29,6 +29,16 @@ flag { } flag { + name: "dismiss_dream_on_keyguard_dismiss" + namespace: "systemui" + description: "Dismisses the dream in the keyguard-going-away transition, preventing it from being visible" + bug: "333829441" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "dream_tracks_focus" namespace: "communal" description: "This flag enables the ability for dreams to track whether or not they have focus" diff --git a/core/java/android/view/ImeBackAnimationController.java b/core/java/android/view/ImeBackAnimationController.java index e14ddd6ed75b..1afedc185c85 100644 --- a/core/java/android/view/ImeBackAnimationController.java +++ b/core/java/android/view/ImeBackAnimationController.java @@ -63,8 +63,8 @@ public class ImeBackAnimationController implements OnBackAnimationCallback { private boolean mIsPreCommitAnimationInProgress = false; private int mStartRootScrollY = 0; - public ImeBackAnimationController(ViewRootImpl viewRoot) { - mInsetsController = viewRoot.getInsetsController(); + public ImeBackAnimationController(ViewRootImpl viewRoot, InsetsController insetsController) { + mInsetsController = insetsController; mViewRoot = viewRoot; } diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index 85c779bc8c79..6568912a82c0 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -95,7 +95,7 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro private final Matrix mTmpMatrix = new Matrix(); private final InsetsState mInitialInsetsState; private final @AnimationType int mAnimationType; - private final @LayoutInsetsDuringAnimation int mLayoutInsetsDuringAnimation; + private @LayoutInsetsDuringAnimation int mLayoutInsetsDuringAnimation; private final @InsetsType int mTypes; private @InsetsType int mControllingTypes; private final InsetsAnimationControlCallbacks mController; @@ -377,6 +377,12 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro } @Override + public void updateLayoutInsetsDuringAnimation( + @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) { + mLayoutInsetsDuringAnimation = layoutInsetsDuringAnimation; + } + + @Override public void dumpDebug(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); proto.write(IS_CANCELLED, mCancelled); diff --git a/core/java/android/view/InsetsAnimationControlRunner.java b/core/java/android/view/InsetsAnimationControlRunner.java index cf40e7e4d308..8cb8b47dd0ec 100644 --- a/core/java/android/view/InsetsAnimationControlRunner.java +++ b/core/java/android/view/InsetsAnimationControlRunner.java @@ -20,6 +20,7 @@ import android.annotation.Nullable; import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import android.view.InsetsController.AnimationType; +import android.view.InsetsController.LayoutInsetsDuringAnimation; import android.view.WindowInsets.Type.InsetsType; import android.view.inputmethod.ImeTracker; @@ -82,6 +83,14 @@ public interface InsetsAnimationControlRunner { ImeTracker.Token getStatsToken(); /** + * Updates the desired layout insets during the animation. + * + * @param layoutInsetsDuringAnimation Whether the insets should be shown or hidden + */ + void updateLayoutInsetsDuringAnimation( + @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation); + + /** * * Export the state of classes that implement this interface into a protocol buffer * output stream. diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java index 92e20e09d8c4..83ff88bdcc1c 100644 --- a/core/java/android/view/InsetsAnimationThreadControlRunner.java +++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java @@ -186,4 +186,11 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro public int getAnimationType() { return mControl.getAnimationType(); } + + @Override + public void updateLayoutInsetsDuringAnimation( + @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) { + InsetsAnimationThread.getHandler().post( + () -> mControl.updateLayoutInsetsDuringAnimation(layoutInsetsDuringAnimation)); + } } diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 6c9001163459..c54526747c5c 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -1028,8 +1028,23 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation reportRequestedVisibleTypes(); } - void setPredictiveBackImeHideAnimInProgress(boolean isInProgress) { + @VisibleForTesting(visibility = PACKAGE) + public void setPredictiveBackImeHideAnimInProgress(boolean isInProgress) { mIsPredictiveBackImeHideAnimInProgress = isInProgress; + if (isInProgress) { + // The InsetsAnimationControlRunner has layoutInsetsDuringAnimation set to SHOWN during + // predictive back. Let's set it to HIDDEN once the predictive back animation enters the + // post-commit phase. + // That prevents flickers in case the animation is cancelled by an incoming show request + // during the hide animation. + for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { + final InsetsAnimationControlRunner runner = mRunningAnimations.get(i).runner; + if ((runner.getTypes() & ime()) != 0) { + runner.updateLayoutInsetsDuringAnimation(LAYOUT_INSETS_DURING_ANIMATION_HIDDEN); + break; + } + } + } } boolean isPredictiveBackImeHideAnimInProgress() { @@ -1231,7 +1246,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation false /* fromPredictiveBack */); } - void controlWindowInsetsAnimation(@InsetsType int types, + @VisibleForTesting(visibility = PACKAGE) + public void controlWindowInsetsAnimation(@InsetsType int types, @Nullable CancellationSignal cancellationSignal, WindowInsetsAnimationControlListener listener, boolean fromIme, long durationMs, @Nullable Interpolator interpolator, @@ -1983,7 +1999,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } } - Host getHost() { + @VisibleForTesting(visibility = PACKAGE) + public Host getHost() { return mHost; } } diff --git a/core/java/android/view/InsetsResizeAnimationRunner.java b/core/java/android/view/InsetsResizeAnimationRunner.java index ebdddd537ae3..6e6222187e49 100644 --- a/core/java/android/view/InsetsResizeAnimationRunner.java +++ b/core/java/android/view/InsetsResizeAnimationRunner.java @@ -34,6 +34,7 @@ import android.graphics.Insets; import android.graphics.Rect; import android.util.SparseArray; import android.util.proto.ProtoOutputStream; +import android.view.InsetsController.LayoutInsetsDuringAnimation; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsAnimation.Bounds; import android.view.animation.Interpolator; @@ -242,4 +243,9 @@ public class InsetsResizeAnimationRunner implements InsetsAnimationControlRunner @Override public void onCancelled(WindowInsetsAnimationController controller) { } + + @Override + public void updateLayoutInsetsDuringAnimation( + @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) { + } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 645d69bfef03..f0d27dae799f 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1243,7 +1243,7 @@ public final class ViewRootImpl implements ViewParent, // TODO(b/222696368): remove getSfInstance usage and use vsyncId for transactions mChoreographer = Choreographer.getInstance(); mInsetsController = new InsetsController(new ViewRootInsetsControllerHost(this)); - mImeBackAnimationController = new ImeBackAnimationController(this); + mImeBackAnimationController = new ImeBackAnimationController(this, mInsetsController); mHandwritingInitiator = new HandwritingInitiator( mViewConfiguration, mContext.getSystemService(InputMethodManager.class)); @@ -4276,6 +4276,10 @@ public final class ViewRootImpl implements ViewParent, 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; } } @@ -5956,13 +5960,19 @@ public final class ViewRootImpl implements ViewParent, return handled; } - void setScrollY(int scrollY) { + @VisibleForTesting(visibility = PACKAGE) + public void setScrollY(int scrollY) { if (mScroller != null) { mScroller.abortAnimation(); } mScrollY = scrollY; } + @VisibleForTesting + public int getScrollY() { + return mScrollY; + } + /** * @hide */ @@ -12559,6 +12569,13 @@ public final class ViewRootImpl implements ViewParent, 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/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java index e5658e63f7ec..29bb32e6443f 100644 --- a/core/java/android/window/SnapshotDrawerUtils.java +++ b/core/java/android/window/SnapshotDrawerUtils.java @@ -52,11 +52,9 @@ import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.GraphicBuffer; -import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Rect; -import android.graphics.RectF; import android.hardware.HardwareBuffer; import android.os.IBinder; import android.util.Log; @@ -98,11 +96,6 @@ public class SnapshotDrawerUtils { | FLAG_SECURE | FLAG_DIM_BEHIND; - private static final RectF sTmpSnapshotSize = new RectF(); - private static final RectF sTmpDstFrame = new RectF(); - - private static final Matrix sSnapshotMatrix = new Matrix(); - private static final float[] sTmpFloat9 = new float[9]; private static final Paint sBackgroundPaint = new Paint(); /** @@ -116,24 +109,27 @@ public class SnapshotDrawerUtils { private final CharSequence mTitle; private SystemBarBackgroundPainter mSystemBarBackgroundPainter; - private final Rect mTaskBounds; private final Rect mFrame = new Rect(); private final Rect mSystemBarInsets = new Rect(); + private final int mSnapshotW; + private final int mSnapshotH; private boolean mSizeMismatch; public SnapshotSurface(SurfaceControl rootSurface, TaskSnapshot snapshot, - CharSequence title, - Rect taskBounds) { + CharSequence title) { mRootSurface = rootSurface; mSnapshot = snapshot; mTitle = title; - mTaskBounds = taskBounds; + final HardwareBuffer hwBuffer = snapshot.getHardwareBuffer(); + mSnapshotW = hwBuffer.getWidth(); + mSnapshotH = hwBuffer.getHeight(); } /** * Initiate system bar painter to draw the system bar background. */ - void initiateSystemBarPainter(int windowFlags, int windowPrivateFlags, + @VisibleForTesting + public void initiateSystemBarPainter(int windowFlags, int windowPrivateFlags, int appearance, ActivityManager.TaskDescription taskDescription, @WindowInsets.Type.InsetsType int requestedVisibleTypes) { mSystemBarBackgroundPainter = new SystemBarBackgroundPainter(windowFlags, @@ -143,14 +139,13 @@ public class SnapshotDrawerUtils { } /** - * Set frame size. + * Set frame size that the snapshot should fill. It is the bounds of a task or activity. */ - void setFrames(Rect frame, Rect systemBarInsets) { + @VisibleForTesting + public void setFrames(Rect frame, Rect systemBarInsets) { mFrame.set(frame); mSystemBarInsets.set(systemBarInsets); - final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer(); - mSizeMismatch = (mFrame.width() != snapshot.getWidth() - || mFrame.height() != snapshot.getHeight()); + mSizeMismatch = (mFrame.width() != mSnapshotW || mFrame.height() != mSnapshotH); mSystemBarBackgroundPainter.setInsets(systemBarInsets); } @@ -186,7 +181,7 @@ public class SnapshotDrawerUtils { // We consider nearly matched dimensions as there can be rounding errors and the user // won't notice very minute differences from scaling one dimension more than the other - boolean aspectRatioMismatch = !isAspectRatioMatch(mFrame, mSnapshot); + boolean aspectRatioMismatch = !isAspectRatioMatch(mFrame, mSnapshotW, mSnapshotH); // Keep a reference to it such that it doesn't get destroyed when finalized. SurfaceControl childSurfaceControl = new SurfaceControl.Builder(session) @@ -198,12 +193,14 @@ public class SnapshotDrawerUtils { .build(); final Rect frame; + final Rect letterboxInsets = mSnapshot.getLetterboxInsets(); + float offsetX = letterboxInsets.left; + float offsetY = letterboxInsets.top; // We can just show the surface here as it will still be hidden as the parent is // still hidden. mTransaction.show(childSurfaceControl); if (aspectRatioMismatch) { Rect crop = null; - final Rect letterboxInsets = mSnapshot.getLetterboxInsets(); if (letterboxInsets.left != 0 || letterboxInsets.top != 0 || letterboxInsets.right != 0 || letterboxInsets.bottom != 0) { // Clip off letterbox. @@ -214,23 +211,27 @@ public class SnapshotDrawerUtils { // if letterbox doesn't match window frame, try crop by content insets if (aspectRatioMismatch) { // Clip off ugly navigation bar. - crop = calculateSnapshotCrop(mSnapshot.getContentInsets()); + final Rect contentInsets = mSnapshot.getContentInsets(); + crop = calculateSnapshotCrop(contentInsets); + offsetX = contentInsets.left; + offsetY = contentInsets.top; } frame = calculateSnapshotFrame(crop); - mTransaction.setWindowCrop(childSurfaceControl, crop); - mTransaction.setPosition(childSurfaceControl, frame.left, frame.top); - sTmpSnapshotSize.set(crop); - sTmpDstFrame.set(frame); + mTransaction.setCrop(childSurfaceControl, crop); } else { frame = null; - sTmpSnapshotSize.set(0, 0, buffer.getWidth(), buffer.getHeight()); - sTmpDstFrame.set(mFrame); - sTmpDstFrame.offsetTo(0, 0); } - // Scale the mismatch dimensions to fill the task bounds - sSnapshotMatrix.setRectToRect(sTmpSnapshotSize, sTmpDstFrame, Matrix.ScaleToFit.FILL); - mTransaction.setMatrix(childSurfaceControl, sSnapshotMatrix, sTmpFloat9); + // Align the snapshot with content area. + if (offsetX != 0f || offsetY != 0f) { + mTransaction.setPosition(childSurfaceControl, + -offsetX * mFrame.width() / mSnapshot.getTaskSize().x, + -offsetY * mFrame.height() / mSnapshot.getTaskSize().y); + } + // Scale the mismatch dimensions to fill the target frame. + final float scaleX = (float) mFrame.width() / mSnapshotW; + final float scaleY = (float) mFrame.height() / mSnapshotH; + mTransaction.setScale(childSurfaceControl, scaleX, scaleY); mTransaction.setColorSpace(childSurfaceControl, mSnapshot.getColorSpace()); mTransaction.setBuffer(childSurfaceControl, mSnapshot.getHardwareBuffer()); @@ -261,17 +262,17 @@ public class SnapshotDrawerUtils { * @param insets Content insets or Letterbox insets * @return crop rect in snapshot coordinate space. */ - Rect calculateSnapshotCrop(@NonNull Rect insets) { + @VisibleForTesting + public Rect calculateSnapshotCrop(@NonNull Rect insets) { final Rect rect = new Rect(); - final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer(); - rect.set(0, 0, snapshot.getWidth(), snapshot.getHeight()); + rect.set(0, 0, mSnapshotW, mSnapshotH); - final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x; - final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y; + final float scaleX = (float) mSnapshotW / mSnapshot.getTaskSize().x; + final float scaleY = (float) mSnapshotH / mSnapshot.getTaskSize().y; // Let's remove all system decorations except the status bar, but only if the task is at // the very top of the screen. - final boolean isTop = mTaskBounds.top == 0 && mFrame.top == 0; + final boolean isTop = mFrame.top == 0; rect.inset((int) (insets.left * scaleX), isTop ? 0 : (int) (insets.top * scaleY), (int) (insets.right * scaleX), @@ -284,10 +285,10 @@ public class SnapshotDrawerUtils { * * @param crop rect that is in snapshot coordinate space. */ - Rect calculateSnapshotFrame(Rect crop) { - final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer(); - final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x; - final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y; + @VisibleForTesting + public Rect calculateSnapshotFrame(Rect crop) { + final float scaleX = (float) mSnapshotW / mSnapshot.getTaskSize().x; + final float scaleY = (float) mSnapshotH / mSnapshot.getTaskSize().y; // Rescale the frame from snapshot to window coordinate space final Rect frame = new Rect(0, 0, @@ -303,7 +304,8 @@ public class SnapshotDrawerUtils { /** * Draw status bar and navigation bar background. */ - void drawBackgroundAndBars(Canvas c, Rect frame) { + @VisibleForTesting + public void drawBackgroundAndBars(Canvas c, Rect frame) { final int statusBarHeight = mSystemBarBackgroundPainter.getStatusBarColorViewHeight(); final boolean fillHorizontally = c.getWidth() > frame.right; final boolean fillVertically = c.getHeight() > frame.bottom; @@ -320,33 +322,27 @@ public class SnapshotDrawerUtils { /** * Ask system bar background painter to draw status bar background. - * */ - void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame) { + @VisibleForTesting + public void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame) { mSystemBarBackgroundPainter.drawStatusBarBackground(c, alreadyDrawnFrame, mSystemBarBackgroundPainter.getStatusBarColorViewHeight()); } /** * Ask system bar background painter to draw navigation bar background. - * */ - void drawNavigationBarBackground(Canvas c) { + @VisibleForTesting + public void drawNavigationBarBackground(Canvas c) { mSystemBarBackgroundPainter.drawNavigationBarBackground(c); } } - /** - * @return true if the aspect ratio match between a frame and a snapshot buffer. - */ - public static boolean isAspectRatioMatch(Rect frame, TaskSnapshot snapshot) { + private static boolean isAspectRatioMatch(Rect frame, int w, int h) { if (frame.isEmpty()) { return false; } - final HardwareBuffer buffer = snapshot.getHardwareBuffer(); - return Math.abs( - ((float) buffer.getWidth() / buffer.getHeight()) - - ((float) frame.width() / frame.height())) <= 0.01f; + return Math.abs(((float) w / h) - ((float) frame.width() / frame.height())) <= 0.01f; } private static boolean isAspectRatioMatch(Rect frame1, Rect frame2) { @@ -378,14 +374,14 @@ public class SnapshotDrawerUtils { */ public static void drawSnapshotOnSurface(StartingWindowInfo info, WindowManager.LayoutParams lp, SurfaceControl rootSurface, TaskSnapshot snapshot, - Rect configBounds, Rect windowBounds, InsetsState topWindowInsetsState, + Rect windowBounds, InsetsState topWindowInsetsState, boolean releaseAfterDraw) { if (windowBounds.isEmpty()) { Log.e(TAG, "Unable to draw snapshot on an empty windowBounds"); return; } final SnapshotSurface drawSurface = new SnapshotSurface( - rootSurface, snapshot, lp.getTitle(), configBounds); + rootSurface, snapshot, lp.getTitle()); final WindowManager.LayoutParams attrs = info.topOpaqueWindowLayoutParams; final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo; diff --git a/core/java/android/window/StartingWindowInfo.java b/core/java/android/window/StartingWindowInfo.java index 260d9a82472f..72df343a2dbe 100644 --- a/core/java/android/window/StartingWindowInfo.java +++ b/core/java/android/window/StartingWindowInfo.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.app.TaskInfo; import android.content.pm.ActivityInfo; +import android.graphics.Rect; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; @@ -79,11 +80,17 @@ public final class StartingWindowInfo implements Parcelable { /** * The {@link TaskInfo} from this task. - * @hide + * <p>Note that the configuration of this taskInfo could be from the top activity of its task. + * Because only activity contains persisted configuration (e.g. night mode, language). Besides, + * it can also be used for activity level snapshot. */ @NonNull public ActivityManager.RunningTaskInfo taskInfo; + /** The bounds of the target task. */ + @NonNull + public final Rect taskBounds = new Rect(); + /** * The {@link ActivityInfo} of the target activity which to create the starting window. * It can be null if the info is the same as the top in task info. @@ -253,6 +260,7 @@ public final class StartingWindowInfo implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeTypedObject(taskInfo, flags); + taskBounds.writeToParcel(dest, flags); dest.writeTypedObject(targetActivityInfo, flags); dest.writeInt(startingWindowTypeParameter); dest.writeTypedObject(topOpaqueWindowInsetsState, flags); @@ -269,6 +277,7 @@ public final class StartingWindowInfo implements Parcelable { void readFromParcel(@NonNull Parcel source) { taskInfo = source.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR); + taskBounds.readFromParcel(source); targetActivityInfo = source.readTypedObject(ActivityInfo.CREATOR); startingWindowTypeParameter = source.readInt(); topOpaqueWindowInsetsState = source.readTypedObject(InsetsState.CREATOR); diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java index 7c7c7b8fa51d..9f9aae53e0af 100644 --- a/core/java/com/android/internal/os/PowerStats.java +++ b/core/java/com/android/internal/os/PowerStats.java @@ -473,7 +473,14 @@ public final class PowerStats { } 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 69d3d6a6d521..c21a43e807a9 100644 --- a/core/java/com/android/internal/util/ScreenshotHelper.java +++ b/core/java/com/android/internal/util/ScreenshotHelper.java @@ -53,8 +53,6 @@ public class ScreenshotHelper { 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 class ScreenshotHelper { 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 @@ public class ScreenshotHelper { 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 91ef324a7df0..0d1be3814cb5 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 daa0f553f47a..d421944917ea 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 c84f781d695e..b64334f7f95a 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/ImeBackAnimationControllerTest.java b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java new file mode 100644 index 000000000000..c00ebe487620 --- /dev/null +++ b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java @@ -0,0 +1,257 @@ +/* + * 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 android.view; + +import static android.view.WindowInsets.Type.ime; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; +import static android.window.BackEvent.EDGE_LEFT; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; + +import android.content.Context; +import android.graphics.Insets; +import android.platform.test.annotations.Presubmit; +import android.view.animation.BackGestureInterpolator; +import android.view.animation.Interpolator; +import android.view.inputmethod.InputMethodManager; +import android.widget.TextView; +import android.window.BackEvent; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link ImeBackAnimationController}. + * + * <p>Build/Install/Run: + * atest FrameworksCoreTests:ImeBackAnimationControllerTest + * + * <p>This test class is a part of Window Manager Service tests and specified in + * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}. + */ +@Presubmit +@RunWith(AndroidJUnit4.class) +public class ImeBackAnimationControllerTest { + + private static final float PEEK_FRACTION = 0.1f; + private static final Interpolator BACK_GESTURE = new BackGestureInterpolator(); + private static final int IME_HEIGHT = 200; + private static final Insets IME_INSETS = Insets.of(0, 0, 0, IME_HEIGHT); + + @Mock + private InsetsController mInsetsController; + @Mock + private WindowInsetsAnimationController mWindowInsetsAnimationController; + @Mock + private ViewRootInsetsControllerHost mViewRootInsetsControllerHost; + + private ViewRootImpl mViewRoot; + private ImeBackAnimationController mBackAnimationController; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + InputMethodManager inputMethodManager = context.getSystemService( + InputMethodManager.class); + // cannot mock ViewRootImpl since it's final. + mViewRoot = new ViewRootImpl(context, context.getDisplayNoVerify()); + try { + mViewRoot.setView(new TextView(context), new WindowManager.LayoutParams(), null); + } catch (WindowManager.BadTokenException e) { + // activity isn't running, we will ignore BadTokenException. + } + mBackAnimationController = new ImeBackAnimationController(mViewRoot, mInsetsController); + + when(mWindowInsetsAnimationController.getHiddenStateInsets()).thenReturn(Insets.NONE); + when(mWindowInsetsAnimationController.getShownStateInsets()).thenReturn(IME_INSETS); + when(mWindowInsetsAnimationController.getCurrentInsets()).thenReturn(IME_INSETS); + when(mInsetsController.getHost()).thenReturn(mViewRootInsetsControllerHost); + when(mViewRootInsetsControllerHost.getInputMethodManager()).thenReturn( + inputMethodManager); + }); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } + + @Test + public void testAdjustResizeWithAppWindowInsetsListenerPlaysAnim() { + // setup ViewRoot with InsetsAnimationCallback and softInputMode=adjustResize + mViewRoot.getView() + .setWindowInsetsAnimationCallback(mock(WindowInsetsAnimation.Callback.class)); + mViewRoot.mWindowAttributes.softInputMode = SOFT_INPUT_ADJUST_RESIZE; + // start back gesture + mBackAnimationController.onBackStarted(new BackEvent(0f, 0f, 0f, EDGE_LEFT)); + // verify that ImeBackAnimationController takes control over IME insets + verify(mInsetsController, times(1)).controlWindowInsetsAnimation(anyInt(), any(), any(), + anyBoolean(), anyLong(), any(), anyInt(), anyBoolean()); + } + + @Test + public void testAdjustResizeWithoutAppWindowInsetsListenerNotPlayingAnim() { + // setup ViewRoot with softInputMode=adjustResize + mViewRoot.mWindowAttributes.softInputMode = SOFT_INPUT_ADJUST_RESIZE; + // start back gesture + mBackAnimationController.onBackStarted(new BackEvent(0f, 0f, 0f, EDGE_LEFT)); + // progress back gesture + mBackAnimationController.onBackProgressed(new BackEvent(100f, 0f, 0.5f, EDGE_LEFT)); + // commit back gesture + mBackAnimationController.onBackInvoked(); + // verify that InsetsController#hide is called + verify(mInsetsController, times(1)).hide(ime()); + // verify that ImeBackAnimationController does not take control over IME insets + verify(mInsetsController, never()).controlWindowInsetsAnimation(anyInt(), any(), any(), + anyBoolean(), anyLong(), any(), anyInt(), anyBoolean()); + } + + @Test + public void testAdjustPanScrollsViewRoot() { + // simulate view root being panned upwards by 50px + int appPan = -50; + mViewRoot.setScrollY(appPan); + // setup ViewRoot with softInputMode=adjustPan + mViewRoot.mWindowAttributes.softInputMode = SOFT_INPUT_ADJUST_PAN; + + // start back gesture + WindowInsetsAnimationControlListener animationControlListener = startBackGesture(); + // simulate ImeBackAnimationController receiving control + animationControlListener.onReady(mWindowInsetsAnimationController, ime()); + + // progress back gesture + float progress = 0.5f; + mBackAnimationController.onBackProgressed(new BackEvent(100f, 0f, progress, EDGE_LEFT)); + + // verify that view root is scrolled by expected amount + float interpolatedProgress = BACK_GESTURE.getInterpolation(progress); + int expectedViewRootScroll = + (int) (appPan * (1 - interpolatedProgress * PEEK_FRACTION)); + assertEquals(mViewRoot.getScrollY(), expectedViewRootScroll); + } + + @Test + public void testNewGestureAfterCancelSeamlessTakeover() { + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + // start back gesture + WindowInsetsAnimationControlListener animationControlListener = startBackGesture(); + // simulate ImeBackAnimationController receiving control + animationControlListener.onReady(mWindowInsetsAnimationController, ime()); + // verify initial animation insets are set + verify(mWindowInsetsAnimationController, times(1)).setInsetsAndAlpha( + eq(Insets.of(0, 0, 0, IME_HEIGHT)), eq(1f), anyFloat()); + + // progress back gesture + mBackAnimationController.onBackProgressed(new BackEvent(100f, 0f, 0.5f, EDGE_LEFT)); + + // cancel back gesture + mBackAnimationController.onBackCancelled(); + // verify that InsetsController does not notified of a hide-anim (because the gesture + // was cancelled) + verify(mInsetsController, never()).setPredictiveBackImeHideAnimInProgress(eq(true)); + + Mockito.clearInvocations(mWindowInsetsAnimationController); + // restart back gesture + mBackAnimationController.onBackStarted(new BackEvent(0f, 0f, 0f, EDGE_LEFT)); + // verify that animation controller is reused and initial insets are set immediately + verify(mWindowInsetsAnimationController, times(1)).setInsetsAndAlpha( + eq(Insets.of(0, 0, 0, IME_HEIGHT)), eq(1f), anyFloat()); + }); + } + + @Test + public void testImeInsetsManipulationCurve() { + // start back gesture + WindowInsetsAnimationControlListener animationControlListener = startBackGesture(); + // simulate ImeBackAnimationController receiving control + animationControlListener.onReady(mWindowInsetsAnimationController, ime()); + // verify initial animation insets are set + verify(mWindowInsetsAnimationController, times(1)).setInsetsAndAlpha( + eq(Insets.of(0, 0, 0, IME_HEIGHT)), eq(1f), anyFloat()); + + Mockito.clearInvocations(mWindowInsetsAnimationController); + // progress back gesture + float progress = 0.5f; + mBackAnimationController.onBackProgressed(new BackEvent(100f, 0f, progress, EDGE_LEFT)); + // verify correct ime insets manipulation + float interpolatedProgress = BACK_GESTURE.getInterpolation(progress); + int expectedInset = + (int) (IME_HEIGHT - interpolatedProgress * PEEK_FRACTION * IME_HEIGHT); + verify(mWindowInsetsAnimationController, times(1)).setInsetsAndAlpha( + eq(Insets.of(0, 0, 0, expectedInset)), eq(1f), anyFloat()); + } + + @Test + public void testOnReadyAfterGestureFinished() { + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + // start back gesture + WindowInsetsAnimationControlListener animationControlListener = startBackGesture(); + + // progress back gesture + mBackAnimationController.onBackProgressed(new BackEvent(100f, 0f, 0.5f, EDGE_LEFT)); + + // commit back gesture + mBackAnimationController.onBackInvoked(); + + // verify setInsetsAndAlpha never called due onReady delayed + verify(mWindowInsetsAnimationController, never()).setInsetsAndAlpha(any(), anyInt(), + anyFloat()); + verify(mInsetsController, never()).setPredictiveBackImeHideAnimInProgress(eq(true)); + + // simulate ImeBackAnimationController receiving control + animationControlListener.onReady(mWindowInsetsAnimationController, ime()); + + // verify setInsetsAndAlpha immediately called + verify(mWindowInsetsAnimationController, times(1)).setInsetsAndAlpha( + eq(Insets.of(0, 0, 0, IME_HEIGHT)), eq(1f), anyFloat()); + // verify post-commit hide anim has started + verify(mInsetsController, times(1)).setPredictiveBackImeHideAnimInProgress(eq(true)); + }); + } + + private WindowInsetsAnimationControlListener startBackGesture() { + // start back gesture + mBackAnimationController.onBackStarted(new BackEvent(0f, 0f, 0f, EDGE_LEFT)); + + // verify controlWindowInsetsAnimation is called and capture animationControlListener + ArgumentCaptor<WindowInsetsAnimationControlListener> animationControlListener = + ArgumentCaptor.forClass(WindowInsetsAnimationControlListener.class); + verify(mInsetsController, times(1)).controlWindowInsetsAnimation(anyInt(), any(), + animationControlListener.capture(), anyBoolean(), anyLong(), any(), anyInt(), + anyBoolean()); + + return animationControlListener.getValue(); + } +} diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index 4fb85c1fa7ff..f05390dff5cd 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -21,6 +21,7 @@ import static android.view.InsetsController.ANIMATION_TYPE_HIDE; import static android.view.InsetsController.ANIMATION_TYPE_NONE; import static android.view.InsetsController.ANIMATION_TYPE_RESIZE; import static android.view.InsetsController.ANIMATION_TYPE_SHOW; +import static android.view.InsetsController.ANIMATION_TYPE_USER; import static android.view.InsetsSource.FLAG_ANIMATE_RESIZING; import static android.view.InsetsSource.ID_IME; import static android.view.InsetsSourceConsumer.ShowResult.IME_SHOW_DELAYED; @@ -925,6 +926,95 @@ public class InsetsControllerTest { }); } + @Test + public void testImeRequestedVisibleDuringPredictiveBackAnim() { + prepareControls(); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + // show ime as initial state + mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty()); + mController.cancelExistingAnimations(); // fast forward show animation + assertTrue(mController.getState().peekSource(ID_IME).isVisible()); + + // start control request (for predictive back animation) + WindowInsetsAnimationControlListener listener = + mock(WindowInsetsAnimationControlListener.class); + mController.controlWindowInsetsAnimation(ime(), /*cancellationSignal*/ null, + listener, /*fromIme*/ false, /*duration*/ -1, /*interpolator*/ null, + ANIMATION_TYPE_USER, /*fromPredictiveBack*/ true); + + // Verify that onReady is called (after next predraw) + mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw(); + verify(listener).onReady(notNull(), eq(ime())); + + // verify that insets are requested visible during animation + assertTrue(isRequestedVisible(mController, ime())); + }); + } + + @Test + public void testImeShowRequestCancelsPredictiveBackPostCommitAnim() { + prepareControls(); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + // show ime as initial state + mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty()); + mController.cancelExistingAnimations(); // fast forward show animation + mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw(); + assertTrue(mController.getState().peekSource(ID_IME).isVisible()); + + // start control request (for predictive back animation) + WindowInsetsAnimationControlListener listener = + mock(WindowInsetsAnimationControlListener.class); + mController.controlWindowInsetsAnimation(ime(), /*cancellationSignal*/ null, + listener, /*fromIme*/ false, /*duration*/ -1, /*interpolator*/ null, + ANIMATION_TYPE_USER, /*fromPredictiveBack*/ true); + + // verify that controller + // has ANIMATION_TYPE_USER set for ime() + assertEquals(ANIMATION_TYPE_USER, mController.getAnimationType(ime())); + + // verify show request is ignored during pre commit phase of predictive back anim + mController.show(ime(), true /* fromIme */, null /* statsToken */); + assertEquals(ANIMATION_TYPE_USER, mController.getAnimationType(ime())); + + // verify show request is applied during post commit phase of predictive back anim + mController.setPredictiveBackImeHideAnimInProgress(true); + mController.show(ime(), true /* fromIme */, null /* statsToken */); + assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ime())); + + // additionally verify that IME ends up visible + mController.cancelExistingAnimations(); + assertTrue(mController.getState().peekSource(ID_IME).isVisible()); + }); + } + + @Test + public void testImeHideRequestIgnoredDuringPredictiveBackPostCommitAnim() { + prepareControls(); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + // show ime as initial state + mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty()); + mController.cancelExistingAnimations(); // fast forward show animation + mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw(); + assertTrue(mController.getState().peekSource(ID_IME).isVisible()); + + // start control request (for predictive back animation) + WindowInsetsAnimationControlListener listener = + mock(WindowInsetsAnimationControlListener.class); + mController.controlWindowInsetsAnimation(ime(), /*cancellationSignal*/ null, + listener, /*fromIme*/ false, /*duration*/ -1, /*interpolator*/ null, + ANIMATION_TYPE_USER, /*fromPredictiveBack*/ true); + + // verify that controller has ANIMATION_TYPE_USER set for ime() + assertEquals(ANIMATION_TYPE_USER, mController.getAnimationType(ime())); + + // verify hide request is ignored during post commit phase of predictive back anim + // since IME is already animating away + mController.setPredictiveBackImeHideAnimInProgress(true); + mController.hide(ime(), true /* fromIme */, null /* statsToken */); + assertEquals(ANIMATION_TYPE_USER, mController.getAnimationType(ime())); + }); + } + private void waitUntilNextFrame() throws Exception { final CountDownLatch latch = new CountDownLatch(1); Choreographer.getMainThreadInstance().postCallback(Choreographer.CALLBACK_COMMIT, diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java index f885e31ed270..32aec1a335f2 100644 --- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java +++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java @@ -36,6 +36,7 @@ import static org.junit.Assert.assertTrue; 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 android.widget.FrameLayout; 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 @@ public class ViewFrameRateTest { }); 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 9afc4be20eee..5caf77dbdcff 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; 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 @@ public class ViewRootImplTest { }); 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/android/window/SnapshotDrawerUtilsTest.java b/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java index 036154634ae7..6c8dcd39e223 100644 --- a/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java +++ b/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java @@ -77,7 +77,7 @@ public class SnapshotDrawerUtilsTest { Color.RED, Color.BLUE); mSnapshotSurface = new SnapshotDrawerUtils.SnapshotSurface( - new SurfaceControl(), snapshot, "Test", taskBounds); + new SurfaceControl(), snapshot, "Test"); mSnapshotSurface.initiateSystemBarPainter(windowFlags, 0, 0, taskDescription, WindowInsets.Type.defaultVisible()); } @@ -167,14 +167,16 @@ public class SnapshotDrawerUtilsTest { @Test public void testCalculateSnapshotCrop_taskNotOnTop() { final Rect contentInsets = new Rect(0, 10, 0, 10); - setupSurface(100, 100, contentInsets, 0, new Rect(0, 50, 100, 150)); + final Rect bounds = new Rect(0, 50, 100, 150); + setupSurface(100, 100, contentInsets, 0, bounds); + mSnapshotSurface.setFrames(bounds, contentInsets); assertEquals(new Rect(0, 10, 100, 90), mSnapshotSurface.calculateSnapshotCrop(contentInsets)); } @Test public void testCalculateSnapshotCrop_navBarLeft() { - final Rect contentInsets = new Rect(0, 10, 0, 0); + final Rect contentInsets = new Rect(10, 0, 0, 0); setupSurface(100, 100, contentInsets, 0, new Rect(0, 0, 100, 100)); assertEquals(new Rect(10, 0, 100, 100), mSnapshotSurface.calculateSnapshotCrop(contentInsets)); 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 6402206410b5..baab3b218746 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 @@ public class PowerStatsTest { 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 e1bf40ca19dc..611013365e7d 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 @@ class BubbleExpandedViewPinControllerTest { } @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 @@ class BubbleExpandedViewPinControllerTest { .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 @@ class BubbleExpandedViewPinControllerTest { 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 @@ class BubbleExpandedViewPinControllerTest { 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 @@ class BubbleExpandedViewPinControllerTest { 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 @@ class BubbleExpandedViewPinControllerTest { 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/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index d2958779c0d4..98b2431fecd9 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 @@ -725,6 +725,17 @@ public class BubbleController implements ConfigurationChangeListener, } } + /** + * 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 +2261,19 @@ public class BubbleController implements ConfigurationChangeListener, 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 127a49fc7875..322088b17e63 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 android.window.ScreenCapture.SynchronousScreenCaptureListener; 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 @@ public interface Bubbles { * 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 e48f8d5f1c84..14d29cd887bb 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 @@ oneway interface IBubblesListener { * 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 fe9c4d4c9094..a51ac633ad86 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 @@ class BubbleBarExpandedViewDragController( 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 62cc4da3193e..a351cef223b5 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.ViewTreeObserver; 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.BubbleViewProvider; 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 @@ public class BubbleBarLayerView extends FrameLayout 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 a008045a4b6f..e514f9d70599 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 @@ abstract class BaseBubblePinController(private val screenSizeProvider: () -> Poi fun onDragEnd() { getDropTargetView()?.let { view -> view.animateOut { removeDropTargetView(view) } } dismissZone = null + listener?.onRelease(if (onLeft) LEFT else RIGHT) } /** @@ -170,14 +171,22 @@ abstract class BaseBubblePinController(private val screenSizeProvider: () -> Poi /** 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/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 6834e6d3123f..17121c8de428 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -702,10 +702,12 @@ public abstract class WMShellBaseModule { ShellInit shellInit, ShellController shellController, Transitions transitions, + TaskStackListenerImpl taskStackListener, @ShellMainThread Handler mainHandler, @ShellMainThread ShellExecutor mainExecutor) { return new KeyguardTransitionHandler( - shellInit, shellController, transitions, mainHandler, mainExecutor); + shellInit, shellController, transitions, taskStackListener, mainHandler, + mainExecutor); } @WMSingleton 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 0a9e5d0d5345..08b7c019fa62 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( ) 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( 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 e341f2d4d4b4..e5e435da48b2 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.RectEvaluator 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.TransitionRequestInfo 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 @@ class DragToDesktopTransitionHandler( .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 @@ class DragToDesktopTransitionHandler( 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 @@ class DragToDesktopTransitionHandler( .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 @@ class DragToDesktopTransitionHandler( ?: 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 @@ class DragToDesktopTransitionHandler( 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 @@ class DragToDesktopTransitionHandler( } 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 @@ class DragToDesktopTransitionHandler( 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/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java index 863a51ad575b..9eaf7e4e2e21 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java @@ -20,6 +20,7 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.service.dreams.Flags.dismissDreamOnKeyguardDismiss; import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; @@ -44,10 +45,13 @@ import android.window.IRemoteTransition; import android.window.IRemoteTransitionFinishedCallback; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; +import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.TaskStackListenerCallback; +import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.annotations.ExternalThread; import com.android.wm.shell.sysui.KeyguardChangeListener; @@ -62,7 +66,8 @@ import com.android.wm.shell.transition.Transitions.TransitionFinishCallback; * <p>This takes the highest priority. */ public class KeyguardTransitionHandler - implements Transitions.TransitionHandler, KeyguardChangeListener { + implements Transitions.TransitionHandler, KeyguardChangeListener, + TaskStackListenerCallback { private static final String TAG = "KeyguardTransition"; private final Transitions mTransitions; @@ -71,6 +76,7 @@ public class KeyguardTransitionHandler private final ShellExecutor mMainExecutor; private final ArrayMap<IBinder, StartedTransition> mStartedTransitions = new ArrayMap<>(); + private final TaskStackListenerImpl mTaskStackListener; /** * Local IRemoteTransition implementations registered by the keyguard service. @@ -87,6 +93,8 @@ public class KeyguardTransitionHandler // Last value reported by {@link KeyguardChangeListener}. private boolean mKeyguardShowing = true; + @Nullable + private WindowContainerToken mDreamToken; private final class StartedTransition { final TransitionInfo mInfo; @@ -105,18 +113,23 @@ public class KeyguardTransitionHandler @NonNull ShellInit shellInit, @NonNull ShellController shellController, @NonNull Transitions transitions, + @NonNull TaskStackListenerImpl taskStackListener, @NonNull Handler mainHandler, @NonNull ShellExecutor mainExecutor) { mTransitions = transitions; mShellController = shellController; mMainHandler = mainHandler; mMainExecutor = mainExecutor; + mTaskStackListener = taskStackListener; shellInit.addInitCallback(this::onInit, this); } private void onInit() { mTransitions.addHandler(this); mShellController.addKeyguardChangeListener(this); + if (dismissDreamOnKeyguardDismiss()) { + mTaskStackListener.addListener(this); + } } /** @@ -142,6 +155,11 @@ public class KeyguardTransitionHandler } @Override + public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) { + mDreamToken = taskInfo.getActivityType() == ACTIVITY_TYPE_DREAM ? taskInfo.token : null; + } + + @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @@ -271,6 +289,13 @@ public class KeyguardTransitionHandler @Override public WindowContainerTransaction handleRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request) { + if (dismissDreamOnKeyguardDismiss() + && (request.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0 + && mDreamToken != null) { + // Dismiss the dream in the same transaction, so that it isn't visible once the device + // is unlocked. + return new WindowContainerTransaction().removeTask(mDreamToken); + } return null; } 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 4c68106af1fe..2a50b191b1e9 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 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, 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/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java index e727945d6ee6..66b3553bea09 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java @@ -29,7 +29,6 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManager.TaskDescription; import android.graphics.Paint; -import android.graphics.Point; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; @@ -103,8 +102,6 @@ public class TaskSnapshotWindow { return null; } - final Point taskSize = snapshot.getTaskSize(); - final Rect taskBounds = new Rect(0, 0, taskSize.x, taskSize.y); final int orientation = snapshot.getOrientation(); final int displayId = runningTaskInfo.displayId; @@ -160,7 +157,7 @@ public class TaskSnapshotWindow { } SnapshotDrawerUtils.drawSnapshotOnSurface(info, layoutParams, surfaceControl, snapshot, - taskBounds, tmpFrames.frame, topWindowInsetsState, true /* releaseAfterDraw */); + info.taskBounds, topWindowInsetsState, true /* releaseAfterDraw */); snapshotSurface.mHasDrawn = true; snapshotSurface.reportDrawn(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java index fed2f34b5e0c..5c814dcc9b16 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java @@ -23,7 +23,6 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.app.ActivityManager; import android.content.Context; -import android.graphics.Point; import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.view.Display; @@ -77,15 +76,13 @@ class WindowlessSnapshotWindowCreator { runningTaskInfo.configuration, rootSurface); final SurfaceControlViewHost mViewHost = new SurfaceControlViewHost( mContext, display, wlw, "WindowlessSnapshotWindowCreator"); - final Point taskSize = snapshot.getTaskSize(); - final Rect snapshotBounds = new Rect(0, 0, taskSize.x, taskSize.y); final Rect windowBounds = runningTaskInfo.configuration.windowConfiguration.getBounds(); final InsetsState topWindowInsetsState = info.topOpaqueWindowInsetsState; final FrameLayout rootLayout = new FrameLayout( mSplashscreenContentDrawer.createViewContextWrapper(mContext)); mViewHost.setView(rootLayout, lp); SnapshotDrawerUtils.drawSnapshotOnSurface(info, lp, wlw.mChildSurface, snapshot, - snapshotBounds, windowBounds, topWindowInsetsState, false /* releaseAfterDraw */); + windowBounds, topWindowInsetsState, false /* releaseAfterDraw */); final ActivityManager.TaskDescription taskDescription = SnapshotDrawerUtils.getOrCreateTaskDescription(runningTaskInfo); 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 922c55f49a33..01175f598089 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_POSIT 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 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { 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 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { @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 49810294a888..5f84862ddf49 100644 --- a/location/java/android/location/flags/location.aconfig +++ b/location/java/android/location/flags/location.aconfig @@ -80,10 +80,13 @@ flag { } 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 151581134009..adbfc72cd684 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/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java index 9e9350b1a17a..2e9075caf344 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java @@ -186,9 +186,14 @@ public class SettingsHelper { sendBroadcast = sBroadcastOnRestore.contains(name); sendBroadcastSystemUI = sBroadcastOnRestoreSystemUI.contains(name); - if (sendBroadcast || sendBroadcastSystemUI) { + if (sendBroadcast) { // TODO: http://b/22388012 oldValue = table.lookup(cr, name, UserHandle.USER_SYSTEM); + } else if (sendBroadcastSystemUI) { + // This is only done for broadcasts sent to system ui as the consumers are known. + // It would probably be correct to do it for the ones sent to the system, but consumers + // may be depending on the current behavior. + oldValue = table.lookup(cr, name, context.getUserId()); } try { @@ -266,7 +271,7 @@ public class SettingsHelper { if (sendBroadcastSystemUI) { intent.setPackage( context.getString(com.android.internal.R.string.config_systemUi)); - context.sendBroadcastAsUser(intent, UserHandle.SYSTEM, null); + context.sendBroadcastAsUser(intent, context.getUser(), null); } } } diff --git a/packages/Shell/Android.bp b/packages/Shell/Android.bp index c87916fa3b95..253145468e47 100644 --- a/packages/Shell/Android.bp +++ b/packages/Shell/Android.bp @@ -19,6 +19,9 @@ android_app { include_dirs: ["frameworks/native/cmds/dumpstate/binder"], }, static_libs: shell_static_libs, + libs: [ + "device_policy_aconfig_flags_lib", + ], platform_apis: true, certificate: "platform", privileged: true, diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java index 5ac0e449b8e1..bcfd8f620f9c 100644 --- a/packages/Shell/src/com/android/shell/BugreportProgressService.java +++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java @@ -16,6 +16,7 @@ package com.android.shell; +import static android.app.admin.flags.Flags.onboardingBugreportStorageBugFix; import static android.content.pm.PackageManager.FEATURE_LEANBACK; import static android.content.pm.PackageManager.FEATURE_TELEVISION; import static android.os.Process.THREAD_PRIORITY_BACKGROUND; @@ -89,10 +90,10 @@ import com.android.internal.app.ChooserActivity; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.google.android.collect.Lists; - import libcore.io.Streams; +import com.google.android.collect.Lists; + import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.File; @@ -109,6 +110,8 @@ import java.security.NoSuchAlgorithmException; import java.text.NumberFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; @@ -442,10 +445,14 @@ public class BugreportProgressService extends Service { } } - private static void sendRemoteBugreportFinishedBroadcast(Context context, + private void sendRemoteBugreportFinishedBroadcast(Context context, String bugreportFileName, File bugreportFile, long nonce) { - cleanupOldFiles(REMOTE_BUGREPORT_FILES_AMOUNT, REMOTE_MIN_KEEP_AGE, - bugreportFile.getParentFile()); + // Remote bugreports are stored in the same directory as normal bugreports, meaning that + // the remote bugreport storage limit will get applied to normal bugreports whenever a + // remote bugreport is triggered. The fix in cleanupOldFiles applies the normal bugreport + // limit to the remote bugreports as a quick fix. + cleanupOldFiles( + REMOTE_BUGREPORT_FILES_AMOUNT, REMOTE_MIN_KEEP_AGE, bugreportFile.getParentFile()); final Intent intent = new Intent(DevicePolicyManager.ACTION_REMOTE_BUGREPORT_DISPATCH); final Uri bugreportUri = getUri(context, bugreportFile); final String bugreportHash = generateFileHash(bugreportFileName); @@ -496,12 +503,16 @@ public class BugreportProgressService extends Service { return fileHash; } - static void cleanupOldFiles(final int minCount, final long minAge, File bugreportsDir) { + void cleanupOldFiles(final int minCount, final long minAge, File bugreportsDir) { new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { try { - FileUtils.deleteOlderFiles(bugreportsDir, minCount, minAge); + if (onboardingBugreportStorageBugFix()) { + cleanupOldBugreports(); + } else { + FileUtils.deleteOlderFiles(bugreportsDir, minCount, minAge); + } } catch (RuntimeException e) { Log.e(TAG, "RuntimeException deleting old files", e); } @@ -510,6 +521,42 @@ public class BugreportProgressService extends Service { }.execute(); } + private void cleanupOldBugreports() { + final File[] files = mBugreportsDir.listFiles(); + if (files == null) return; + + // Sort with newest files first + Arrays.sort(files, new Comparator<File>() { + @Override + public int compare(File lhs, File rhs) { + return Long.compare(rhs.lastModified(), lhs.lastModified()); + } + }); + + int normalBugreportFilesCount = 0; + int deferredBugreportFilesCount = 0; + for (int i = 0; i < files.length; i++) { + final File file = files[i]; + + // tmp files are deferred bugreports which have their separate storage limit + boolean isDeferredBugreportFile = file.getName().endsWith(".tmp"); + if (isDeferredBugreportFile) { + deferredBugreportFilesCount++; + } else { + normalBugreportFilesCount++; + } + // Keep files newer than minAgeMs + final long age = System.currentTimeMillis() - file.lastModified(); + final int count = isDeferredBugreportFile + ? deferredBugreportFilesCount : normalBugreportFilesCount; + if (count > MIN_KEEP_COUNT && age > MIN_KEEP_AGE) { + if (file.delete()) { + Log.d(TAG, "Deleted old file " + file); + } + } + } + } + /** * Main thread used to handle all requests but taking screenshots. */ diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp index 64dcf6e8f573..395354ef8f20 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp +++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp @@ -34,6 +34,7 @@ android_test { "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 0ab99fac9ba3..66943d453873 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.Accessibility 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.BrightnessInfo; 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 android.view.accessibility.AccessibilityNodeInfo; 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.AfterClass; 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 @@ public class AccessibilityMenuServiceTest { 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 @@ public class AccessibilityMenuServiceTest { @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 @@ public class AccessibilityMenuServiceTest { // 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 class AccessibilityMenuServiceTest { 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 @@ public class AccessibilityMenuServiceTest { 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 @@ public class AccessibilityMenuServiceTest { 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 @@ public class AccessibilityMenuServiceTest { 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 @@ public class AccessibilityMenuServiceTest { } @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() 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 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 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 338987a60227..7d56a67175fd 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.appwidget.AppWidgetHostView 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.viewinterop.AndroidView 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 @@ fun CommunalHub( viewModel.onHidePopup() viewModel.onOpenWidgetEditor(selectedKey.value) }, - onHide = { viewModel.onHidePopup()} + onHide = { viewModel.onHidePopup() } ) } null -> {} @@ -374,7 +374,7 @@ private fun ScrollOnUpdatedLiveContentEffect( 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 @@ private fun WidgetContent( 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 @@ private fun WidgetContent( 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 f354b80692f5..74af3ca19266 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 @@ private class ClickableSliceView( } 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 4f3a6c84b13d..874c0a299d2b 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.clearAndSetSemantics 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 @@ class ToggleButtonComponent( 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 4273b4fbf7b8..ca643231e874 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 @@ internal class ElementNode( 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 7fb5a4d0cc27..339868c9fbc9 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.mutableStateOf 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 @@ internal class Scene( 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 ad691ba54607..d383cec324d3 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 data class LayoutElement(private val layoutImpl: SceneTransitionLayoutIm 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 bd36cb8655ac..b392c679e49c 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 @@ package com.android.compose.animation.scene.modifiers 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 b1d7055573b6..92e1b2cd030c 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.Alignment 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 @@ class ElementTest { 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 @@ class ElementTest { // 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 ac7717b41a5a..ce4c52757e78 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 @@ class LargeTopAppBarNestedScrollConnectionTest(testCase: TestCase) { 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 c96a8ce9e159..9e9a00290a0b 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.platform.test.flag.junit.FlagsParameterization 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.Flags.COMMUNAL_SERVICE_ENABLED 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.user.data.repository.fakeUserRepository 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 @@ class CommunalViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { 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 @@ class CommunalViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { 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 @@ class CommunalViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { underTest = CommunalViewModel( testScope, + kosmos.keyguardTransitionInteractor, kosmos.communalInteractor, kosmos.communalTutorialInteractor, kosmos.shadeInteractor, @@ -326,6 +342,105 @@ class CommunalViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { 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/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModelTest.kt new file mode 100644 index 000000000000..288dc489c1bd --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModelTest.kt @@ -0,0 +1,80 @@ +/* + * 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.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectValues +import com.android.systemui.flags.Flags +import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class AlternateBouncerToOccludedTransitionViewModelTest : SysuiTestCase() { + val kosmos = + testKosmos().apply { + fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } + } + + private val testScope = kosmos.testScope + private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + private val underTest by lazy { kosmos.alternateBouncerToOccludedTransitionViewModel } + + @Test + fun deviceEntryParentViewDisappear() = + testScope.runTest { + val values by collectValues(underTest.deviceEntryParentViewAlpha) + + keyguardTransitionRepository.sendTransitionSteps( + listOf( + step(0f, TransitionState.STARTED), + step(0f), + step(0.1f), + step(0.2f), + step(0.3f), + step(1f), + ), + testScope, + ) + + values.forEach { assertThat(it).isEqualTo(0f) } + } + + private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep { + return TransitionStep( + from = KeyguardState.ALTERNATE_BOUNCER, + to = KeyguardState.OCCLUDED, + value = value, + transitionState = state, + ownerName = "AlternateBouncerToOccludedTransitionViewModelTest" + ) + } +} 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 2fb8212e2b7c..f58e01ffef27 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.kosmos.testScope 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.MutableStateFlow 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 @@ class SceneInteractorTest : SysuiTestCase() { 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 @@ class SceneInteractorTest : SysuiTestCase() { @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 @@ class SceneInteractorTest : SysuiTestCase() { @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 @@ class SceneInteractorTest : SysuiTestCase() { @Test fun transitionState() = testScope.runTest { + underTest = kosmos.sceneInteractor val underTest = kosmos.sceneContainerRepository val transitionState = MutableStateFlow<ObservableTransitionState>( @@ -143,6 +177,7 @@ class SceneInteractorTest : SysuiTestCase() { @Test fun transitioningTo() = testScope.runTest { + underTest = kosmos.sceneInteractor val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Idle(underTest.currentScene.value) @@ -179,6 +214,7 @@ class SceneInteractorTest : SysuiTestCase() { @Test fun isTransitionUserInputOngoing_idle_false() = testScope.runTest { + underTest = kosmos.sceneInteractor val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Idle(Scenes.Shade) @@ -193,6 +229,7 @@ class SceneInteractorTest : SysuiTestCase() { @Test fun isTransitionUserInputOngoing_transition_true() = testScope.runTest { + underTest = kosmos.sceneInteractor val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Transition( @@ -213,6 +250,7 @@ class SceneInteractorTest : SysuiTestCase() { @Test fun isTransitionUserInputOngoing_updateMidTransition_false() = testScope.runTest { + underTest = kosmos.sceneInteractor val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Transition( @@ -244,6 +282,7 @@ class SceneInteractorTest : SysuiTestCase() { @Test fun isTransitionUserInputOngoing_updateOnIdle_false() = testScope.runTest { + underTest = kosmos.sceneInteractor val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Transition( @@ -268,6 +307,7 @@ class SceneInteractorTest : SysuiTestCase() { @Test fun isVisible() = testScope.runTest { + underTest = kosmos.sceneInteractor val isVisible by collectLastValue(underTest.isVisible) assertThat(isVisible).isTrue() @@ -281,6 +321,7 @@ class SceneInteractorTest : SysuiTestCase() { @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/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index e160cfc0b06e..66f741620e44 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -155,7 +155,6 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { mColorExtractor, mDumpManager, mKeyguardStateController, - mKosmos.getScreenOffAnimationController(), mAuthController, mKosmos::getShadeInteractor, mShadeWindowLogger, 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 d13efd25e353..f644584f747a 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_height="match_parent"> 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 a6e660f940df..46b8e4665a22 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 c724d24a31da..d51fe5849133 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 79b82bf51d8c..7adfa6ca1c29 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 f35de0568327..dc9c4f144cdc 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 a21114798c8f..da5695163cb4 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 @@ object BiometricViewBinder { } // 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 @@ object BiometricViewBinder { 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 511bdc4c6cea..a081ecc269f8 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 @@ abstract class BaseCommunalViewModel( ) { 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 9dacf8cc4c5a..24ea7b6ed5bf 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.communal.domain.model.CommunalContentModel 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 @@ class CommunalViewModel @Inject constructor( @Application private val scope: CoroutineScope, + keyguardTransitionInteractor: KeyguardTransitionInteractor, private val communalInteractor: CommunalInteractor, tutorialInteractor: CommunalTutorialInteractor, private val shadeInteractor: ShadeInteractor, @@ -93,6 +96,18 @@ constructor( 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 840c3a83b758..0fe9bf450753 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.Outline 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 @@ class CommunalAppWidgetHostView(context: Context) : AppWidgetHostView(context), 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 0bc29a8d0f16..95bc514540f5 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.colorextraction.SysuiColorExtractor; 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 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene 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 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene 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 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene 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 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene @NonNull VibratorHelper vibrator, @Main Resources resources, ConfigurationController configurationController, + ActivityStarter activityStarter, UserTracker userTracker, KeyguardStateController keyguardStateController, UserManager userManager, @@ -385,6 +392,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mSecureSettings = secureSettings; mResources = resources; mConfigurationController = configurationController; + mActivityStarter = activityStarter; mUserTracker = userTracker; mUserManager = userManager; mTrustManager = trustManager; @@ -659,6 +667,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene 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 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } } + @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/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index ae163eaf811f..674c128a580e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -17,6 +17,7 @@ package com.android.systemui.keyguard; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.service.dreams.Flags.dismissDreamOnKeyguardDismiss; import static android.view.RemoteAnimationTarget.MODE_OPENING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; @@ -89,14 +90,14 @@ import com.android.wm.shell.shared.ShellTransitions; import com.android.wm.shell.shared.TransitionUtil; import com.android.wm.shell.transition.Transitions; -import kotlinx.coroutines.CoroutineScope; - import java.util.ArrayList; import java.util.Map; import java.util.WeakHashMap; import javax.inject.Inject; +import kotlinx.coroutines.CoroutineScope; + public class KeyguardService extends Service { static final String TAG = "KeyguardService"; static final String PERMISSION = android.Manifest.permission.CONTROL_KEYGUARD; @@ -223,6 +224,20 @@ public class KeyguardService extends Service { initAlphaForAnimationTargets(t, apps); initAlphaForAnimationTargets(t, wallpapers); + // If the keyguard is going away, hide the dream if one exists. + if (dismissDreamOnKeyguardDismiss() + && (info.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) { + for (RemoteAnimationTarget app : apps) { + final boolean isDream = app.taskInfo != null + && app.taskInfo.getActivityType() + == WindowConfiguration.ACTIVITY_TYPE_DREAM; + if (isDream && app.mode == RemoteAnimationTarget.MODE_CLOSING) { + t.hide(app.leash); + break; + } + } + } + t.apply(); runner.onAnimationStart( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 46aec257ba39..5d31d1e0e7af 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -21,6 +21,7 @@ import static android.app.StatusBarManager.SESSION_KEYGUARD; import static android.provider.Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT; import static android.provider.Settings.System.LOCKSCREEN_SOUNDS_ENABLED; import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT; +import static android.service.dreams.Flags.dismissDreamOnKeyguardDismiss; import static android.view.RemoteAnimationTarget.MODE_OPENING; import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS; import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT; @@ -3092,13 +3093,22 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, createInteractionJankMonitorConf( CUJ_LOCKSCREEN_UNLOCK_ANIMATION, "DismissPanel")); + // Filter out any closing apps, such as the dream. + RemoteAnimationTarget[] openingApps = apps; + if (dismissDreamOnKeyguardDismiss()) { + openingApps = Arrays.stream(apps) + .filter(a -> a.mode == RemoteAnimationTarget.MODE_OPENING) + .toArray(RemoteAnimationTarget[]::new); + } + // Pass the surface and metadata to the unlock animation controller. RemoteAnimationTarget[] openingWallpapers = Arrays.stream(wallpapers).filter( w -> w.mode == RemoteAnimationTarget.MODE_OPENING).toArray( RemoteAnimationTarget[]::new); + mKeyguardUnlockAnimationControllerLazy.get() .notifyStartSurfaceBehindRemoteAnimation( - apps, openingWallpapers, startTime, + openingApps, openingWallpapers, startTime, mSurfaceBehindRemoteAnimationRequested); } else { mInteractionJankMonitor.begin( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt index e441017c943b..5a28f7113ebd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt @@ -193,5 +193,6 @@ constructor( val TO_AOD_DURATION = TRANSITION_DURATION_MS val TO_PRIMARY_BOUNCER_DURATION = TRANSITION_DURATION_MS val TO_DOZING_DURATION = TRANSITION_DURATION_MS + val TO_OCCLUDED_DURATION = TRANSITION_DURATION_MS } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt index 3b21141273e0..a8e9041abfd7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.ui.transitions import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToAodTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToDozingTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToOccludedTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToPrimaryBouncerTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel @@ -71,6 +72,12 @@ abstract class DeviceEntryIconTransitionModule { @Binds @IntoSet + abstract fun alternateBouncerToOccluded( + impl: AlternateBouncerToOccludedTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet abstract fun alternateBouncerToPrimaryBouncer( impl: AlternateBouncerToPrimaryBouncerTransitionViewModel ): DeviceEntryIconTransition diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModel.kt new file mode 100644 index 000000000000..27febd38a97c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModel.kt @@ -0,0 +1,49 @@ +/* + * 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.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor.Companion.TO_OCCLUDED_DURATION +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow + +/** + * Breaks down ALTERNATE_BOUNCER->GONE transition into discrete steps for corresponding views to + * consume. + */ +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class AlternateBouncerToOccludedTransitionViewModel +@Inject +constructor( + animationFlow: KeyguardTransitionAnimationFlow, +) : DeviceEntryIconTransition { + private val transitionAnimation = + animationFlow.setup( + duration = TO_OCCLUDED_DURATION, + from = ALTERNATE_BOUNCER, + to = KeyguardState.OCCLUDED, + ) + + override val deviceEntryParentViewAlpha: Flow<Float> = + transitionAnimation.immediatelyTransitionTo(0f) +} 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 30f33a3a2f6f..f8086f5f6fb4 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.Color 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.plugins.NavigationEdgeBackPlugin 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 @@ internal constructor( 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 @@ internal constructor( 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 @@ internal constructor( windowManager, viewConfiguration, mainHandler, + systemClock, vibratorHelper, configurationController, latencyTracker, @@ -158,9 +163,9 @@ internal constructor( 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 @@ internal constructor( // 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 @@ internal constructor( // 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 @@ internal constructor( } 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 @@ internal constructor( 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 @@ internal constructor( mView.isVisible = true updateRestingArrowDimens() - gestureEntryTime = SystemClock.uptimeMillis() + gestureEntryTime = systemClock.uptimeMillis() } GestureState.ACTIVE -> { previousXTranslationOnActiveOffset = previousXTranslation @@ -927,7 +943,7 @@ internal constructor( mView.popOffEdge(popVelocity) } GestureState.INACTIVE -> { - gestureInactiveTime = SystemClock.uptimeMillis() + gestureInactiveTime = systemClock.uptimeMillis() // Typically entering INACTIVE means // totalTouchDelta <= deactivationSwipeTriggerThreshold @@ -1041,6 +1057,11 @@ internal constructor( 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 d5b05ef68288..60469c070bf7 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 @@ import javax.inject.Inject; 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 @@ public class InternetDialogController implements AccessPointController.AccessPoi @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 2a73b5394a79..063a52c43a1b 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 @@ import dagger.Provides 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 994b01216c22..5748ad459ed6 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 @@ constructor( 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 2ccd3b9e9f8a..93cef61d9dae 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 @@ constructor( 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 39ec12f2701c..d5de28a3ddde 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.notification.domain.interactor.HeadsUpNoti 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 @@ constructor( 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 @@ constructor( 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 @@ constructor( } .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 864f29a5c5e0..d4e711e38b3c 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 @@ public class ImageExporter { 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 afb0280a3917..d1b08f167f22 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 @@ public interface ReferenceScreenshotModule { @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 12a3daa57517..9b754f3271a7 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.view.WindowManager 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 @@ constructor( 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 @@ constructor( 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 @@ constructor( 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 @@ constructor( 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 000000000000..cf62a14d886c --- /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 2e4473e75ed0..4eceb176fcd2 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.graphics.Rect 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 @@ import kotlin.math.sign 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 f9af4b93bf15..4437bf533353 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 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) : // 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 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) : 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 3376b8c84826..734a530efea7 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 @@ object ScreenshotShelfViewBinder { 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 @@ object ScreenshotShelfViewBinder { } } } + + 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/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java index 8b7e11c4ab47..4a636d28aa88 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java @@ -68,7 +68,6 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBypassController; -import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.StatusBarWindowCallback; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -131,7 +130,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW mCallbacks = new ArrayList<>(); private final SysuiColorExtractor mColorExtractor; - private final ScreenOffAnimationController mScreenOffAnimationController; /** * Layout params would be aggregated and dispatched all at once if this is > 0. * @@ -159,7 +157,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW SysuiColorExtractor colorExtractor, DumpManager dumpManager, KeyguardStateController keyguardStateController, - ScreenOffAnimationController screenOffAnimationController, AuthController authController, Lazy<ShadeInteractor> shadeInteractorLazy, ShadeWindowLogger logger, @@ -179,7 +176,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW mKeyguardBypassController = keyguardBypassController; mBackgroundExecutor = backgroundExecutor; mColorExtractor = colorExtractor; - mScreenOffAnimationController = screenOffAnimationController; // prefix with {slow} to make sure this dumps at the END of the critical section. dumpManager.registerCriticalDumpable("{slow}NotificationShadeWindowControllerImpl", this); mAuthController = authController; @@ -441,14 +437,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW private void applyFocusableFlag(NotificationShadeWindowState state) { boolean panelFocusable = state.notificationShadeFocusable && state.shadeOrQsExpanded; if (state.bouncerShowing && (state.keyguardOccluded || state.keyguardNeedsInput) - || ENABLE_REMOTE_INPUT && state.remoteInputActive - // Make the panel focusable if we're doing the screen off animation, since the light - // reveal scrim is drawing in the panel and should consume touch events so that they - // don't go to the app behind. - || mScreenOffAnimationController.shouldIgnoreKeyguardTouches()) { - mLpChanged.flags &= ~LayoutParams.FLAG_NOT_FOCUSABLE; - mLpChanged.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM; - } else if (state.glanceableHubShowing) { + || (ENABLE_REMOTE_INPUT && state.remoteInputActive) + || state.glanceableHubShowing) { mLpChanged.flags &= ~LayoutParams.FLAG_NOT_FOCUSABLE; mLpChanged.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM; } else if (state.isKeyguardShowingAndNotOccluded() || panelFocusable) { 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 000000000000..4db40582def4 --- /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 0dcbe9b2bfc4..229bdce62cf6 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.IndentingPrintWriter; 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 @@ public class ObservableServiceConnection<T> implements ServiceConnection { 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 @@ public class ObservableServiceConnection<T> implements ServiceConnection { * 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 @@ public class ObservableServiceConnection<T> implements ServiceConnection { 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 @@ public class ObservableServiceConnection<T> implements ServiceConnection { Log.d(TAG, "addCallback:" + callback); } - mExecutor.execute(() -> { + mBgExecutor.execute(() -> { final Iterator<WeakReference<Callback<T>>> iterator = mCallbacks.iterator(); while (iterator.hasNext()) { @@ -210,14 +214,15 @@ public class ObservableServiceConnection<T> implements ServiceConnection { * 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 @@ public class ObservableServiceConnection<T> implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { - mExecutor.execute(() -> { + mBgExecutor.execute(() -> { if (DEBUG) { Log.d(TAG, "onServiceConnected"); } @@ -268,7 +273,7 @@ public class ObservableServiceConnection<T> implements ServiceConnection { 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 @@ public class ObservableServiceConnection<T> implements ServiceConnection { @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 5979f3e60cb9..64f8246118c8 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 @@ public class PersistentConnectionManager<T> implements Dumpable { 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 @@ public class PersistentConnectionManager<T> implements Dumpable { 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 @@ public class PersistentConnectionManager<T> implements Dumpable { } }; - // 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 @@ public class PersistentConnectionManager<T> implements Dumpable { @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 @@ public class PersistentConnectionManager<T> implements Dumpable { "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 ee642a64242d..038633810fd7 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 @@ package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel 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 @@ constructor( 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 @@ constructor( 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 @@ constructor( * 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 77b30402c040..cfe37eef1f87 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.broadcast.BroadcastDispatcher; 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 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { 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 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { mVibratorHelper, mResources, mConfigurationController, + mActivityStarter, mUserTracker, mKeyguardStateController, mUserManager, @@ -458,6 +461,18 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { } } + 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 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { 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/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 59f7d613c1e6..325e7bf31a43 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -262,7 +262,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mColorExtractor, mDumpManager, mKeyguardStateController, - mScreenOffAnimationController, mAuthController, () -> mShadeInteractor, mShadeWindowLogger, 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 e6c259abb456..f1c97dd45f09 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.MotionEvent.ACTION_UP 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.plugins.NavigationEdgeBackPlugin 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.any 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 @@ class BackPanelControllerTest : SysuiTestCase() { } 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 @@ class BackPanelControllerTest : SysuiTestCase() { @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 @@ class BackPanelControllerTest : SysuiTestCase() { mBackPanelController.setLayoutParams(layoutParams) mBackPanelController.setBackCallback(backCallback) mBackPanelController.setIsLeftPanel(true) - testableLooper = TestableLooper.get(this) triggerThreshold = mBackPanelController.params.staticTriggerThreshold } @@ -103,6 +108,7 @@ class BackPanelControllerTest : SysuiTestCase() { assertThat(mBackPanelController.currentState) .isEqualTo(BackPanelController.GestureState.GONE) + verify(interactionJankMonitor, never()).begin(any()) } @Test @@ -110,23 +116,37 @@ class BackPanelControllerTest : SysuiTestCase() { 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 @@ class BackPanelControllerTest : SysuiTestCase() { 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 @@ class BackPanelControllerTest : SysuiTestCase() { 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 5d341207ef6a..8d26c877f4cf 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 @@ public class ObservableServiceConnectionTest extends SysuiTestCase { connection.addCallback(mCallback); mExecutor.runAllReady(); connection.bind(); + mExecutor.runAllReady(); when(mTransformer.convert(eq(mBinder))).thenReturn(mResult); @@ -143,8 +142,8 @@ public class ObservableServiceConnectionTest extends SysuiTestCase { 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 @@ public class ObservableServiceConnectionTest extends SysuiTestCase { 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 @@ public class ObservableServiceConnectionTest extends SysuiTestCase { // 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 @@ public class ObservableServiceConnectionTest extends SysuiTestCase { .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 db998f3b5cd8..56e5e293c799 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.UserHandle; 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.internal.statusbar.IStatusBarService; 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.VisualInterrupti 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.ConfigurationController; 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.TaskView; 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.List; 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 @@ -338,8 +317,6 @@ public class BubblesTest extends SysuiTestCase { @Mock private KeyguardStateController mKeyguardStateController; @Mock - private ScreenOffAnimationController mScreenOffAnimationController; - @Mock Transitions mTransitions; @Mock private Optional<OneHandedController> mOneHandedOptional; @@ -357,11 +334,8 @@ public class BubblesTest extends SysuiTestCase { 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; @@ -378,8 +352,16 @@ public class BubblesTest extends SysuiTestCase { 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 { @@ -404,77 +386,14 @@ public class BubblesTest extends SysuiTestCase { 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, @@ -491,7 +410,6 @@ public class BubblesTest extends SysuiTestCase { mColorExtractor, mDumpManager, mKeyguardStateController, - mScreenOffAnimationController, mAuthController, () -> mShadeInteractor, mShadeWindowLogger, @@ -2470,6 +2388,10 @@ public class BubblesTest extends SysuiTestCase { 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/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModelKosmos.kt new file mode 100644 index 000000000000..71ad3c6689f7 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModelKosmos.kt @@ -0,0 +1,30 @@ +/* + * 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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import kotlinx.coroutines.ExperimentalCoroutinesApi + +val Kosmos.alternateBouncerToOccludedTransitionViewModel by Fixture { + AlternateBouncerToOccludedTransitionViewModel( + animationFlow = keyguardTransitionAnimationFlow, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt index d4b793720328..a81ac038b38a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt @@ -56,7 +56,6 @@ import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shade.shadeController import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor -import com.android.systemui.statusbar.phone.screenOffAnimationController import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor @@ -90,7 +89,6 @@ class KosmosJavaAdapter( val simBouncerInteractor by lazy { kosmos.simBouncerInteractor } val statusBarStateController by lazy { kosmos.statusBarStateController } val interactionJankMonitor by lazy { kosmos.interactionJankMonitor } - val screenOffAnimationController by lazy { kosmos.screenOffAnimationController } val fakeSceneContainerConfig by lazy { kosmos.sceneContainerConfig } val sceneInteractor by lazy { kosmos.sceneInteractor } val falsingCollector by lazy { kosmos.falsingCollector } 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 16d08dd91ca8..fff3b14c95ec 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.fakeScenes by Fixture { 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 c0f50393b1d7..ebe591b32536 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.settings.displayTracker 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 @@ val Kosmos.sceneContainerStartable by Fixture { 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 1611f628e5c4..f71bf0374646 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 @@ package com.android.systemui.statusbar.phone 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 e77f846ffaa6..f6885e1e74ba 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 b5843d0fa26b..beacde282d49 100755 --- a/ravenwood/scripts/ravenwood-stats-collector.sh +++ b/ravenwood/scripts/ravenwood-stats-collector.sh @@ -39,14 +39,18 @@ dump() { 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_stats() { 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 2d1aba4b43aa..bc83a0edd918 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -2691,7 +2691,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } + /** + * 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 23891d23cf4f..ec0d8974603e 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 @@ public final class ActiveServices { 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 b7030765d053..c6c1f9873a45 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.Arrays; 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 @@ class UserController implements Handler.Callback { 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 @@ class UserController implements Handler.Callback { 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,34 +2515,56 @@ class UserController implements Handler.Callback { @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)); - } - )); - } - - private void await(boolean condition, Consumer<Runnable> conditionalStep, Runnable nextStep) { - if (condition) { - conditionalStep.accept(nextStep); + 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 { - nextStep.run(); + sendUserSwitchCompleteMessage.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) { boolean homeInFront = mInjector.taskSupervisorSwitchUser(newUserId, uss); if (homeInFront) { @@ -3977,29 +3998,45 @@ class UserController implements Handler.Callback { 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) { + + } + + @Override + public void onKeyguardStateChanged(boolean isShowing) { + if (isShowing) { + getActivityTaskManagerInternal().unregisterScreenObserver(this); + runnable.run(); + } + } + } + ); + getWindowManager().lockDeviceNow(); + } - mHandler.postDelayed(runOnce, DISMISS_KEYGUARD_TIMEOUT_MS); + 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 @@ class UserController implements Handler.Callback { 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 e59de6aceb04..798aaee58a8e 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 @@ public class AppOpsService extends IAppOpsService.Stub { 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 @@ public class AppOpsService extends IAppOpsService.Stub { 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 77654d4a5413..da528a2591a1 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 @@ public class AudioDeviceBroker { @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 @@ public class AudioDeviceBroker { 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 @@ public class AudioDeviceBroker { // 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 9bdc51efb76f..c9612caf67a0 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 @@ public class AudioDeviceInventory { } } + // 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 @@ public class AudioDeviceInventory { + "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 @@ public class AudioDeviceInventory { .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 @@ public class AudioDeviceInventory { 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 @@ public class AudioDeviceInventory { .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 @@ public class AudioDeviceInventory { + address + " codec=" + AudioSystem.audioFormatToString(codec)) .printSlog(EventLogger.Event.ALOGI, TAG)); + delayMs = BT_CONFIG_CHANGE_MUTE_DELAY_MS; } } } @@ -952,6 +969,7 @@ public class AudioDeviceInventory { } } 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 e330ed56239b..030ce12f5063 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_RAISE; 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 @@ class AudioManagerShellCommand extends ShellCommand { 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 @@ class AudioManagerShellCommand extends ShellCommand { 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 @@ class AudioManagerShellCommand extends ShellCommand { 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 @@ class AudioManagerShellCommand extends ShellCommand { 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/location/gnss/GnssConfiguration.java b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java index a5939e924adb..a439f16e6d97 100644 --- a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java +++ b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java @@ -386,7 +386,7 @@ public class GnssConfiguration { configs = CarrierConfigManager.getDefaultConfig(); } for (String configKey : configs.keySet()) { - if (configKey.startsWith(CarrierConfigManager.Gps.KEY_PREFIX)) { + if (configKey != null && configKey.startsWith(CarrierConfigManager.Gps.KEY_PREFIX)) { String key = configKey .substring(CarrierConfigManager.Gps.KEY_PREFIX.length()) .toUpperCase(Locale.ROOT); 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 8002300dacc0..880787e8664c 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 @@ class GnssNetworkConnectivityHandler { 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 4fc1a17032e9..ad6b0ca71527 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/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index bff3d39d7361..9d4ab11d7001 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -266,7 +266,6 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.WorkSource; import android.permission.PermissionManager; -import android.provider.DeviceConfig; import android.provider.Settings; import android.provider.Settings.Secure; import android.service.notification.Adjustment; @@ -313,7 +312,6 @@ import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.compat.IPlatformCompat; -import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags; import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags; import com.android.internal.logging.InstanceId; @@ -704,7 +702,6 @@ public class NotificationManagerService extends SystemService { private ConditionProviders mConditionProviders; private NotificationUsageStats mUsageStats; private boolean mLockScreenAllowSecureNotifications = true; - boolean mSystemExemptFromDismissal = false; final ArrayMap<String, ArrayMap<Integer, RemoteCallbackList<ICallNotificationEventCallback>>> mCallNotificationEventCallbacks = new ArrayMap<>(); @@ -722,7 +719,6 @@ public class NotificationManagerService extends SystemService { private GroupHelper mGroupHelper; private int mAutoGroupAtCount; private boolean mIsTelevision; - private DeviceConfig.OnPropertiesChangedListener mDeviceConfigChangedListener; protected NotificationAttentionHelper mAttentionHelper; private int mWarnRemoteViewsSizeBytes; @@ -973,18 +969,6 @@ public class NotificationManagerService extends SystemService { } protected void setDefaultAssistantForUser(int userId) { - String overrideDefaultAssistantString = DeviceConfig.getProperty( - DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.NAS_DEFAULT_SERVICE); - if (overrideDefaultAssistantString != null) { - ArraySet<ComponentName> approved = mAssistants.queryPackageForServices( - overrideDefaultAssistantString, - MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, - userId); - for (int i = 0; i < approved.size(); i++) { - if (allowAssistant(userId, approved.valueAt(i))) return; - } - } ArraySet<ComponentName> defaults = mAssistants.getDefaultComponents(); // We should have only one default assistant by default // allowAssistant should execute once in practice @@ -2670,10 +2654,6 @@ public class NotificationManagerService extends SystemService { mStatsManager.clearPullAtomCallback(DND_MODE_RULE); mAppOps.stopWatchingMode(mAppOpsListener); mAlarmManager.cancelAll(); - - if (mDeviceConfigChangedListener != null) { - DeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigChangedListener); - } } protected String[] getStringArrayResource(int key) { @@ -2744,27 +2724,6 @@ public class NotificationManagerService extends SystemService { publishLocalService(NotificationManagerInternal.class, mInternalService); } - void registerDeviceConfigChange() { - mDeviceConfigChangedListener = properties -> { - if (!DeviceConfig.NAMESPACE_SYSTEMUI.equals(properties.getNamespace())) { - return; - } - for (String name : properties.getKeyset()) { - if (SystemUiDeviceConfigFlags.NAS_DEFAULT_SERVICE.equals(name)) { - mAssistants.resetDefaultAssistantsIfNecessary(); - } - } - }; - mSystemExemptFromDismissal = DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER, - /* name= */ "application_exemptions", - /* defaultValue= */ true); - DeviceConfig.addOnPropertiesChangedListener( - DeviceConfig.NAMESPACE_SYSTEMUI, - new HandlerExecutor(mHandler), - mDeviceConfigChangedListener); - } - private void registerNotificationPreferencesPullers() { mPullAtomCallback = new StatsPullAtomCallbackImpl(); mStatsManager.setPullAtomCallback( @@ -2938,7 +2897,6 @@ public class NotificationManagerService extends SystemService { mAssistants.onBootPhaseAppsCanStart(); mConditionProviders.onBootPhaseAppsCanStart(); mHistoryManager.onBootPhaseAppsCanStart(); - registerDeviceConfigChange(); migrateDefaultNAS(); maybeShowInitialReviewPermissionsNotification(); @@ -7738,7 +7696,7 @@ public class NotificationManagerService extends SystemService { return true; } // Check if an app has been given system exemption - return mSystemExemptFromDismissal && mAppOps.checkOpNoThrow( + return mAppOps.checkOpNoThrow( AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, ai.uid, ai.packageName) == MODE_ALLOWED; } diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 474768935bbd..143bc5cb20ff 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 @@ public class ZenModeHelper { 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 c8fd7e47d80a..8a853287738b 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 @@ package com.android.server.os; 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.IOException; 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 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { } 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 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { void setSystemProperty(String key, String value) { SystemProperties.set(key, value); } + + RoleManagerWrapper getRoleManagerWrapper() { + return mRoleManagerWrapper; + } } BugreportManagerServiceImpl(Context context) { @@ -546,7 +560,7 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { 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 0f4e4821dee8..ae485ede1bec 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 @@ public class PackageManagerService implements PackageSender, TestUtilityService // 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 @@ public class PackageManagerService implements PackageSender, TestUtilityService // 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 @@ public class PackageManagerService implements PackageSender, TestUtilityService 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 a0b6897a080e..20598f91a51d 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 @@ package com.android.server.pm; 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.Path; 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 @@ final class PackageMetrics { 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/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 42efc2de5b47..1f320dab99dd 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -4105,6 +4105,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A boolean removedFromHistory = false; cleanUp(false /* cleanServices */, false /* setState */); + setVisibleRequested(false); if (hasProcess()) { app.removeActivity(this, true /* keepAssociation */); @@ -8689,6 +8690,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // calculate the override, skip the override. return; } + // Make sure the orientation related fields will be updated by the override insets, because + // fixed rotation has assigned the fields from display's configuration. + if (hasFixedRotationTransform()) { + inOutConfig.windowConfiguration.setAppBounds(null); + inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED; + inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED; + inOutConfig.smallestScreenWidthDp = Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; + inOutConfig.orientation = ORIENTATION_UNDEFINED; + } // Override starts here. final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); @@ -8725,8 +8735,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // For the case of PIP transition and multi-window environment, the // smallestScreenWidthDp is handled already. Override only if the app is in // fullscreen. - DisplayInfo info = new DisplayInfo(); - mDisplayContent.getDisplay().getDisplayInfo(info); + final DisplayInfo info = new DisplayInfo(mDisplayContent.getDisplayInfo()); mDisplayContent.computeSizeRanges(info, rotated, dw, dh, mDisplayContent.getDisplayMetrics().density, inOutConfig, true /* overrideConfig */); diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index 3b422503591f..c9703d871431 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 @@ class BackNavigationController { 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 @@ -312,7 +311,9 @@ class BackNavigationController { // another task. final Task prevParent = prevTask.getParent().asTask(); final Task currParent = currentTask.getParent().asTask(); - if (prevTask.inMultiWindowMode() && prevParent != currParent) { + if ((prevTask.inMultiWindowMode() && prevParent != currParent) + // Do not animate to translucent task, it could be trampoline. + || hasTranslucentActivity(currentActivity, prevActivities)) { backType = BackNavigationInfo.TYPE_CALLBACK; } else { removedWindowContainer = prevTask; @@ -527,7 +528,7 @@ class BackNavigationController { } for (int i = prevActivities.size() - 1; i >= 0; --i) { final ActivityRecord test = prevActivities.get(i); - if (!test.occludesParent() || test.showWallpaper()) { + if (!test.occludesParent() || test.hasWallpaper()) { return true; } } diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java index f04b4af285be..d55e41534ac8 100644 --- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java +++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java @@ -48,8 +48,15 @@ public class DesktopModeLaunchParamsModifier implements LaunchParamsModifier { /** * Flag to indicate whether to restrict desktop mode to supported devices. */ + @VisibleForTesting + static final String ENFORCE_DEVICE_RESTRICTIONS_KEY = + "persist.wm.debug.desktop_mode_enforce_device_restrictions"; + + /** + * Flag to indicate whether to restrict desktop mode to supported devices. + */ private static final boolean ENFORCE_DEVICE_RESTRICTIONS = SystemProperties.getBoolean( - "persist.wm.debug.desktop_mode_enforce_device_restrictions", true); + ENFORCE_DEVICE_RESTRICTIONS_KEY, true); private StringBuilder mLogBuilder; @@ -111,19 +118,7 @@ public class DesktopModeLaunchParamsModifier implements LaunchParamsModifier { } 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 +130,7 @@ public class DesktopModeLaunchParamsModifier implements LaunchParamsModifier { appendLog("setting desktop mode task bounds to %s", outParams.mBounds); - return RESULT_DONE; + return RESULT_CONTINUE; } /** @@ -178,24 +173,24 @@ public class DesktopModeLaunchParamsModifier implements LaunchParamsModifier { * Return {@code true} if desktop mode should be restricted to supported devices. */ @VisibleForTesting - public boolean enforceDeviceRestrictions() { + static boolean enforceDeviceRestrictions() { return ENFORCE_DEVICE_RESTRICTIONS; } /** * Return {@code true} if the current device supports desktop mode. */ + // TODO(b/337819319): use a companion object instead. @VisibleForTesting - public boolean isDesktopModeSupported(@NonNull Context context) { + static boolean isDesktopModeSupported(@NonNull Context context) { return context.getResources().getBoolean(R.bool.config_isDesktopModeSupported); } /** * Return {@code true} if desktop mode can be entered on the current device. */ - boolean canEnterDesktopMode(@NonNull Context context) { + static boolean canEnterDesktopMode(@NonNull Context context) { return isDesktopModeEnabled() && (!enforceDeviceRestrictions() || isDesktopModeSupported(context)); } - } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 9a7f87d04e88..9b98380dd5e8 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3691,6 +3691,9 @@ class Task extends TaskFragment { info.requestedVisibleTypes = topMainWin.getRequestedVisibleTypes(); } } + final Rect rotatedBounds = activity.getFixedRotationTransformDisplayBounds(); + info.taskBounds.set(rotatedBounds != null ? rotatedBounds + : info.taskInfo.configuration.windowConfiguration.getBounds()); // If the developer has persist a different configuration, we need to override it to the // starting window because persisted configuration does not effect to Task. info.taskInfo.configuration.setTo(activity.getConfiguration()); diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 90ac57613cff..a437914a324e 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -3889,6 +3889,18 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return false; } + + /** @return {@code true} if this container wants to show wallpaper. */ + boolean hasWallpaper() { + for (int i = mChildren.size() - 1; i >= 0; --i) { + final WindowContainer child = mChildren.get(i); + if (child.hasWallpaper()) { + return true; + } + } + return false; + } + @Nullable static WindowContainer fromBinder(IBinder binder) { return RemoteToken.fromBinder(binder).getContainer(); diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index d91a2115a884..fd1b5bee7060 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -110,9 +110,9 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio private static final String TAG_RELEASE = TAG + POSTFIX_RELEASE; private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION; - private static final int MAX_RAPID_ACTIVITY_LAUNCH_COUNT = 50; + private static final int MAX_RAPID_ACTIVITY_LAUNCH_COUNT = 200; private static final long RAPID_ACTIVITY_LAUNCH_MS = 500; - private static final long RESET_RAPID_ACTIVITY_LAUNCH_MS = 5 * RAPID_ACTIVITY_LAUNCH_MS; + private static final long RESET_RAPID_ACTIVITY_LAUNCH_MS = 3 * RAPID_ACTIVITY_LAUNCH_MS; public static final int STOPPED_STATE_NOT_STOPPED = 0; public static final int STOPPED_STATE_FIRST_LAUNCH = 1; diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 2fcee50e6f85..c25080f9e756 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -3092,12 +3092,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } void setForceHideNonSystemOverlayWindowIfNeeded(boolean forceHide) { + final int baseType = getBaseType(); if (mSession.mCanAddInternalSystemWindow - || (!isSystemAlertWindowType(mAttrs.type) && mAttrs.type != TYPE_TOAST)) { + || (!isSystemAlertWindowType(baseType) && baseType != TYPE_TOAST)) { return; } - if (mAttrs.type == TYPE_APPLICATION_OVERLAY && mAttrs.isSystemApplicationOverlay() + if (baseType == TYPE_APPLICATION_OVERLAY && mAttrs.isSystemApplicationOverlay() && mSession.mCanCreateSystemApplicationOverlay) { return; } @@ -5857,6 +5858,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return hasWallpaper(); } + @Override boolean hasWallpaper() { return (mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0 || hasWallpaperForLetterboxBackground(); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 6458eace56cf..d555f1af1a10 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { void loadOwners() { synchronized (getLockObject()) { mOwners.load(); - setDeviceOwnershipSystemPropertyLocked(); if (mOwners.hasDeviceOwner()) { setGlobalSettingDeviceOwnerType( mOwners.getDeviceOwnerType(mOwners.getDeviceOwnerPackageName())); @@ -2720,27 +2710,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { + 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 a0d9be549880..eeb49765cc9d 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 @@ final class PolicyEnforcerCallbacks { } } + // 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 @@ final class PolicyEnforcerCallbacks { 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 23314cdaf041..1322545c8d7e 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 com.google.common.truth.Truth.assertThat; 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.After; 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 @@ public class DreamServiceTest { 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 c6f3eb357442..30e3b1808819 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.any; 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.Looper; 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 @@ public class UserControllerTest { 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 @@ public class UserControllerTest { 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 @@ public class UserControllerTest { // 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 @@ public class UserControllerTest { 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 @@ public class UserControllerTest { 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 @@ public class UserControllerTest { @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 a6f2196cf05b..9862663c37b2 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.anyInt; 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.Mock; 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 @@ public class BugreportManagerServiceImplTest { 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 @@ public class BugreportManagerServiceImplTest { @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 @@ public class BugreportManagerServiceImplTest { @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 @@ public class BugreportManagerServiceImplTest { @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 @@ public class BugreportManagerServiceImplTest { 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/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index ce7a0a03ea0a..74d8433700d3 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -243,7 +243,6 @@ import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.FlagsParameterization; import android.platform.test.flag.junit.SetFlagsRule; import android.platform.test.rule.LimitDevicesRule; -import android.provider.DeviceConfig; import android.provider.MediaStore; import android.provider.Settings; import android.service.notification.Adjustment; @@ -280,7 +279,6 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import com.android.internal.R; -import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.config.sysui.TestableFlagResolver; import com.android.internal.logging.InstanceIdSequence; import com.android.internal.logging.InstanceIdSequenceFake; @@ -602,7 +600,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mContext.getContentResolver()).thenReturn(cr); doNothing().when(cr).registerContentObserver(any(), anyBoolean(), any(), anyInt()); - setDpmAppOppsExemptFromDismissal(false); + when(mAppOpsManager.checkOpNoThrow( + AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, mUid, + mPkg)).thenReturn(AppOpsManager.MODE_IGNORED); // Use this testable looper. mTestableLooper = TestableLooper.get(this); @@ -900,7 +900,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @After public void tearDown() throws Exception { if (mFile != null) mFile.delete(); - clearDeviceConfig(); if (mActivityIntent != null) { mActivityIntent.cancel(); @@ -1200,19 +1199,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { return answers; } - private void clearDeviceConfig() { - DeviceConfig.resetToDefaults( - Settings.RESET_MODE_PACKAGE_DEFAULTS, DeviceConfig.NAMESPACE_SYSTEMUI); - } - - private void setDefaultAssistantInDeviceConfig(String componentName) { - DeviceConfig.setProperty( - DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.NAS_DEFAULT_SERVICE, - componentName, - false); - } - private Notification.Builder getMessageStyleNotifBuilder(boolean addBubbleMetadata, String groupKey, boolean isSummary, boolean mutable) { // Give it a person @@ -9092,7 +9078,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void setDefaultAssistantForUser_fromConfigXml() { - clearDeviceConfig(); ComponentName xmlConfig = new ComponentName("config", "xml"); ArraySet<ComponentName> components = new ArraySet<>(Arrays.asList(xmlConfig)); when(mResources @@ -9115,51 +9100,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void setDefaultAssistantForUser_fromDeviceConfig() { - ComponentName xmlConfig = new ComponentName("xml", "config"); - ComponentName deviceConfig = new ComponentName("device", "config"); - setDefaultAssistantInDeviceConfig(deviceConfig.flattenToString()); - when(mResources - .getString(com.android.internal.R.string.config_defaultAssistantAccessComponent)) - .thenReturn(xmlConfig.flattenToString()); - when(mContext.getResources()).thenReturn(mResources); - when(mAssistants.queryPackageForServices(eq(null), anyInt(), anyInt())) - .thenReturn(new ArraySet<>(Arrays.asList(xmlConfig, deviceConfig))); - when(mAssistants.getDefaultComponents()) - .thenReturn(new ArraySet<>(Arrays.asList(deviceConfig))); - mService.setNotificationAssistantAccessGrantedCallback( - mNotificationAssistantAccessGrantedCallback); - - mService.setDefaultAssistantForUser(0); - - verify(mNotificationAssistantAccessGrantedCallback) - .onGranted(eq(deviceConfig), eq(0), eq(true), eq(false)); - } - - @Test - public void setDefaultAssistantForUser_deviceConfigInvalid() { - ComponentName xmlConfig = new ComponentName("xml", "config"); - ComponentName deviceConfig = new ComponentName("device", "config"); - setDefaultAssistantInDeviceConfig(deviceConfig.flattenToString()); - when(mResources - .getString(com.android.internal.R.string.config_defaultAssistantAccessComponent)) - .thenReturn(xmlConfig.flattenToString()); - when(mContext.getResources()).thenReturn(mResources); - // Only xmlConfig is valid, deviceConfig is not. - when(mAssistants.queryPackageForServices(eq(null), anyInt(), eq(0))) - .thenReturn(new ArraySet<>(Collections.singleton(xmlConfig))); - when(mAssistants.getDefaultComponents()) - .thenReturn(new ArraySet<>(Arrays.asList(xmlConfig, deviceConfig))); - mService.setNotificationAssistantAccessGrantedCallback( - mNotificationAssistantAccessGrantedCallback); - - mService.setDefaultAssistantForUser(0); - - verify(mNotificationAssistantAccessGrantedCallback) - .onGranted(eq(xmlConfig), eq(0), eq(true), eq(false)); - } - - @Test public void clearMultipleDefaultAssistantPackagesShouldEnableOnlyOne() throws RemoteException { ArrayMap<Boolean, ArrayList<ComponentName>> changedListeners = generateResetComponentValues(); @@ -11006,7 +10946,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { tr.addOverride(com.android.internal.R.string.config_defaultListenerAccessPackages, ""); tr.addOverride(com.android.internal.R.string.config_defaultDndAccessPackages, ""); tr.addOverride(com.android.internal.R.string.config_defaultAssistantAccessComponent, ""); - setDefaultAssistantInDeviceConfig(""); mService.loadDefaultApprovedServices(USER_SYSTEM); @@ -13425,7 +13364,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { throws Exception { when(mDevicePolicyManager.isActiveDeviceOwner(mUid)).thenReturn(true); // Given: a notification has the flag FLAG_ONGOING_EVENT set - setDpmAppOppsExemptFromDismissal(false); Notification n = new Notification.Builder(mContext, "test") .setOngoing(true) .build(); @@ -13451,7 +13389,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, mUid, mPkg)).thenReturn(AppOpsManager.MODE_ALLOWED); // Given: a notification has the flag FLAG_ONGOING_EVENT set - setDpmAppOppsExemptFromDismissal(true); Notification n = new Notification.Builder(mContext, "test") .setOngoing(true) .build(); @@ -13459,8 +13396,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // When: fix the notification with NotificationManagerService mService.fixNotification(n, mPkg, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true); - // Then: the notification's flag FLAG_NO_DISMISS should be cleared - assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS); + // Then: the notification's flag FLAG_NO_DISMISS should be set + assertNotSame(0, n.flags & Notification.FLAG_NO_DISMISS); } @Test @@ -13468,9 +13405,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { throws Exception { when(mAppOpsManager.checkOpNoThrow( AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, mUid, - mPkg)).thenReturn(AppOpsManager.MODE_ALLOWED); + mPkg)).thenReturn(AppOpsManager.MODE_IGNORED); // Given: a notification has the flag FLAG_ONGOING_EVENT set - setDpmAppOppsExemptFromDismissal(false); Notification n = new Notification.Builder(mContext, "test") .setOngoing(true) .build(); @@ -15551,14 +15487,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { PendingIntent.FLAG_MUTABLE); } - private void setDpmAppOppsExemptFromDismissal(boolean isOn) { - DeviceConfig.setProperty( - DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER, - /* name= */ "application_exemptions", - String.valueOf(isOn), - /* makeDefault= */ false); - } - private void allowTestPackageToToast() throws Exception { assertWithMessage("toast queue").that(mService.mToastQueue).isEmpty(); mService.isSystemUid = false; 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 9559a256d326..5fdb3965be76 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 @@ public class ZenModeHelperTest extends UiServiceTestCase { 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/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java index ab8c53c6a465..a39a1a8637df 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -167,8 +167,18 @@ public class BackNavigationControllerTests extends WindowTestsBase { assertThat(typeToString(backNavigationInfo.getType())) .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK)); + // reset drawing status to test if previous task is translucent activity + backNavigationInfo.onBackNavigationFinished(false); + mBackNavigationController.clearBackAnimations(); + // simulate translucent + recordA.setOccludesParent(false); + backNavigationInfo = startBackNavigation(); + assertThat(typeToString(backNavigationInfo.getType())) + .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK)); + // reset drawing status to test keyguard occludes topActivity.setOccludesParent(true); + recordA.setOccludesParent(true); backNavigationInfo.onBackNavigationFinished(false); mBackNavigationController.clearBackAnimations(); makeWindowVisibleAndDrawn(topActivity.findMainWindow()); 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 353ba01d3cf4..c2bb16249e7f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java @@ -22,25 +22,28 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static com.android.server.wm.DesktopModeLaunchParamsModifier.DESKTOP_MODE_INITIAL_BOUNDS_SCALE; +import static com.android.server.wm.DesktopModeLaunchParamsModifier.ENFORCE_DEVICE_RESTRICTIONS_KEY; 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; -import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +import android.content.res.Resources; import android.graphics.Rect; +import android.os.SystemProperties; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; +import com.android.internal.R; import com.android.server.wm.LaunchParamsController.LaunchParamsModifier.Result; import com.android.window.flags.Flags; @@ -48,6 +51,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; /** * Tests for desktop mode task bounds. @@ -71,8 +75,6 @@ public class DesktopModeLaunchParamsModifierTests extends WindowTestsBase { @Before public void setUp() throws Exception { mActivity = new ActivityBuilder(mAtm).build(); - mTarget = spy(new DesktopModeLaunchParamsModifier(mContext)); - doReturn(true).when(mTarget).isDesktopModeSupported(any()); mCurrent = new LaunchParamsController.LaunchParams(); mCurrent.reset(); mResult = new LaunchParamsController.LaunchParams(); @@ -82,33 +84,43 @@ public class DesktopModeLaunchParamsModifierTests extends WindowTestsBase { @Test @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) public void testReturnsContinueIfDesktopWindowingIsDisabled() { + setupDesktopModeLaunchParamsModifier(); + assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(null).calculate()); } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) public void testReturnsContinueIfDesktopWindowingIsEnabledOnUnsupportedDevice() { - doReturn(false).when(mTarget).isDesktopModeSupported(any()); + setupDesktopModeLaunchParamsModifier(/*isDesktopModeSupported=*/ false, + /*enforceDeviceRestrictions=*/ true); + assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(null).calculate()); } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void testReturnsDoneIfDesktopWindowingIsEnabledAndUnsupportedDeviceOverridden() { - doReturn(false).when(mTarget).enforceDeviceRestrictions(); + 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 @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) public void testReturnsSkipIfTaskIsNull() { + setupDesktopModeLaunchParamsModifier(); + assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(null).calculate()); } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) public void testReturnsSkipIfNotBoundsPhase() { + setupDesktopModeLaunchParamsModifier(); + final Task task = new TaskBuilder(mSupervisor).build(); assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(task).setPhase( PHASE_DISPLAY).calculate()); @@ -117,6 +129,8 @@ public class DesktopModeLaunchParamsModifierTests extends WindowTestsBase { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) public void testReturnsSkipIfTaskNotUsingActivityTypeStandardOrUndefined() { + setupDesktopModeLaunchParamsModifier(); + final Task task = new TaskBuilder(mSupervisor).setActivityType( ACTIVITY_TYPE_ASSISTANT).build(); assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(task).calculate()); @@ -124,23 +138,29 @@ public class DesktopModeLaunchParamsModifierTests extends WindowTestsBase { @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 @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) public void testReturnsSkipIfCurrentParamsHasBounds() { + setupDesktopModeLaunchParamsModifier(); + final Task task = new TaskBuilder(mSupervisor).setActivityType( ACTIVITY_TYPE_STANDARD).build(); mCurrent.mBounds.set(/* left */ 0, /* top */ 0, /* right */ 100, /* bottom */ 100); @@ -150,6 +170,8 @@ public class DesktopModeLaunchParamsModifierTests extends WindowTestsBase { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) public void testUsesDefaultBounds() { + setupDesktopModeLaunchParamsModifier(); + final Task task = new TaskBuilder(mSupervisor).setActivityType( ACTIVITY_TYPE_STANDARD).build(); final int displayHeight = 1600; @@ -157,7 +179,7 @@ public class DesktopModeLaunchParamsModifierTests extends WindowTestsBase { 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()); } @@ -165,17 +187,37 @@ public class DesktopModeLaunchParamsModifierTests extends WindowTestsBase { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) public void testUsesDisplayAreaAndWindowingModeFromSource() { + setupDesktopModeLaunchParamsModifier(); + final Task task = new TaskBuilder(mSupervisor).setActivityType( ACTIVITY_TYPE_STANDARD).build(); TaskDisplayArea mockTaskDisplayArea = mock(TaskDisplayArea.class); 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); } + private void setupDesktopModeLaunchParamsModifier() { + setupDesktopModeLaunchParamsModifier(/*isDesktopModeSupported=*/ true, + /*enforceDeviceRestrictions=*/ true); + } + + private void setupDesktopModeLaunchParamsModifier(boolean isDesktopModeSupported, + boolean enforceDeviceRestrictions) { + Resources mockResources = Mockito.mock(Resources.class); + when(mockResources.getBoolean(eq(R.bool.config_isDesktopModeSupported))) + .thenReturn(isDesktopModeSupported); + doReturn(mockResources).when(mContext).getResources(); + + SystemProperties.set(ENFORCE_DEVICE_RESTRICTIONS_KEY, + String.valueOf(enforceDeviceRestrictions)); + + mTarget = new DesktopModeLaunchParamsModifier(mContext); + } + private class CalculateRequestBuilder { private Task mTask; private int mPhase = PHASE_BOUNDS; diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index ac1dc087fedb..8677738f3edc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -4193,11 +4193,49 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + @EnableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION}) + public void testPortraitCloseToSquareDisplayWithTaskbar_insetsOverridden_notLetterboxed() { + // Set up portrait close to square display. + setUpDisplaySizeWithApp(2200, 2280); + final DisplayContent display = mActivity.mDisplayContent; + display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + // Simulate insets, final app bounds are (0, 0, 2200, 2130) - landscape. + final WindowState navbar = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent, + "navbar"); + final Binder owner = new Binder(); + navbar.mAttrs.providedInsets = new InsetsFrameProvider[] { + new InsetsFrameProvider(owner, 0, WindowInsets.Type.navigationBars()) + .setInsetsSize(Insets.of(0, 0, 0, 150)) + }; + display.getDisplayPolicy().addWindowLw(navbar, navbar.mAttrs); + assertTrue(display.getDisplayPolicy().updateDecorInsetsInfo()); + display.sendNewConfiguration(); + + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setTask(mTask) + .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) + .setComponent(ComponentName.createRelative(mContext, + SizeCompatTests.class.getName())) + .setUid(android.os.Process.myUid()) + .build(); + + // Activity should not be letterboxed and should have portrait app bounds even though + // orientation is not respected with insets as insets have been decoupled. + final Rect appBounds = activity.getWindowConfiguration().getAppBounds(); + final Rect displayBounds = display.getBounds(); + assertFalse(activity.isLetterboxedForFixedOrientationAndAspectRatio()); + assertNotNull(appBounds); + assertEquals(displayBounds.width(), appBounds.width()); + assertEquals(displayBounds.height(), appBounds.height()); + } + + @Test @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED}) public void testPortraitCloseToSquareDisplayWithTaskbar_letterboxed() { // Set up portrait close to square display setUpDisplaySizeWithApp(2200, 2280); final DisplayContent display = mActivity.mDisplayContent; + display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); // Simulate taskbar, final app bounds are (0, 0, 2200, 2130) - landscape final WindowState navbar = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent, "navbar"); diff --git a/tests/FlickerTests/IME/AndroidTestTemplate.xml b/tests/FlickerTests/IME/AndroidTestTemplate.xml index 988f76f4175c..38442db699f8 100644 --- a/tests/FlickerTests/IME/AndroidTestTemplate.xml +++ b/tests/FlickerTests/IME/AndroidTestTemplate.xml @@ -10,6 +10,8 @@ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup"> <!-- keeps the screen on during tests --> <option name="screen-always-on" value="on"/> + <!-- enable AOD --> + <option name="set-secure-setting" key="doze_always_on" value="1" /> <!-- prevents the phone from restarting --> <option name="force-skip-system-props" value="true"/> <!-- set WM tracing verbose level to all --> diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt new file mode 100644 index 000000000000..31506b5eabf0 --- /dev/null +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt @@ -0,0 +1,101 @@ +/* + * 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.server.wm.flicker.ime + +import android.platform.test.annotations.Presubmit +import android.platform.test.rule.UnlockScreenRule +import android.tools.Rotation +import android.tools.flicker.junit.FlickerParametersRunnerFactory +import android.tools.flicker.legacy.FlickerBuilder +import android.tools.flicker.legacy.LegacyFlickerTest +import android.tools.flicker.legacy.LegacyFlickerTestFactory +import android.tools.traces.component.ComponentNameMatcher +import com.android.server.wm.flicker.BaseTest +import com.android.server.wm.flicker.helpers.ImeAppHelper +import org.junit.FixMethodOrder +import org.junit.Ignore +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test IME window closing on lock and opening on screen unlock. To run this test: `atest + * FlickerTests:CloseImeWindowToHomeTest` + */ +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class ShowImeOnUnlockScreenTest(flicker: LegacyFlickerTest) : BaseTest(flicker) { + private val testApp = ImeAppHelper(instrumentation) + private val imeOrSnapshot = ComponentNameMatcher.IME.or(ComponentNameMatcher.IME_SNAPSHOT) + + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { + tapl.expectedRotationCheckEnabled = false + testApp.launchViaIntent(wmHelper) + testApp.openIME(wmHelper) + } + transitions { + device.sleep() + wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify() + UnlockScreenRule.unlockScreen(device) + wmHelper.StateSyncBuilder().withImeShown().waitForAndVerify() + } + teardown { testApp.exit(wmHelper) } + } + + @Presubmit + @Test + fun imeAndAppAnimateTogetherWhenLockingAndUnlocking() { + flicker.assertLayers { + this.isVisible(testApp) + .isVisible(imeOrSnapshot) + .then() + .isInvisible(testApp) + .isInvisible(imeOrSnapshot) + .then() + .isVisible(testApp) + .isVisible(imeOrSnapshot) + } + } + + /** {@inheritDoc} */ + @Test + @Ignore("Not applicable to this CUJ. Display turns off during transition") + override fun navBarWindowIsAlwaysVisible() {} + + /** {@inheritDoc} */ + @Test + @Ignore("Not applicable to this CUJ. Display turns off during transition") + override fun statusBarWindowIsAlwaysVisible() {} + + /** {@inheritDoc} */ + @Test + @Ignore("Not applicable to this CUJ. Display turns off during transition") + override fun taskBarWindowIsAlwaysVisible() {} + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( + supportedRotations = listOf(Rotation.ROTATION_0) + ) + } +} diff --git a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java index 7c5dcf8b95f7..e8be33cba3a1 100644 --- a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java +++ b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java @@ -51,6 +51,7 @@ public final class FrameworksTestsFilter extends SelectTest { "android.view.CutoutSpecificationTest", "android.view.DisplayCutoutTest", "android.view.DisplayShapeTest", + "android.view.ImeBackAnimationControllerTest", "android.view.InsetsAnimationControlImplTest", "android.view.InsetsControllerTest", "android.view.InsetsFlagsTest", diff --git a/tools/hoststubgen/TEST_MAPPING b/tools/hoststubgen/TEST_MAPPING index eca258c5a74d..f6885e1e74ba 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 2f432cc7ac96..7212beb6ae4b 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 @@ class HostStubGen(val options: HostStubGenOptions) { 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 da6146911a21..9045db210495 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 @@ open class HostStubGenStats { // 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 @@ open class HostStubGenStats { } } } - - 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 83e122feeeb2..b8d18001f37b 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 @@ fun findAnnotationValueAsString(an: AnnotationNode, propertyName: String): Strin return null } -private val removeLastElement = """[./][^./]*$""".toRegex() +val periodOrSlash = charArrayOf('.', '/') + +fun getPackageNameFromFullClassName(fullClassName: String): String { + val pos = fullClassName.lastIndexOfAny(periodOrSlash) + if (pos == -1) { + return "" + } else { + return fullClassName.substring(0, pos) + } +} + +fun getClassNameFromFullClassName(fullClassName: String): String { + val pos = fullClassName.lastIndexOfAny(periodOrSlash) + if (pos == -1) { + return fullClassName + } else { + return fullClassName.substring(pos + 1) + } +} -fun getPackageNameFromClassName(className: String): String { - return className.replace(removeLastElement, "") +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) + } } -fun resolveClassName(className: String, packageName: String): String { +/** + * 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 000000000000..aaefee4f71e8 --- /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 45e140c8e3ff..6643492a1394 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.HostStubGenStats 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 @@ abstract class BaseAdapter ( ) { 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 @@ abstract class BaseAdapter ( 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 " + |